X-Git-Url: http://git.tuebingen.mpg.de/?p=paraslash.git;a=blobdiff_plain;f=wma_afh.c;h=ebb2f9c34c97dbb445b312dd1e8ab2004b0d5bc6;hp=9cf8e50190db96d86da9eeb76aa2cd6c04ca8236;hb=a85b3b947174c64ce06b4d6e438677055bf3f1ae;hpb=108fa92dea4be74a26e92d9804926abf1c0d4fd4 diff --git a/wma_afh.c b/wma_afh.c index 9cf8e501..ebb2f9c3 100644 --- a/wma_afh.c +++ b/wma_afh.c @@ -1,20 +1,14 @@ /* - * Copyright (C) 2009 Andre Noll + * Copyright (C) 2009 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file wma_afh.c The audio format handler for WMA files. */ -#include -#include #include -#include -#include -#include -#include -#include #include +#include #include "para.h" #include "error.h" @@ -22,29 +16,29 @@ #include "portable_io.h" #include "string.h" #include "wma.h" +#include "fd.h" -#define FOR_EACH_FRAME(_f, _buf, _size, _ba) for (_f = (_buf); \ - _f + (_ba) + WMA_FRAME_SKIP < (_buf) + (_size); \ - _f += (_ba) + WMA_FRAME_SKIP) +#define FOR_EACH_FRAME(_f, _buf, _size, _ps) for (_f = (_buf); \ + _f + (_ps) < (_buf) + (_size); \ + _f += (_ps)) /* * Must be called on a frame boundary, e.g. start + header_len. * \return Frame count, superframe count via *num_superframes. */ -static int count_frames(const char *buf, int buf_size, int block_align, +static int count_frames(const char *buf, int buf_size, uint32_t packet_size, int *num_superframes) { int fc = 0, sfc = 0; /* frame count, superframe count */ const uint8_t *p; - FOR_EACH_FRAME(p, (uint8_t *)buf, buf_size, block_align) { + FOR_EACH_FRAME(p, (uint8_t *)buf, buf_size, packet_size) { fc += p[WMA_FRAME_SKIP] & 0x0f; sfc++; } PARA_INFO_LOG("%d frames, %d superframes\n", fc, sfc); - if (num_superframes) - *num_superframes = sfc; + *num_superframes = sfc; return fc; } @@ -73,7 +67,7 @@ static int put_utf8(uint32_t val, char *result) *out++ = in; return 1; } - bytes = (wma_log2(in) + 4) / 5; + bytes = DIV_ROUND_UP(wma_log2(in), 5); shift = (bytes - 1) * 6; *out++ = (256 - (256 >> bytes)) | (in >> shift); while (shift >= 6) { @@ -107,7 +101,7 @@ static char *get_str16(const char *in, int len) return out; } -static const char comment_header[] = { +static const char content_description_header[] = { 0x33, 0x26, 0xb2, 0x75, 0x8E, 0x66, 0xCF, 0x11, 0xa6, 0xd9, 0x00, 0xaa, 0x00, 0x62, 0xce, 0x6c }; @@ -132,12 +126,12 @@ static const char album_tag_header[] = { /* WM/AlbumTitle */ static void read_asf_tags(const char *buf, int buf_size, struct taginfo *ti) { const char *p, *end = buf + buf_size, *q; - uint16_t len1, len2, len3, len4, len5; + uint16_t len1, len2, len3, len4; - p = search_pattern(comment_header, sizeof(comment_header), - buf, buf_size); + p = search_pattern(content_description_header, + sizeof(content_description_header), buf, buf_size); if (!p || p + 34 >= end) { - PARA_NOTICE_LOG("comment header not found\n"); + PARA_NOTICE_LOG("content description header not found\n"); goto next; } p += 24; @@ -149,7 +143,7 @@ static void read_asf_tags(const char *buf, int buf_size, struct taginfo *ti) p += 2; len4 = read_u16(p); p += 2; - len5 = read_u16(p); + /* ignore length of the rating information */ p += 2; if (p + len1 >= end) goto next; @@ -158,10 +152,10 @@ static void read_asf_tags(const char *buf, int buf_size, struct taginfo *ti) if (p + len2 >= end) goto next; ti->artist = get_str16(p, len2); - p += len2 + len3 + len4; - if (p + len5 >= end) + p += len2 + len3; + if (p + len4 >= end) goto next; - ti->comment = get_str16(p, len5); + ti->comment = get_str16(p, len4); next: p = search_pattern(extended_content_header, sizeof(extended_content_header), buf, buf_size); @@ -197,7 +191,7 @@ static void set_chunk_tv(int frames_per_chunk, int frequency, } /* Must be called on a frame boundary. */ -static int wma_make_chunk_table(char *buf, size_t buf_size, int block_align, +static int wma_make_chunk_table(char *buf, size_t buf_size, uint32_t packet_size, struct afh_info *afhi) { const uint8_t *f, *start = (uint8_t *)buf; @@ -209,7 +203,7 @@ static int wma_make_chunk_table(char *buf, size_t buf_size, int block_align, afhi->chunk_table[0] = 0; afhi->chunk_table[1] = afhi->header_len; - num_frames = count_frames(buf, buf_size, block_align, + num_frames = count_frames(buf, buf_size, packet_size, &num_superframes); ret = -E_NO_WMA; if (num_frames == 0 || num_superframes == 0) @@ -219,7 +213,7 @@ static int wma_make_chunk_table(char *buf, size_t buf_size, int block_align, frames_per_chunk = num_frames / num_superframes / 2; PARA_INFO_LOG("%d frames per chunk\n", frames_per_chunk); j = 1; - FOR_EACH_FRAME(f, start, buf_size, block_align) { + FOR_EACH_FRAME(f, start, buf_size, packet_size) { count += f[WMA_FRAME_SKIP] & 0x0f; while (count > j * frames_per_chunk) { j++; @@ -229,10 +223,12 @@ static int wma_make_chunk_table(char *buf, size_t buf_size, int block_align, afhi->chunk_table, ct_size * sizeof(uint32_t)); } - afhi->chunk_table[j] = f - start + afhi->header_len + block_align + WMA_FRAME_SKIP; + afhi->chunk_table[j] = f - start + afhi->header_len + + packet_size; } } afhi->chunks_total = j; + set_max_chunk_size(afhi); set_chunk_tv(frames_per_chunk, afhi->frequency, &afhi->chunk_tv); return 1; fail: @@ -257,14 +253,396 @@ static int wma_get_file_info(char *map, size_t numbytes, __a_unused int fd, afhi->frequency = ahi.sample_rate; afhi->channels = ahi.channels; afhi->header_len = ahi.header_len; - afhi->header_offset = 0; + + afhi->techinfo = make_message("%s%s%s%s%s", + ahi.use_exp_vlc? "exp vlc" : "", + (ahi.use_bit_reservoir && ahi.use_exp_vlc)? ", " : "", + ahi.use_bit_reservoir? "bit reservoir" : "", + (ahi.use_variable_block_len && + (ahi.use_exp_vlc || ahi.use_bit_reservoir)? ", " : ""), + ahi.use_variable_block_len? "vbl" : "" + ); wma_make_chunk_table(map + ahi.header_len, numbytes - ahi.header_len, - ahi.block_align, afhi); + ahi.packet_size, afhi); read_asf_tags(map, ahi.header_len, &afhi->tags); return 0; } -static const char* wma_suffixes[] = {"wma", NULL}; +struct asf_object { + char *ptr; + uint64_t size; +}; + +struct tag_object_nums { + int content_descr_obj_num; + int extended_content_descr_obj_num; +}; + +struct afs_top_level_header_object { + uint64_t size; + uint32_t num_objects; + uint8_t reserved1, reserved2; + struct asf_object *objects; +}; + +#define CHECK_HEADER(_p, _h) (memcmp((_p), (_h), sizeof((_h))) == 0) + +static int read_asf_objects(const char *src, size_t size, uint32_t num_objects, + struct asf_object *objs, struct tag_object_nums *ton) +{ + int i; + const char *p; + + for (i = 0, p = src; i < num_objects; p += objs[i++].size) { + if (p + 24 > src + size) + return -E_NO_WMA; + objs[i].ptr = (char *)p; + objs[i].size = read_u64(p + 16); + if (p + objs[i].size > src + size) + return -E_NO_WMA; + + if (CHECK_HEADER(p, content_description_header)) + ton->content_descr_obj_num = i; + else if (CHECK_HEADER(p, extended_content_header)) + ton->extended_content_descr_obj_num = i; + } + return 1; +} + +static const char top_level_header_object_guid[] = { + 0x30, 0x26, 0xb2, 0x75, 0x8e, 0x66, 0xcf, 0x11, + 0xa6, 0xd9, 0x00, 0xaa, 0x00, 0x62, 0xce, 0x6c +}; + +static int convert_utf8_to_utf16(char *src, char **dst) +{ + iconv_t cd; + size_t sz, inbytes, outbytes, inbytesleft, outbytesleft; + char *inbuf, *outbuf; + int ret; + + if (!src || !*src) { + *dst = para_calloc(2); + return 0; + } + /* + * Without specifying LE (little endian), iconv includes a byte order + * mark (e.g. 0xFFFE) at the beginning. + */ + cd = iconv_open("UTF-16LE", "UTF-8"); + if (cd == (iconv_t)-1) { + *dst = NULL; + return -ERRNO_TO_PARA_ERROR(errno); + } + inbuf = src; + /* even though src is in UTF-8, strlen() should DTRT */ + inbytes = inbytesleft = strlen(src); + outbytes = outbytesleft = 4 * inbytes + 2; /* hope that's enough */ + *dst = outbuf = para_malloc(outbytes); + sz = iconv(cd, ICONV_CAST &inbuf, &inbytesleft, &outbuf, &outbytesleft); + if (sz == (size_t)-1) { + ret = -ERRNO_TO_PARA_ERROR(errno); + free(*dst); + *dst = NULL; + goto out; + } + assert(outbytes >= outbytesleft); + assert(outbytes - outbytesleft < INT_MAX - 2); + ret = outbytes - outbytesleft; + outbuf = para_realloc(*dst, ret + 2); + outbuf[ret] = outbuf[ret + 1] = '\0'; + ret += 2; + *dst = outbuf; + PARA_INFO_LOG("converted %s to %d UTF-16 bytes\n", src, ret); +out: + if (iconv_close(cd) < 0) + PARA_WARNING_LOG("iconv_close: %s\n", strerror(errno)); + return ret; +} + +/* The content description object contains artist, title, comment. */ +static int make_cdo(struct taginfo *tags, const struct asf_object *cdo, + struct asf_object *result) +{ + const char *cr, *rating; /* orig data */ + uint16_t orig_cr_bytes, orig_rating_bytes; + /* pointers to new UTF-16 tags */ + char *artist = NULL, *title = NULL, *comment = NULL; + /* number of bytes in UTF-16 for the new tags */ + int artist_bytes, title_bytes, comment_bytes, ret; + char *p, null[2] = "\0\0"; + + result->ptr = NULL; + result->size = 0; + ret = convert_utf8_to_utf16(tags->artist, &artist); + if (ret < 0) + return ret; + artist_bytes = ret; + ret = convert_utf8_to_utf16(tags->title, &title); + if (ret < 0) + goto out; + title_bytes = ret; + ret = convert_utf8_to_utf16(tags->comment, &comment); + if (ret < 0) + goto out; + comment_bytes = ret; + + if (cdo) { + uint16_t orig_title_bytes, orig_artist_bytes, orig_comment_bytes; + /* + * Sizes of the five fields (stored as 16-bit numbers) are + * located after the header (16 bytes) and the cdo size (8 + * bytes). + */ + orig_title_bytes = read_u16(cdo->ptr + 24); + orig_artist_bytes = read_u16(cdo->ptr + 26); + orig_cr_bytes = read_u16(cdo->ptr + 28); + orig_comment_bytes = read_u16(cdo->ptr + 30); + orig_rating_bytes = read_u16(cdo->ptr + 32); + cr = cdo->ptr + 34 + orig_title_bytes + orig_artist_bytes; + rating = cr + orig_cr_bytes + orig_comment_bytes; + } else { + orig_cr_bytes = 2; + orig_rating_bytes = 2; + cr = null; + rating = null; + } + + /* compute size of result cdo */ + result->size = 16 + 8 + 5 * 2 + title_bytes + artist_bytes + + orig_cr_bytes + comment_bytes + orig_rating_bytes; + PARA_DEBUG_LOG("cdo is %zu bytes\n", (size_t)result->size); + p = result->ptr = para_malloc(result->size); + memcpy(p, content_description_header, 16); + p += 16; + write_u64(p, result->size); + p += 8; + write_u16(p, title_bytes); + p += 2; + write_u16(p, artist_bytes); + p += 2; + write_u16(p, orig_cr_bytes); + p += 2; + write_u16(p, comment_bytes); + p += 2; + write_u16(p, orig_rating_bytes); + p += 2; + memcpy(p, title, title_bytes); + p += title_bytes; + memcpy(p, artist, artist_bytes); + p += artist_bytes; + memcpy(p, cr, orig_cr_bytes); + p += orig_cr_bytes; + memcpy(p, comment, comment_bytes); + p += comment_bytes; + memcpy(p, rating, orig_rating_bytes); + p += orig_rating_bytes; + assert(p - result->ptr == result->size); + ret = 1; +out: + free(artist); + free(title); + free(comment); + return ret; +} + +/* The extended content description object contains album and year. */ +static int make_ecdo(struct taginfo *tags, struct asf_object *result) +{ + int ret; + char *p, *album = NULL, *year = NULL, null[2] = "\0\0"; + int album_bytes, year_bytes; + + result->ptr = NULL; + result->size = 0; + ret = convert_utf8_to_utf16(tags->album, &album); + if (ret < 0) + return ret; + album_bytes = ret; + ret = convert_utf8_to_utf16(tags->year, &year); + if (ret < 0) + goto out; + year_bytes = ret; + result->size = 16 + 8 + 2; /* GUID, size, count */ + /* name_length + name + null + data type + val length + val */ + result->size += 2 + sizeof(album_tag_header) + 2 + 2 + 2 + album_bytes; + result->size += 2 + sizeof(year_tag_header) + 2 + 2 + 2 + year_bytes; + + p = result->ptr = para_malloc(result->size); + memcpy(p, extended_content_header, 16); + p += 16; + write_u64(p, result->size); + p += 8; + write_u16(p, 2); /* count */ + p += 2; + + /* album */ + write_u16(p, sizeof(album_tag_header) + 2); + p += 2; + memcpy(p, album_tag_header, sizeof(album_tag_header)); + p += sizeof(album_tag_header); + memcpy(p, null, 2); + p += 2; + write_u16(p, 0); /* data type (UTF-16) */ + p += 2; + write_u16(p, album_bytes); + p += 2; + memcpy(p, album, album_bytes); + p += album_bytes; + + /* year */ + write_u16(p, sizeof(year_tag_header)); + p += 2; + memcpy(p, year_tag_header, sizeof(year_tag_header)); + p += sizeof(year_tag_header); + memcpy(p, null, 2); + p += 2; + write_u16(p, 0); /* data type (UTF-16) */ + p += 2; + write_u16(p, year_bytes); + p += 2; + memcpy(p, year, year_bytes); + p += year_bytes; + assert(p - result->ptr == result->size); + ret = 1; +out: + free(album); + free(year); + return ret; +} + +static int write_output_file(int fd, const char *map, size_t mapsize, + struct afs_top_level_header_object *top, struct tag_object_nums *ton, + struct asf_object *cdo, struct asf_object *ecdo) +{ + int i, ret; + uint64_t sz; /* of the new header object */ + uint32_t num_objects; + char tmp[8]; + + sz = 16 + 8 + 4 + 1 + 1; /* top-level header object */ + for (i = 0; i < top->num_objects; i++) { + if (i == ton->content_descr_obj_num) + continue; + if (i == ton->extended_content_descr_obj_num) + continue; + sz += top->objects[i].size; + } + sz += cdo->size; + sz += ecdo->size; + num_objects = top->num_objects; + if (ton->content_descr_obj_num < 0) + num_objects++; + if (ton->extended_content_descr_obj_num < 0) + num_objects++; + ret = xwrite(fd, top_level_header_object_guid, 16); + if (ret < 0) + goto out; + write_u64(tmp, sz); + ret = xwrite(fd, tmp, 8); + if (ret < 0) + goto out; + write_u32(tmp, num_objects); + ret = xwrite(fd, tmp, 4); + if (ret < 0) + goto out; + write_u8(tmp, top->reserved1); + ret = xwrite(fd, tmp, 1); + if (ret < 0) + goto out; + write_u8(tmp, top->reserved2); + ret = xwrite(fd, tmp, 1); + if (ret < 0) + goto out; + /* + * Write cto and ecto as objects 0 and 1 if they did not exist in the + * original file. + */ + if (ton->content_descr_obj_num < 0) { + ret = xwrite(fd, cdo->ptr, cdo->size); + if (ret < 0) + goto out; + } + if (ton->extended_content_descr_obj_num < 0) { + ret = xwrite(fd, ecdo->ptr, ecdo->size); + if (ret < 0) + goto out; + } + + for (i = 0; i < top->num_objects; i++) { + char *buf = top->objects[i].ptr; + sz = top->objects[i].size; + if (i == ton->content_descr_obj_num) { + buf = cdo->ptr; + sz = cdo->size; + } else if (i == ton->extended_content_descr_obj_num) { + buf = ecdo->ptr; + sz = ecdo->size; + } + ret = xwrite(fd, buf, sz); + if (ret < 0) + goto out; + } + ret = xwrite(fd, map + top->size, mapsize - top->size); +out: + return ret; +} + +static int wma_rewrite_tags(const char *map, size_t mapsize, + struct taginfo *tags, int fd, + __a_unused const char *filename) +{ + struct afs_top_level_header_object top; + struct tag_object_nums ton = {-1, -1}; + const char *p = map; + /* (extended) content description object */ + struct asf_object cdo = {.ptr = NULL}, ecdo = {.ptr = NULL}; + int ret; + + /* guid + size + num_objects + 2 * reserved */ + if (mapsize < 16 + 8 + 4 + 1 + 1) + return -E_NO_WMA; + if (memcmp(map, top_level_header_object_guid, 16)) + return -E_NO_WMA; + p += 16; + top.size = read_u64(p); + PARA_INFO_LOG("header_size: %lu\n", (long unsigned)top.size); + if (top.size >= mapsize) + return -E_NO_WMA; + p += 8; + top.num_objects = read_u32(p); + PARA_NOTICE_LOG("%u header objects\n", top.num_objects); + if (top.num_objects > top.size / 24) + return -E_NO_WMA; + p += 4; + top.reserved1 = read_u8(p); + p++; + top.reserved2 = read_u8(p); + if (top.reserved2 != 2) + return -E_NO_WMA; + p++; /* objects start at p */ + top.objects = para_malloc(top.num_objects * sizeof(struct asf_object)); + ret = read_asf_objects(p, top.size - (p - map), top.num_objects, + top.objects, &ton); + if (ret < 0) + goto out; + ret = make_cdo(tags, ton.content_descr_obj_num >= 0? + top.objects + ton.content_descr_obj_num : NULL, &cdo); + if (ret < 0) + goto out; + ret = make_ecdo(tags, &ecdo); + if (ret < 0) + goto out; + ret = write_output_file(fd, map, mapsize, &top, &ton, &cdo, + &ecdo); +out: + free(cdo.ptr); + free(ecdo.ptr); + free(top.objects); + return ret; +} + +static const char * const wma_suffixes[] = {"wma", NULL}; /** * The init function of the wma audio format handler. @@ -275,4 +653,5 @@ void wma_afh_init(struct audio_format_handler *afh) { afh->get_file_info = wma_get_file_info; afh->suffixes = wma_suffixes; + afh->rewrite_tags = wma_rewrite_tags; }