Remove unused show_bits().
[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;
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 }