dc7994e0053a560a1e716a01312a658d155d175c
[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 void get_str16(const char *in, int len, char *out, int out_size)
87 {
88 const char *p = in;
89 char *q = out;
90
91 len /= 2;
92 while (len-- && q + 7 + 1 < out + out_size) {
93 uint32_t x = read_u16(p);
94 p += 2;
95 q += put_utf8(x, q);
96 if (x == 0)
97 return;
98 }
99 *q = '\0';
100 }
101
102 static const char comment_header[] = {
103 0x33, 0x26, 0xb2, 0x75, 0x8E, 0x66, 0xCF, 0x11,
104 0xa6, 0xd9, 0x00, 0xaa, 0x00, 0x62, 0xce, 0x6c
105 };
106
107 static const char extended_content_header[] = {
108 0x40, 0xA4, 0xD0, 0xD2, 0x07, 0xE3, 0xD2, 0x11,
109 0x97, 0xF0, 0x00, 0xA0, 0xC9, 0x5E, 0xA8, 0x50
110 };
111
112 static const char year_tag_header[] = { /* WM/Year */
113 0x57, 0x00, 0x4d, 0x00, 0x2f, 0x00, 0x59, 0x00,
114 0x65, 0x00, 0x61, 0x00, 0x72, 0x00
115 };
116
117 static const char album_tag_header[] = { /* WM/AlbumTitle */
118 0x57, 0x00, 0x4d, 0x00, 0x2f, 0x00, 0x41, 0x00,
119 0x6c, 0x00, 0x62, 0x00, 0x75, 0x00, 0x6d, 0x00,
120 0x54, 0x00, 0x69, 0x00, 0x74, 0x00, 0x6c, 0x00,
121 0x65, 0x00
122 };
123
124 static void read_asf_tags(const char *buf, int buf_size, struct taginfo *ti)
125 {
126 const char *p, *end = buf + buf_size;
127 char tag[255];
128
129 p = search_pattern(comment_header, sizeof(comment_header),
130 buf, buf_size);
131 if (p) {
132 int len1, len2, len3, len4, len5;
133 p += 24;
134 len1 = read_u16(p);
135 p += 2;
136 len2 = read_u16(p);
137 p += 2;
138 len3 = read_u16(p);
139 p += 2;
140 len4 = read_u16(p);
141 p += 2;
142 len5 = read_u16(p);
143 p += 2;
144 /* TODO: Check len values */
145 get_str16(p, len1, tag, sizeof(tag));
146 ti->title = para_strdup(tag);
147 PARA_INFO_LOG("title: %s\n", tag);
148 get_str16(p + len1, len2, tag, sizeof(tag));
149 ti->artist = para_strdup(tag);
150 PARA_INFO_LOG("artist: %s\n", tag);
151 get_str16(p + len1 + len2 + len3 + len4, len5, tag, sizeof(tag));
152 ti->comment = para_strdup(tag);
153 PARA_INFO_LOG("comment: %s\n", tag);
154 } else
155 PARA_NOTICE_LOG("comment header not found\n");
156 p = search_pattern(extended_content_header, sizeof(extended_content_header),
157 buf, buf_size);
158 if (p) {
159 const char *q;
160
161 q = search_pattern(year_tag_header, sizeof(year_tag_header),
162 p, end - p);
163 if (q) {
164 const char *r = q + sizeof(year_tag_header) + 6;
165 get_str16(r, end - r, tag, sizeof(tag));
166 ti->year = para_strdup(tag);
167 PARA_INFO_LOG("year: %s\n", tag);
168 }
169 q = search_pattern(album_tag_header, sizeof(album_tag_header),
170 p, end - p);
171 if (q) {
172 const char *r = q + sizeof(album_tag_header) + 6;
173 get_str16(r, end - r, tag, sizeof(tag));
174 ti->album = para_strdup(tag);
175 PARA_INFO_LOG("album: %s\n", tag);
176 }
177 return;
178 } else
179 PARA_NOTICE_LOG("extended content header not found\n");
180
181 }
182
183 static void set_chunk_tv(int num_frames, int num_chunks, int frequency,
184 struct timeval *result)
185 {
186 uint64_t x = (uint64_t)num_frames * 1000 * 1000
187 / frequency / num_chunks;
188
189 result->tv_sec = x / 1000 / 1000;
190 result->tv_usec = x % (1000 * 1000);
191 PARA_INFO_LOG("%d chunks, chunk time: %lums\n", num_chunks,
192 tv2ms(result));
193 }
194
195 /* Must be called on a frame boundary. */
196 static int wma_make_chunk_table(char *buf, size_t buf_size, int block_align,
197 struct afh_info *afhi)
198 {
199 const uint8_t *f, *start = (uint8_t *)buf;
200 int i, j, frames_per_chunk, chunk_time;
201 size_t ct_size = 250;
202 int ret, count = 0, num_frames, num_superframes;
203
204 afhi->chunk_table = para_malloc(ct_size * sizeof(uint32_t));
205 afhi->chunk_table[0] = 0;
206 afhi->chunk_table[1] = afhi->header_len;
207
208 num_frames = count_frames(buf, buf_size, block_align,
209 &num_superframes);
210 ret = -E_NO_WMA;
211 if (num_frames == 0 || num_superframes == 0)
212 goto fail;
213 afhi->seconds_total = num_frames * 2048 /* FIXME */
214 / afhi->frequency;
215 frames_per_chunk = num_frames / num_superframes;
216 PARA_INFO_LOG("%d frames per chunk\n", frames_per_chunk);
217 i = 0;
218 j = 1;
219 FOR_EACH_FRAME(f, start, buf_size, block_align) {
220 count += f[WMA_FRAME_SKIP] & 0x0f;
221 while (count > j * frames_per_chunk) {
222 j++;
223 if (j >= ct_size) {
224 ct_size *= 2;
225 afhi->chunk_table = para_realloc(
226 afhi->chunk_table,
227 ct_size * sizeof(uint32_t));
228 }
229 afhi->chunk_table[j] = f - start + afhi->header_len + block_align + WMA_FRAME_SKIP;
230 }
231 }
232 afhi->chunks_total = j;
233 set_chunk_tv(num_frames * 2048, j + 10 /* FIXME */, afhi->frequency, &afhi->chunk_tv);
234 return 1;
235 fail:
236 free(afhi->chunk_table);
237 return ret;
238 }
239
240 static int wma_get_file_info(char *map, size_t numbytes, __a_unused int fd,
241 struct afh_info *afhi)
242 {
243 int ret;
244 struct asf_header_info ahi;
245
246 ret = read_asf_header(map, numbytes, &ahi);
247 if (ret < 0)
248 return ret;
249 if (ret == 0)
250 return -E_NO_WMA;
251 afhi->bitrate = ahi.bit_rate / 1000;
252 if (ahi.sample_rate == 0)
253 return -E_NO_WMA;
254 afhi->frequency = ahi.sample_rate;
255 afhi->channels = ahi.channels;
256 afhi->header_len = ahi.header_len;
257 afhi->header_offset = 0;
258 wma_make_chunk_table(map + ahi.header_len, numbytes - ahi.header_len,
259 ahi.block_align, afhi);
260 read_asf_tags(map, ahi.header_len, &afhi->tags);
261 return 0;
262 }
263
264 static const char* wma_suffixes[] = {"wma", NULL};
265
266 /**
267 * The init function of the wma audio format handler.
268 *
269 * \param afh Pointer to the struct to initialize.
270 */
271 void wma_afh_init(struct audio_format_handler *afh)
272 {
273 afh->get_file_info = wma_get_file_info;
274 afh->suffixes = wma_suffixes;
275 }