]> git.tuebingen.mpg.de Git - paraslash.git/blob - wma_afh.c
wma: Fix chunk_table computation.
[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         int count = 0, num_frames;
195
196         afhi->chunk_table = para_malloc(ct_size * sizeof(uint32_t));
197         afhi->chunk_table[0] = 0;
198         afhi->chunk_table[1] = afhi->header_len;
199
200         num_frames = count_frames(buf, buf_size, block_align,
201                 &afhi->chunks_total);
202         PARA_INFO_LOG("%d frames\n", num_frames);
203         afhi->seconds_total = num_frames * 2048 /* FIXME */
204                 / afhi->frequency;
205         frames_per_chunk = num_frames / afhi->chunks_total;
206         i = 0;
207         j = 1;
208         start += afhi->header_len;
209         buf_size -= afhi->header_len;
210         FOR_EACH_FRAME(f, start, buf_size, block_align) {
211                 count += f[WMA_FRAME_SKIP] & 0x0f;
212                 while (count > j * frames_per_chunk && f > start) {
213                         j++;
214                         if (j >= ct_size) {
215                                 ct_size *= 2;
216                                 afhi->chunk_table = para_realloc(
217                                         afhi->chunk_table,
218                                         ct_size * sizeof(uint32_t));
219                         }
220                         PARA_DEBUG_LOG("ct[%d]: %zu\n", j, f - start);
221                         afhi->chunk_table[j] = f - start + afhi->header_len;
222                 }
223         }
224         afhi->chunks_total = j;
225         chunk_time = num_frames * 1000 / afhi->frequency * 2048
226                 / afhi->chunks_total;
227         PARA_INFO_LOG("ct: %d\n", chunk_time);
228         afhi->chunk_tv.tv_sec = chunk_time / 1000;
229         afhi->chunk_tv.tv_usec = (chunk_time % 1000) * 1000;
230         //set_chunk_tv(num_frames, j, afhi->frequency, &afhi->chunk_tv);
231         return 1;
232 }
233
234 static int wma_get_file_info(char *map, size_t numbytes, __a_unused int fd,
235         struct afh_info *afhi)
236 {
237         int ret;
238         struct asf_header_info ahi;
239
240         ret = read_asf_header(map, numbytes, &ahi);
241         if (ret < 0)
242                 return ret;
243         afhi->bitrate = ahi.bit_rate / 1000;
244         afhi->frequency = ahi.sample_rate;
245         afhi->channels = ahi.channels;
246         afhi->header_len = ahi.header_len;
247         afhi->header_offset = 0;
248         read_asf_tags(map, ahi.header_len, &afhi->tags);
249         wma_make_chunk_table(map, numbytes, 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 }