gui: Reorder functions.
[paraslash.git] / wma_afh.c
1 /*
2  * Copyright (C) 2009-2014 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;
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         /* ignore length of the rating information */
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;
162         if (p + len4 >= end)
163                 goto next;
164         ti->comment = get_str16(p, len4);
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 frames_per_chunk, int frequency,
189                 struct timeval *result)
190 {
191         uint64_t x = (uint64_t)frames_per_chunk * 2048 * 1000 * 1000
192                 / frequency;
193
194         result->tv_sec = x / 1000 / 1000;
195         result->tv_usec = x % (1000 * 1000);
196         PARA_INFO_LOG("chunk time: %lums\n", tv2ms(result));
197 }
198
199 /* Must be called on a frame boundary. */
200 static int wma_make_chunk_table(char *buf, size_t buf_size, int block_align,
201                 struct afh_info *afhi)
202 {
203         const uint8_t *f, *start = (uint8_t *)buf;
204         int j, frames_per_chunk;
205         size_t ct_size = 250;
206         int ret, count = 0, num_frames, num_superframes;
207
208         afhi->chunk_table = para_malloc(ct_size * sizeof(uint32_t));
209         afhi->chunk_table[0] = 0;
210         afhi->chunk_table[1] = afhi->header_len;
211
212         num_frames = count_frames(buf, buf_size, block_align,
213                 &num_superframes);
214         ret = -E_NO_WMA;
215         if (num_frames == 0 || num_superframes == 0)
216                 goto fail;
217         afhi->seconds_total = num_frames * 2048 /* FIXME */
218                 / afhi->frequency;
219         frames_per_chunk = num_frames / num_superframes / 2;
220         PARA_INFO_LOG("%d frames per chunk\n", frames_per_chunk);
221         j = 1;
222         FOR_EACH_FRAME(f, start, buf_size, block_align) {
223                 count += f[WMA_FRAME_SKIP] & 0x0f;
224                 while (count > j * frames_per_chunk) {
225                         j++;
226                         if (j >= ct_size) {
227                                 ct_size *= 2;
228                                 afhi->chunk_table = para_realloc(
229                                         afhi->chunk_table,
230                                         ct_size * sizeof(uint32_t));
231                         }
232                         afhi->chunk_table[j] = f - start + afhi->header_len + block_align + WMA_FRAME_SKIP;
233                 }
234         }
235         afhi->chunks_total = j;
236         set_chunk_tv(frames_per_chunk, afhi->frequency, &afhi->chunk_tv);
237         return 1;
238 fail:
239         free(afhi->chunk_table);
240         return ret;
241 }
242
243 static int wma_get_file_info(char *map, size_t numbytes, __a_unused int fd,
244         struct afh_info *afhi)
245 {
246         int ret;
247         struct asf_header_info ahi;
248
249         ret = read_asf_header(map, numbytes, &ahi);
250         if (ret < 0)
251                 return ret;
252         if (ret == 0)
253                 return -E_NO_WMA;
254         afhi->bitrate = ahi.bit_rate / 1000;
255         if (ahi.sample_rate == 0)
256                 return -E_NO_WMA;
257         afhi->frequency = ahi.sample_rate;
258         afhi->channels = ahi.channels;
259         afhi->header_len = ahi.header_len;
260         wma_make_chunk_table(map + ahi.header_len, numbytes - ahi.header_len,
261                 ahi.block_align, afhi);
262         read_asf_tags(map, ahi.header_len, &afhi->tags);
263         return 0;
264 }
265
266 static const char* wma_suffixes[] = {"wma", NULL};
267
268 /**
269  * The init function of the wma audio format handler.
270  *
271  * \param afh Pointer to the struct to initialize.
272  */
273 void wma_afh_init(struct audio_format_handler *afh)
274 {
275         afh->get_file_info = wma_get_file_info;
276         afh->suffixes = wma_suffixes;
277 }