1 /*
2 * Copyright (c) 2015 Tim van der Molen <tim@kariliq.nl>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17 #include <stdarg.h>
18 #include <stdint.h>
19 #include <stdlib.h>
20 #include <string.h>
21
22 #include <mp4v2/mp4v2.h>
23 #include <neaacdec.h>
24
25 #include "../siren.h"
26
27 #if MP4V2_PROJECT_version_hex < 0x00020000
28 #define IP_AAC_OLD_MP4V2_API
29 #endif
30
31 #ifdef IP_AAC_OLD_MP4V2_API
32 #define MP4Close(hdl, flags) MP4Close(hdl)
33 #define MP4Read(path) MP4Read(path, MP4_DETAILS_ERROR)
34 #define MP4SetLogCallback(func) MP4SetLibFunc(func)
35 #define MP4TagsFetch(tag, hdl) (MP4TagsFetch(tag, hdl), 1)
36 #endif
37
38 struct ip_aac_ipdata {
39 MP4FileHandle hdl;
40 MP4TrackId track;
41 MP4SampleId nsamples;
42 MP4SampleId sample;
43 MP4Duration pos;
44 NeAACDecHandle dec;
45 uint32_t aacbufsize;
46 uint8_t *aacbuf;
47 unsigned long pcmbuflen;
48 char *pcmbuf;
49 };
50
51 static void ip_aac_close(struct track *);
52 static void ip_aac_get_metadata(struct track *);
53 static int ip_aac_get_position(struct track *, unsigned int *);
54 static int ip_aac_init(void);
55 static int ip_aac_open(struct track *);
56 static int ip_aac_read(struct track *, struct sample_buffer *);
57 static void ip_aac_seek(struct track *, unsigned int);
58
59 static const char *ip_aac_extensions[] = { "aac", "m4a", "m4b", "mp4",
60 NULL };
61
62 const struct ip ip = {
63 "aac",
64 IP_PRIORITY_AAC,
65 ip_aac_extensions,
66 ip_aac_close,
67 ip_aac_get_metadata,
68 ip_aac_get_position,
69 ip_aac_init,
70 ip_aac_open,
71 ip_aac_read,
72 ip_aac_seek
73 };
74
75 #ifdef IP_AAC_OLD_MP4V2_API
76 PRINTFLIKE(3, 0) static void
ip_aac_log(UNUSED int loglevel,UNUSED const char * lib,const char * fmt,...)77 ip_aac_log(UNUSED int loglevel, UNUSED const char *lib, const char *fmt, ...)
78 {
79 va_list ap;
80
81 va_start(ap, fmt);
82 LOG_VERRX(fmt, ap);
83 va_end(ap);
84 }
85 #else
86 PRINTFLIKE(2, 0) static void
ip_aac_log(UNUSED MP4LogLevel loglevel,const char * fmt,va_list ap)87 ip_aac_log(UNUSED MP4LogLevel loglevel, const char *fmt, va_list ap)
88 {
89 LOG_VERRX(fmt, ap);
90 }
91 #endif
92
93 static MP4TrackId
ip_aac_get_aac_track(MP4FileHandle hdl)94 ip_aac_get_aac_track(MP4FileHandle hdl)
95 {
96 uint32_t i, ntracks;
97 MP4TrackId trk;
98 uint8_t obj;
99
100 /* Look for an audio track with AAC audio. */
101 ntracks = MP4GetNumberOfTracks(hdl, MP4_AUDIO_TRACK_TYPE, 0);
102 for (i = 0; i < ntracks; i++) {
103 trk = MP4FindTrackId(hdl, i, MP4_AUDIO_TRACK_TYPE, 0);
104 obj = MP4GetTrackEsdsObjectTypeId(hdl, trk);
105 if (MP4_IS_AAC_AUDIO_TYPE(obj))
106 return trk;
107 }
108
109 return MP4_INVALID_TRACK_ID;
110 }
111
112 static int
ip_aac_open_file(const char * path,MP4FileHandle * hdl,MP4TrackId * trk)113 ip_aac_open_file(const char *path, MP4FileHandle *hdl, MP4TrackId *trk)
114 {
115 *hdl = MP4Read(path);
116 if (*hdl == MP4_INVALID_FILE_HANDLE) {
117 LOG_ERRX("%s: MP4Read() failed", path);
118 msg_errx("%s: Cannot open file", path);
119 return -1;
120 }
121
122 *trk = ip_aac_get_aac_track(*hdl);
123 if (*trk == MP4_INVALID_TRACK_ID) {
124 LOG_ERRX("%s: cannot find AAC track", path);
125 msg_errx("%s: Cannot find AAC track", path);
126 MP4Close(*hdl, 0);
127 return -1;
128 }
129
130 return 0;
131 }
132
133 static int
ip_aac_fill_buffer(struct track * t,struct ip_aac_ipdata * ipd)134 ip_aac_fill_buffer(struct track *t, struct ip_aac_ipdata *ipd)
135 {
136 NeAACDecFrameInfo frame;
137 uint32_t buflen;
138 char *errmsg;
139
140 for (;;) {
141 if (ipd->sample > ipd->nsamples)
142 return 0; /* EOF reached */
143
144 buflen = ipd->aacbufsize;
145 if (!MP4ReadSample(ipd->hdl, ipd->track, ipd->sample,
146 &ipd->aacbuf, &buflen, NULL, NULL, NULL, NULL)) {
147 LOG_ERRX("%s: MP4ReadSample() failed", t->path);
148 msg_errx("Cannot read from file");
149 return -1;
150 }
151
152 ipd->pos += MP4GetSampleDuration(ipd->hdl, ipd->track,
153 ipd->sample);
154 ipd->sample++;
155
156 ipd->pcmbuf = NeAACDecDecode(ipd->dec, &frame, ipd->aacbuf,
157 buflen);
158 if (frame.error) {
159 errmsg = NeAACDecGetErrorMessage(frame.error);
160 LOG_ERRX("NeAACDecDecode: %s: %s", t->path, errmsg);
161 msg_errx("Cannot read from file: %s", errmsg);
162 return -1;
163 }
164 if (frame.samples > 0) {
165 /* 16-bit samples */
166 ipd->pcmbuflen = frame.samples * 2;
167 return 1;
168 }
169 }
170 }
171
172 static void
ip_aac_close(struct track * t)173 ip_aac_close(struct track *t)
174 {
175 struct ip_aac_ipdata *ipd;
176
177 ipd = t->ipdata;
178 NeAACDecClose(ipd->dec);
179 MP4Close(ipd->hdl, 0);
180 free(ipd->aacbuf);
181 free(ipd);
182 }
183
184 static void
ip_aac_get_metadata(struct track * t)185 ip_aac_get_metadata(struct track *t)
186 {
187 MP4FileHandle hdl;
188 MP4TrackId trk;
189 const MP4Tags *tag;
190
191 if (ip_aac_open_file(t->path, &hdl, &trk) == -1)
192 return;
193
194 tag = MP4TagsAlloc();
195 if (tag == NULL) {
196 LOG_ERRX("%s: MP4TagsAlloc() failed", t->path);
197 msg_errx("%s: Cannot get metadata", t->path);
198 MP4Close(hdl, 0);
199 return;
200 }
201
202 if (!MP4TagsFetch(tag, hdl)) {
203 LOG_ERRX("%s: MP4TagsFetch failed", t->path);
204 msg_errx("%s: Cannot get metadata", t->path);
205 MP4TagsFree(tag);
206 MP4Close(hdl, 0);
207 return;
208 }
209
210 if (tag->album != NULL)
211 t->album = xstrdup(tag->album);
212 if (tag->albumArtist != NULL)
213 t->albumartist = xstrdup(tag->albumArtist);
214 if (tag->artist != NULL)
215 t->artist = xstrdup(tag->artist);
216 if (tag->comments != NULL)
217 t->comment = xstrdup(tag->comments);
218 if (tag->releaseDate != NULL)
219 t->date = xstrdup(tag->releaseDate);
220 if (tag->genre != NULL)
221 t->genre = xstrdup(tag->genre);
222 if (tag->name != NULL)
223 t->title = xstrdup(tag->name);
224 if (tag->disk != NULL) {
225 xasprintf(&t->discnumber, "%u", tag->disk->index);
226 xasprintf(&t->disctotal, "%u", tag->disk->total);
227 }
228 if (tag->track != NULL) {
229 xasprintf(&t->tracknumber, "%u", tag->track->index);
230 xasprintf(&t->tracktotal, "%u", tag->track->total);
231 }
232
233 t->duration = MP4ConvertFromTrackDuration(hdl, trk,
234 MP4GetTrackDuration(hdl, trk), MP4_SECS_TIME_SCALE);
235
236 MP4TagsFree(tag);
237 MP4Close(hdl, 0);
238 }
239
240 static int
ip_aac_get_position(struct track * t,unsigned int * pos)241 ip_aac_get_position(struct track *t, unsigned int *pos)
242 {
243 struct ip_aac_ipdata *ipd;
244
245 ipd = t->ipdata;
246 *pos = MP4ConvertFromTrackDuration(ipd->hdl, ipd->track, ipd->pos,
247 MP4_SECS_TIME_SCALE);
248 return 0;
249 }
250
251 static int
ip_aac_init(void)252 ip_aac_init(void)
253 {
254 MP4SetLogCallback(ip_aac_log);
255 return 0;
256 }
257
258 static int
ip_aac_open(struct track * t)259 ip_aac_open(struct track *t)
260 {
261 struct ip_aac_ipdata *ipd;
262 NeAACDecConfigurationPtr cfg;
263 uint8_t *esc;
264 uint32_t escsize;
265 unsigned long rate;
266 unsigned char nchan;
267
268 ipd = xmalloc(sizeof *ipd);
269
270 if (ip_aac_open_file(t->path, &ipd->hdl, &ipd->track) == -1)
271 goto error1;
272
273 ipd->aacbufsize = MP4GetTrackMaxSampleSize(ipd->hdl, ipd->track);
274 if (ipd->aacbufsize == 0) {
275 /* Avoid zero-size allocation. */
276 LOG_ERRX("%s: MP4GetTrackMaxSampleSize() returned 0", t->path);
277 goto error2;
278 }
279
280 ipd->dec = NeAACDecOpen();
281 if (ipd->dec == NULL) {
282 LOG_ERRX("%s: NeAACDecOpen() failed", t->path);
283 goto error2;
284 }
285
286 cfg = NeAACDecGetCurrentConfiguration(ipd->dec);
287 cfg->outputFormat = FAAD_FMT_16BIT;
288 cfg->downMatrix = 1; /* Down-matrix 5.1 channels to 2 */
289 if (NeAACDecSetConfiguration(ipd->dec, cfg) != 1) {
290 LOG_ERRX("%s: NeAACDecSetConfiguration() failed", t->path);
291 goto error3;
292 }
293
294 if (!MP4GetTrackESConfiguration(ipd->hdl, ipd->track, &esc,
295 &escsize)) {
296 LOG_ERRX("%s: MP4GetTrackESConfiguration() failed", t->path);
297 goto error3;
298 }
299
300 if (NeAACDecInit2(ipd->dec, esc, escsize, &rate, &nchan) != 0) {
301 LOG_ERRX("%s: NeAACDecInit2() failed", t->path);
302 free(esc);
303 goto error3;
304 }
305 free(esc);
306
307 ipd->nsamples = MP4GetTrackNumberOfSamples(ipd->hdl, ipd->track);
308 ipd->sample = 1;
309 ipd->pos = 0;
310 ipd->aacbuf = xmalloc(ipd->aacbufsize);
311 ipd->pcmbuflen = 0;
312
313 t->format.nbits = 16;
314 t->format.nchannels = nchan;
315 t->format.rate = rate;
316 t->ipdata = ipd;
317
318 return 0;
319
320 error3:
321 NeAACDecClose(ipd->dec);
322 error2:
323 MP4Close(ipd->hdl, 0);
324 error1:
325 free(ipd);
326 msg_errx("%s: Cannot open file", t->path);
327 return -1;
328 }
329
330 static int
ip_aac_read(struct track * t,struct sample_buffer * sb)331 ip_aac_read(struct track *t, struct sample_buffer *sb)
332 {
333 struct ip_aac_ipdata *ipd;
334 char *buf;
335 size_t len, bufsize;
336 int ret;
337
338 ipd = t->ipdata;
339 buf = (char *)sb->data;
340 bufsize = sb->size_b;
341
342 while (bufsize > 0) {
343 if (ipd->pcmbuflen == 0) {
344 ret = ip_aac_fill_buffer(t, ipd);
345 if (ret == 0)
346 break; /* EOF */
347 if (ret == -1)
348 return -1; /* Error */
349 }
350 len = (bufsize < ipd->pcmbuflen) ? bufsize : ipd->pcmbuflen;
351 memcpy(buf, ipd->pcmbuf, len);
352 buf += len;
353 bufsize -= len;
354 ipd->pcmbuf += len;
355 ipd->pcmbuflen -= len;
356 }
357
358 sb->len_b = sb->size_b - bufsize;
359 sb->len_s = sb->len_b / sb->nbytes;
360 return sb->len_s != 0;
361 }
362
363 static void
ip_aac_seek(struct track * t,unsigned int pos)364 ip_aac_seek(struct track *t, unsigned int pos)
365 {
366 struct ip_aac_ipdata *ipd;
367 MP4SampleId sample;
368 MP4Timestamp tim;
369
370 ipd = t->ipdata;
371 tim = MP4ConvertToTrackTimestamp(ipd->hdl, ipd->track, pos,
372 MP4_SECS_TIME_SCALE);
373 sample = MP4GetSampleIdFromTime(ipd->hdl, ipd->track, tim, true);
374 if (sample != MP4_INVALID_SAMPLE_ID) {
375 ipd->sample = sample;
376 ipd->pos = MP4GetSampleTime(ipd->hdl, ipd->track, sample);
377 }
378 }
379