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