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