]> git.tuebingen.mpg.de Git - paraslash.git/blob - wma_afh.c
wma_afh: Compute the chunk time more accurately.
[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 frames_per_chunk, int frequency,
189                 struct timeval *result)
190 {
191         uint64_t x = (uint64_t)frames_per_chunk * 2048 * 1000 * 1000
192                 / frequency;
193
194         result->tv_sec = x / 1000 / 1000;
195         result->tv_usec = x % (1000 * 1000);
196         PARA_INFO_LOG("chunk time: %lums\n", tv2ms(result));
197 }
198
199 /* Must be called on a frame boundary. */
200 static int wma_make_chunk_table(char *buf, size_t buf_size, int block_align,
201                 struct afh_info *afhi)
202 {
203         const uint8_t *f, *start = (uint8_t *)buf;
204         int i, j, frames_per_chunk;
205         size_t ct_size = 250;
206         int ret, count = 0, num_frames, num_superframes;
207
208         afhi->chunk_table = para_malloc(ct_size * sizeof(uint32_t));
209         afhi->chunk_table[0] = 0;
210         afhi->chunk_table[1] = afhi->header_len;
211
212         num_frames = count_frames(buf, buf_size, block_align,
213                 &num_superframes);
214         ret = -E_NO_WMA;
215         if (num_frames == 0 || num_superframes == 0)
216                 goto fail;
217         afhi->seconds_total = num_frames * 2048 /* FIXME */
218                 / afhi->frequency;
219         frames_per_chunk = num_frames / num_superframes;
220         PARA_INFO_LOG("%d frames per chunk\n", frames_per_chunk);
221         i = 0;
222         j = 1;
223         FOR_EACH_FRAME(f, start, buf_size, block_align) {
224                 count += f[WMA_FRAME_SKIP] & 0x0f;
225                 while (count > j * frames_per_chunk) {
226                         j++;
227                         if (j >= ct_size) {
228                                 ct_size *= 2;
229                                 afhi->chunk_table = para_realloc(
230                                         afhi->chunk_table,
231                                         ct_size * sizeof(uint32_t));
232                         }
233                         afhi->chunk_table[j] = f - start + afhi->header_len + block_align + WMA_FRAME_SKIP;
234                 }
235         }
236         afhi->chunks_total = j;
237         set_chunk_tv(frames_per_chunk, afhi->frequency, &afhi->chunk_tv);
238         return 1;
239 fail:
240         free(afhi->chunk_table);
241         return ret;
242 }
243
244 static int wma_get_file_info(char *map, size_t numbytes, __a_unused int fd,
245         struct afh_info *afhi)
246 {
247         int ret;
248         struct asf_header_info ahi;
249
250         ret = read_asf_header(map, numbytes, &ahi);
251         if (ret < 0)
252                 return ret;
253         if (ret == 0)
254                 return -E_NO_WMA;
255         afhi->bitrate = ahi.bit_rate / 1000;
256         if (ahi.sample_rate == 0)
257                 return -E_NO_WMA;
258         afhi->frequency = ahi.sample_rate;
259         afhi->channels = ahi.channels;
260         afhi->header_len = ahi.header_len;
261         afhi->header_offset = 0;
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 }