manual: Improve section on decoders.
[paraslash.git] / wma_afh.c
1 /*
2  * Copyright (C) 2009 Andre Noll <maan@tuebingen.mpg.de>
3  *
4  * Licensed under the GPL v2. For licencing details see COPYING.
5  */
6
7 /** \file wma_afh.c The audio format handler for WMA files. */
8
9 #include <sys/types.h>
10 #include <regex.h>
11
12 #include "para.h"
13 #include "error.h"
14 #include "afh.h"
15 #include "portable_io.h"
16 #include "string.h"
17 #include "wma.h"
18
19 #define FOR_EACH_FRAME(_f, _buf, _size, _ba) for (_f = (_buf); \
20         _f + (_ba) + WMA_FRAME_SKIP < (_buf) + (_size); \
21         _f += (_ba) + WMA_FRAME_SKIP)
22
23 /*
24  * Must be called on a frame boundary, e.g. start + header_len.
25  * \return Frame count, superframe count via *num_superframes.
26  */
27 static int count_frames(const char *buf, int buf_size, int block_align,
28         int *num_superframes)
29 {
30         int fc = 0, sfc = 0; /* frame count, superframe count */
31         const uint8_t *p;
32
33
34         FOR_EACH_FRAME(p, (uint8_t *)buf, buf_size, block_align) {
35                 fc += p[WMA_FRAME_SKIP] & 0x0f;
36                 sfc++;
37         }
38         PARA_INFO_LOG("%d frames, %d superframes\n", fc, sfc);
39         if (num_superframes)
40                 *num_superframes = sfc;
41         return fc;
42 }
43
44 /*
45  * put_utf8() and get_str16() below are based on macros in libavutil/common.h
46  * of the mplayer source code, copyright (c) 2006 Michael Niedermayer
47  * <michaelni@gmx.at>.
48  */
49
50 /*
51  * Convert a 32-bit Unicode character to its UTF-8 encoded form.
52  *
53  * Writes up to 4 bytes for values in the valid UTF-8 range and up to 7 bytes
54  * in the general case, depending on the length of the converted Unicode
55  * character.
56  *
57  * \param result Where the converted UTF-8 bytes are written.
58  */
59 static int put_utf8(uint32_t val, char *result)
60 {
61         char *out = result;
62         int bytes, shift;
63         uint32_t in = val;
64
65         if (in < 0x80) {
66                 *out++ = in;
67                 return 1;
68         }
69         bytes = (wma_log2(in) + 4) / 5;
70         shift = (bytes - 1) * 6;
71         *out++ = (256 - (256 >> bytes)) | (in >> shift);
72         while (shift >= 6) {
73                 shift -= 6;
74                 *out++ = 0x80 | ((in >> shift) & 0x3f);
75         }
76         return out - result;
77 }
78
79 static char *get_str16(const char *in, int len)
80 {
81         const char *p = in;
82         int out_size = 0, out_len = 0;
83         char *out = NULL;
84
85         len /= 2;
86         while (len--) {
87                 uint32_t x;
88                 if (out_len + 7 + 1 >= out_size) {
89                         out_size = 2 * out_size + 50;
90                         out = para_realloc(out, out_size);
91                 }
92                 x = read_u16(p);
93                 p += 2;
94                 out_len += put_utf8(x, out + out_len);
95                 if (x == 0)
96                         return out;
97         }
98         if (out)
99                 out[out_len] = '\0';
100         return out;
101 }
102
103 static const char content_description_header[] = {
104         0x33, 0x26, 0xb2, 0x75, 0x8E, 0x66, 0xCF, 0x11,
105         0xa6, 0xd9, 0x00, 0xaa, 0x00, 0x62, 0xce, 0x6c
106 };
107
108 static const char extended_content_header[] = {
109         0x40, 0xA4, 0xD0, 0xD2, 0x07, 0xE3, 0xD2, 0x11,
110         0x97, 0xF0, 0x00, 0xA0, 0xC9, 0x5E, 0xA8, 0x50
111 };
112
113 static const char year_tag_header[] = { /* WM/Year */
114         0x57, 0x00, 0x4d, 0x00, 0x2f, 0x00, 0x59, 0x00,
115         0x65, 0x00, 0x61, 0x00, 0x72, 0x00
116 };
117
118 static const char album_tag_header[] = { /* WM/AlbumTitle */
119         0x57, 0x00, 0x4d, 0x00, 0x2f, 0x00, 0x41, 0x00,
120         0x6c, 0x00, 0x62, 0x00, 0x75, 0x00, 0x6d, 0x00,
121         0x54, 0x00, 0x69, 0x00, 0x74, 0x00, 0x6c, 0x00,
122         0x65, 0x00
123 };
124
125 static void read_asf_tags(const char *buf, int buf_size, struct taginfo *ti)
126 {
127         const char *p, *end = buf + buf_size, *q;
128         uint16_t len1, len2, len3, len4;
129
130         p = search_pattern(content_description_header,
131                 sizeof(content_description_header), buf, buf_size);
132         if (!p || p + 34 >= end) {
133                 PARA_NOTICE_LOG("content description header not found\n");
134                 goto next;
135         }
136         p += 24;
137         len1 = read_u16(p);
138         p += 2;
139         len2 = read_u16(p);
140         p += 2;
141         len3 = read_u16(p);
142         p += 2;
143         len4 = read_u16(p);
144         p += 2;
145         /* ignore length of the rating information */
146         p += 2;
147         if (p + len1 >= end)
148                 goto next;
149         ti->title = get_str16(p, len1);
150         p += len1;
151         if (p + len2 >= end)
152                 goto next;
153         ti->artist = get_str16(p, len2);
154         p += len2 + len3;
155         if (p + len4 >= end)
156                 goto next;
157         ti->comment = get_str16(p, len4);
158 next:
159         p = search_pattern(extended_content_header, sizeof(extended_content_header),
160                 buf, buf_size);
161         if (!p) {
162                 PARA_NOTICE_LOG("extended content header not found\n");
163                 return;
164         }
165         q = search_pattern(year_tag_header, sizeof(year_tag_header),
166                 p, end - p);
167         if (q) {
168                 const char *r = q + sizeof(year_tag_header) + 6;
169                 if (r < end)
170                         ti->year = get_str16(r, end - r);
171         }
172         q = search_pattern(album_tag_header, sizeof(album_tag_header),
173                 p, end - p);
174         if (q) {
175                 const char *r = q + sizeof(album_tag_header) + 6;
176                 if (r < end)
177                         ti->album = get_str16(r, end - r);
178         }
179 }
180
181 static void set_chunk_tv(int frames_per_chunk, int frequency,
182                 struct timeval *result)
183 {
184         uint64_t x = (uint64_t)frames_per_chunk * 2048 * 1000 * 1000
185                 / frequency;
186
187         result->tv_sec = x / 1000 / 1000;
188         result->tv_usec = x % (1000 * 1000);
189         PARA_INFO_LOG("chunk time: %lums\n", tv2ms(result));
190 }
191
192 /* Must be called on a frame boundary. */
193 static int wma_make_chunk_table(char *buf, size_t buf_size, int block_align,
194                 struct afh_info *afhi)
195 {
196         const uint8_t *f, *start = (uint8_t *)buf;
197         int j, frames_per_chunk;
198         size_t ct_size = 250;
199         int ret, count = 0, num_frames, num_superframes;
200
201         afhi->chunk_table = para_malloc(ct_size * sizeof(uint32_t));
202         afhi->chunk_table[0] = 0;
203         afhi->chunk_table[1] = afhi->header_len;
204
205         num_frames = count_frames(buf, buf_size, block_align,
206                 &num_superframes);
207         ret = -E_NO_WMA;
208         if (num_frames == 0 || num_superframes == 0)
209                 goto fail;
210         afhi->seconds_total = num_frames * 2048 /* FIXME */
211                 / afhi->frequency;
212         frames_per_chunk = num_frames / num_superframes / 2;
213         PARA_INFO_LOG("%d frames per chunk\n", frames_per_chunk);
214         j = 1;
215         FOR_EACH_FRAME(f, start, buf_size, block_align) {
216                 count += f[WMA_FRAME_SKIP] & 0x0f;
217                 while (count > j * frames_per_chunk) {
218                         j++;
219                         if (j >= ct_size) {
220                                 ct_size *= 2;
221                                 afhi->chunk_table = para_realloc(
222                                         afhi->chunk_table,
223                                         ct_size * sizeof(uint32_t));
224                         }
225                         afhi->chunk_table[j] = f - start + afhi->header_len + block_align + WMA_FRAME_SKIP;
226                 }
227         }
228         afhi->chunks_total = j;
229         set_chunk_tv(frames_per_chunk, afhi->frequency, &afhi->chunk_tv);
230         return 1;
231 fail:
232         free(afhi->chunk_table);
233         return ret;
234 }
235
236 static int wma_get_file_info(char *map, size_t numbytes, __a_unused int fd,
237         struct afh_info *afhi)
238 {
239         int ret;
240         struct asf_header_info ahi;
241
242         ret = read_asf_header(map, numbytes, &ahi);
243         if (ret < 0)
244                 return ret;
245         if (ret == 0)
246                 return -E_NO_WMA;
247         afhi->bitrate = ahi.bit_rate / 1000;
248         if (ahi.sample_rate == 0)
249                 return -E_NO_WMA;
250         afhi->frequency = ahi.sample_rate;
251         afhi->channels = ahi.channels;
252         afhi->header_len = ahi.header_len;
253
254         afhi->techinfo = make_message("%s%s%s%s%s",
255                 ahi.use_exp_vlc? "exp vlc" : "",
256                 (ahi.use_bit_reservoir && ahi.use_exp_vlc)? ", " : "",
257                 ahi.use_bit_reservoir? "bit reservoir" : "",
258                 (ahi.use_variable_block_len &&
259                         (ahi.use_exp_vlc || ahi.use_bit_reservoir)? ", " : ""),
260                 ahi.use_variable_block_len? "vbl" : ""
261         );
262         wma_make_chunk_table(map + ahi.header_len, numbytes - ahi.header_len,
263                 ahi.block_align, afhi);
264         read_asf_tags(map, ahi.header_len, &afhi->tags);
265         return 0;
266 }
267
268 static const char* wma_suffixes[] = {"wma", NULL};
269
270 /**
271  * The init function of the wma audio format handler.
272  *
273  * \param afh Pointer to the struct to initialize.
274  */
275 void wma_afh_init(struct audio_format_handler *afh)
276 {
277         afh->get_file_info = wma_get_file_info;
278         afh->suffixes = wma_suffixes;
279 }