compute_mdct_coefficients(): Kill pointless cast.
[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 }