3df9b5fb818bf3e9a406eb175f7090a796502337
[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 comment_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(comment_header, sizeof(comment_header),
131 buf, buf_size);
132 if (!p || p + 34 >= end) {
133 PARA_NOTICE_LOG("comment 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 }