61
|
1 /*
|
|
2 * MP4/AAC decoder for xmms
|
|
3 *
|
|
4 * This decoding source code is completly independent of the faad2
|
|
5 * package.
|
|
6 * This package exist for people who don't want to install
|
|
7 * faad2 and mpeg4ip project files.
|
|
8 *
|
|
9 * OPTIONNAL need
|
|
10 * --------------
|
|
11 * libid3 (3.8.x - www.id3.org)
|
|
12 */
|
|
13
|
|
14 #include <pthread.h>
|
|
15 #include <gtk/gtk.h>
|
|
16 #include "faad.h"
|
|
17 #include "mp4.h"
|
|
18
|
|
19 #include <audacious/plugin.h>
|
|
20 #include <libaudacious/util.h>
|
|
21 #include <libaudacious/configfile.h>
|
|
22 #include <libaudacious/titlestring.h>
|
|
23
|
|
24 #define MP4_DESCRIPTION "MP4 & MPEG2/4-AAC audio player - 1.2.x"
|
|
25 #define MP4_VERSION "ver. 0.4 - 24 November 2003"
|
|
26 #define LIBMP4V2_VERSION "0.9.7"
|
|
27 #define MP4_ABOUT "Written by ciberfred"
|
|
28 #define BUFFER_SIZE FAAD_MIN_STREAMSIZE*64
|
|
29
|
|
30 static void mp4_init(void);
|
|
31 static void mp4_about(void);
|
|
32 static void mp4_play(char *);
|
|
33 static void mp4_stop(void);
|
|
34 static void mp4_pause(short);
|
|
35 static void mp4_seek(int);
|
|
36 static int mp4_getTime(void);
|
|
37 static void mp4_cleanup(void);
|
|
38 static void mp4_getSongInfo(char *);
|
|
39 static int mp4_isFile(char *);
|
|
40 static void* mp4Decode(void *);
|
|
41
|
|
42 InputPlugin mp4_ip =
|
|
43 {
|
|
44 0, // handle
|
|
45 0, // filename
|
|
46 MP4_DESCRIPTION,
|
|
47 mp4_init,
|
|
48 mp4_about,
|
|
49 0, // configuration
|
|
50 mp4_isFile,
|
|
51 0, //scandir
|
|
52 mp4_play,
|
|
53 mp4_stop,
|
|
54 mp4_pause,
|
|
55 mp4_seek,
|
|
56 0, // set equalizer
|
|
57 mp4_getTime,
|
|
58 0, // get volume
|
|
59 0,
|
|
60 mp4_cleanup,
|
|
61 0, // obsolete
|
|
62 0, // send visualisation data
|
|
63 0, // set player window info
|
|
64 0, // set song title text
|
|
65 0, // get song title text
|
|
66 mp4_getSongInfo, // info box
|
|
67 0, // to output plugin
|
|
68 };
|
|
69
|
|
70 typedef struct _mp4cfg{
|
|
71 gshort file_type;
|
|
72 #define FILE_UNKNOW 0
|
|
73 #define FILE_MP4 1
|
|
74 #define FILE_AAC 2
|
|
75 } Mp4Config;
|
|
76
|
|
77 static Mp4Config mp4cfg;
|
|
78 static gboolean bPlaying = FALSE;
|
|
79 static pthread_t decodeThread;
|
|
80 static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
|
|
81 static int seekPosition = -1;
|
|
82
|
|
83
|
|
84 InputPlugin *get_iplugin_info(void)
|
|
85 {
|
|
86 return(&mp4_ip);
|
|
87 }
|
|
88
|
|
89 static void mp4_init(void)
|
|
90 {
|
|
91 memset(&decodeThread, 0, sizeof(pthread_t));
|
|
92 mp4cfg.file_type = FILE_UNKNOW;
|
|
93 seekPosition = -1;
|
|
94 return;
|
|
95 }
|
|
96
|
|
97 static void mp4_play(char *filename)
|
|
98 {
|
|
99 bPlaying = TRUE;
|
|
100 pthread_create(&decodeThread, 0, mp4Decode, g_strdup(filename));
|
|
101 return;
|
|
102 }
|
|
103
|
|
104 static void mp4_stop(void)
|
|
105 {
|
|
106 if(bPlaying){
|
|
107 bPlaying = FALSE;
|
|
108 pthread_join(decodeThread, NULL);
|
|
109 memset(&decodeThread, 0, sizeof(pthread_t));
|
|
110 mp4_ip.output->close_audio();
|
|
111 }
|
|
112 }
|
|
113
|
|
114 static int mp4_isFile(char *filename)
|
|
115 {
|
|
116 char *ext;
|
|
117
|
|
118 if(filename){
|
|
119 ext = strrchr(filename, '.');
|
|
120 if (ext && (!strncasecmp(ext, ".mp4", 4) || // official extention
|
|
121 !strncasecmp(ext, ".m4a", 4) || // Apple mp4 extention
|
|
122 !strncasecmp(ext, ".aac", 4) // old MPEG2/4-AAC extention
|
|
123 )){
|
|
124 return (1);
|
|
125 }
|
|
126 }
|
|
127 return(0);
|
|
128 }
|
|
129
|
|
130 static void mp4_about(void)
|
|
131 {
|
|
132 static GtkWidget *aboutbox;
|
|
133
|
|
134 if(aboutbox!=NULL)
|
|
135 return;
|
|
136 aboutbox = xmms_show_message("About MP4 AAC player plugin",
|
|
137 "libfaad2-" FAAD2_VERSION "\n"
|
|
138 "libmp4v2-" LIBMP4V2_VERSION "\n"
|
|
139 "plugin version: " MP4_VERSION "\n"
|
|
140 MP4_ABOUT,
|
|
141 "Ok", FALSE, NULL, NULL);
|
|
142 gtk_signal_connect(GTK_OBJECT(aboutbox), "destroy",
|
|
143 GTK_SIGNAL_FUNC(gtk_widget_destroyed),
|
|
144 &aboutbox);
|
|
145 }
|
|
146
|
|
147 static void mp4_pause(short flag)
|
|
148 {
|
|
149 mp4_ip.output->pause(flag);
|
|
150 }
|
|
151
|
|
152 static void mp4_seek(int time)
|
|
153 {
|
|
154 seekPosition = time;
|
|
155 while(bPlaying && seekPosition!=-1)
|
|
156 xmms_usleep(10000);
|
|
157 }
|
|
158
|
|
159 static int mp4_getTime(void)
|
|
160 {
|
|
161 if(!bPlaying)
|
|
162 return (-1);
|
|
163 else
|
|
164 return (mp4_ip.output->output_time());
|
|
165 }
|
|
166
|
|
167 static void mp4_cleanup(void)
|
|
168 {
|
|
169 }
|
|
170
|
|
171 static void mp4_getSongInfo(char *filename)
|
|
172 {
|
|
173 if(mp4cfg.file_type == FILE_MP4)
|
|
174 getMP4info(filename);
|
|
175 else if(mp4cfg.file_type == FILE_AAC)
|
|
176 ;
|
|
177 }
|
|
178
|
|
179 static void *mp4Decode(void *args)
|
|
180 {
|
|
181 MP4FileHandle mp4file;
|
|
182
|
|
183 pthread_mutex_lock(&mutex);
|
|
184 seekPosition = -1;
|
|
185 bPlaying = TRUE;
|
|
186 if(!(mp4file = MP4Read(args, 0))){
|
|
187 mp4cfg.file_type = FILE_AAC;
|
|
188 MP4Close(mp4file);
|
|
189 }else{
|
|
190 mp4cfg.file_type = FILE_MP4;
|
|
191 }
|
|
192
|
|
193 if(mp4cfg.file_type == FILE_MP4){
|
|
194 // We are reading a MP4 file
|
|
195 gint mp4track;
|
|
196
|
|
197 if((mp4track = getAACTrack(mp4file)) < 0){
|
|
198 //TODO: check here for others Audio format.....
|
|
199 g_print("Unsupported Audio track type\n");
|
|
200 g_free(args);
|
|
201 MP4Close(mp4file);
|
|
202 bPlaying = FALSE;
|
|
203 pthread_mutex_unlock(&mutex);
|
|
204 pthread_exit(NULL);
|
|
205 }else{
|
|
206 faacDecHandle decoder;
|
|
207 unsigned char *buffer = NULL;
|
|
208 guint bufferSize = 0;
|
|
209 gulong samplerate;
|
|
210 guchar channels;
|
|
211 guint avgBitrate;
|
|
212 MP4Duration duration;
|
|
213 gulong msDuration;
|
|
214 MP4SampleId numSamples;
|
|
215 MP4SampleId sampleID = 1;
|
|
216
|
|
217 decoder = faacDecOpen();
|
|
218 MP4GetTrackESConfiguration(mp4file, mp4track, &buffer, &bufferSize);
|
|
219 if(!buffer){
|
|
220 g_free(args);
|
|
221 faacDecClose(decoder);
|
|
222 MP4Close(mp4file);
|
|
223 bPlaying = FALSE;
|
|
224 pthread_mutex_unlock(&mutex);
|
|
225 pthread_exit(NULL);
|
|
226 }
|
|
227 if(faacDecInit2(decoder, buffer, bufferSize, &samplerate, &channels)<0){
|
|
228 g_free(args);
|
|
229 faacDecClose(decoder);
|
|
230 MP4Close(mp4file);
|
|
231 bPlaying = FALSE;
|
|
232 pthread_mutex_unlock(&mutex);
|
|
233 pthread_exit(NULL);
|
|
234 }
|
|
235 g_free(buffer);
|
|
236 if(channels == 0){
|
|
237 g_print("Number of Channels not supported\n");
|
|
238 g_free(args);
|
|
239 faacDecClose(decoder);
|
|
240 MP4Close(mp4file);
|
|
241 bPlaying = FALSE;
|
|
242 pthread_mutex_unlock(&mutex);
|
|
243 pthread_exit(NULL);
|
|
244 }
|
|
245 duration = MP4GetTrackDuration(mp4file, mp4track);
|
|
246 msDuration = MP4ConvertFromTrackDuration(mp4file, mp4track, duration,
|
|
247 MP4_MSECS_TIME_SCALE);
|
|
248 numSamples = MP4GetTrackNumberOfSamples(mp4file, mp4track);
|
|
249 mp4_ip.output->open_audio(FMT_S16_NE, samplerate, channels);
|
|
250 mp4_ip.output->flush(0);
|
|
251 mp4_ip.set_info(args, msDuration, -1, samplerate/1000, channels);
|
|
252 g_print("MP4 - %d channels @ %d Hz\n", channels, samplerate);
|
|
253
|
|
254 while(bPlaying){
|
|
255 void* sampleBuffer;
|
|
256 faacDecFrameInfo frameInfo;
|
|
257 gint rc;
|
|
258
|
|
259 if(seekPosition!=-1){
|
|
260 duration = MP4ConvertToTrackDuration(mp4file,
|
|
261 mp4track,
|
|
262 seekPosition*1000,
|
|
263 MP4_MSECS_TIME_SCALE);
|
|
264 sampleID = MP4GetSampleIdFromTime(mp4file, mp4track, duration, 0);
|
|
265 mp4_ip.output->flush(seekPosition*1000);
|
|
266 seekPosition = -1;
|
|
267 }
|
|
268 buffer=NULL;
|
|
269 bufferSize=0;
|
|
270 if(sampleID > numSamples){
|
|
271 mp4_ip.output->close_audio();
|
|
272 g_free(args);
|
|
273 faacDecClose(decoder);
|
|
274 MP4Close(mp4file);
|
|
275 bPlaying = FALSE;
|
|
276 pthread_mutex_unlock(&mutex);
|
|
277 pthread_exit(NULL);
|
|
278 }
|
|
279 rc = MP4ReadSample(mp4file, mp4track, sampleID++, &buffer, &bufferSize,
|
|
280 NULL, NULL, NULL, NULL);
|
|
281 //g_print("%d/%d\n", sampleID-1, numSamples);
|
|
282 if((rc==0) || (buffer== NULL)){
|
|
283 g_print("MP4: read error\n");
|
|
284 sampleBuffer = NULL;
|
|
285 sampleID=0;
|
|
286 mp4_ip.output->buffer_free();
|
|
287 mp4_ip.output->close_audio();
|
|
288 g_free(args);
|
|
289 faacDecClose(decoder);
|
|
290 MP4Close(mp4file);
|
|
291 bPlaying = FALSE;
|
|
292 pthread_mutex_unlock(&mutex);
|
|
293 pthread_exit(NULL);
|
|
294 }else{
|
|
295 sampleBuffer = faacDecDecode(decoder, &frameInfo, buffer, bufferSize);
|
|
296 if(frameInfo.error > 0){
|
|
297 g_print("MP4: %s\n",
|
|
298 faacDecGetErrorMessage(frameInfo.error));
|
|
299 mp4_ip.output->close_audio();
|
|
300 g_free(args);
|
|
301 faacDecClose(decoder);
|
|
302 MP4Close(mp4file);
|
|
303 bPlaying = FALSE;
|
|
304 pthread_mutex_unlock(&mutex);
|
|
305 pthread_exit(NULL);
|
|
306 }
|
|
307 if(buffer){
|
|
308 g_free(buffer); buffer=NULL; bufferSize=0;
|
|
309 }
|
|
310 while(bPlaying && mp4_ip.output->buffer_free()<frameInfo.samples<<1)
|
|
311 xmms_usleep(30000);
|
|
312 }
|
|
313 mp4_ip.add_vis_pcm(mp4_ip.output->written_time(),
|
|
314 FMT_S16_NE,
|
|
315 channels,
|
|
316 frameInfo.samples<<1,
|
|
317 sampleBuffer);
|
|
318 mp4_ip.output->write_audio(sampleBuffer, frameInfo.samples<<1);
|
|
319 }
|
|
320 while(bPlaying && mp4_ip.output->buffer_free()){
|
|
321 xmms_usleep(10000);
|
|
322 }
|
|
323 mp4_ip.output->close_audio();
|
|
324 g_free(args);
|
|
325 faacDecClose(decoder);
|
|
326 MP4Close(mp4file);
|
|
327 bPlaying = FALSE;
|
|
328 pthread_mutex_unlock(&mutex);
|
|
329 pthread_exit(NULL);
|
|
330 }
|
|
331 } else{
|
|
332 // WE ARE READING AN AAC FILE
|
|
333 FILE *file = NULL;
|
|
334 faacDecHandle decoder = 0;
|
|
335 guchar *buffer = 0;
|
|
336 gulong bufferconsumed = 0;
|
|
337 gulong samplerate = 0;
|
|
338 guchar channels;
|
|
339 gulong buffervalid = 0;
|
|
340 TitleInput* input;
|
|
341 gchar *temp = g_strdup(args);
|
|
342 gchar *ext = strrchr(temp, '.');
|
|
343 gchar *xmmstitle = NULL;
|
|
344 faacDecConfigurationPtr config;
|
|
345
|
|
346 if((file = fopen(args, "rb")) == 0){
|
|
347 g_print("AAC: can't find file %s\n", args);
|
|
348 bPlaying = FALSE;
|
|
349 pthread_mutex_unlock(&mutex);
|
|
350 pthread_exit(NULL);
|
|
351 }
|
|
352 if((decoder = faacDecOpen()) == NULL){
|
|
353 g_print("AAC: Open Decoder Error\n");
|
|
354 fclose(file);
|
|
355 bPlaying = FALSE;
|
|
356 pthread_mutex_unlock(&mutex);
|
|
357 pthread_exit(NULL);
|
|
358 }
|
|
359 config = faacDecGetCurrentConfiguration(decoder);
|
|
360 config->useOldADTSFormat = 0;
|
|
361 faacDecSetConfiguration(decoder, config);
|
|
362 if((buffer = g_malloc(BUFFER_SIZE)) == NULL){
|
|
363 g_print("AAC: error g_malloc\n");
|
|
364 fclose(file);
|
|
365 bPlaying = FALSE;
|
|
366 faacDecClose(decoder);
|
|
367 pthread_mutex_unlock(&mutex);
|
|
368 pthread_exit(NULL);
|
|
369 }
|
|
370 if((buffervalid = fread(buffer, 1, BUFFER_SIZE, file))==0){
|
|
371 g_print("AAC: Error reading file\n");
|
|
372 g_free(buffer);
|
|
373 fclose(file);
|
|
374 bPlaying = FALSE;
|
|
375 faacDecClose(decoder);
|
|
376 pthread_mutex_unlock(&mutex);
|
|
377 pthread_exit(NULL);
|
|
378 }
|
|
379 XMMS_NEW_TITLEINPUT(input);
|
|
380 input->file_name = g_basename(temp);
|
|
381 input->file_ext = ext ? ext+1 : NULL;
|
|
382 input->file_path = temp;
|
|
383 if(!strncmp(buffer, "ID3", 3)){
|
|
384 gint size = 0;
|
|
385
|
|
386 fseek(file, 0, SEEK_SET);
|
|
387 size = (buffer[6]<<21) | (buffer[7]<<14) | (buffer[8]<<7) | buffer[9];
|
|
388 size+=10;
|
|
389 fread(buffer, 1, size, file);
|
|
390 buffervalid = fread(buffer, 1, BUFFER_SIZE, file);
|
|
391 }
|
|
392 xmmstitle = xmms_get_titlestring(xmms_get_gentitle_format(), input);
|
|
393 if(xmmstitle == NULL)
|
|
394 xmmstitle = g_strdup(input->file_name);
|
|
395 if(temp) g_free(temp);
|
|
396 if(input->performer) g_free(input->performer);
|
|
397 if(input->album_name) g_free(input->album_name);
|
|
398 if(input->track_name) g_free(input->track_name);
|
|
399 if(input->genre) g_free(input->genre);
|
|
400 g_free(input);
|
|
401 bufferconsumed = faacDecInit(decoder,
|
|
402 buffer,
|
|
403 buffervalid,
|
|
404 &samplerate,
|
|
405 &channels);
|
|
406 if(mp4_ip.output->open_audio(FMT_S16_NE,samplerate,channels) == FALSE){
|
|
407 g_print("AAC: Output Error\n");
|
|
408 g_free(buffer); buffer=0;
|
|
409 faacDecClose(decoder);
|
|
410 fclose(file);
|
|
411 mp4_ip.output->close_audio();
|
|
412 /*
|
|
413 if(positionTable){
|
|
414 g_free(positionTable); positionTable=0;
|
|
415 }
|
|
416 */
|
|
417 g_free(xmmstitle);
|
|
418 bPlaying = FALSE;
|
|
419 pthread_mutex_unlock(&mutex);
|
|
420 pthread_exit(NULL);
|
|
421 }
|
|
422 //if(bSeek){
|
|
423 //mp4_ip.set_info(xmmstitle, lenght*1000, -1, samplerate, channels);
|
|
424 //}else{
|
|
425 mp4_ip.set_info(xmmstitle, -1, -1, samplerate, channels);
|
|
426 //}
|
|
427 mp4_ip.output->flush(0);
|
|
428
|
|
429 while(bPlaying && buffervalid > 0){
|
|
430 faacDecFrameInfo finfo;
|
|
431 unsigned long samplesdecoded;
|
|
432 char* sample_buffer = NULL;
|
|
433 /*
|
|
434 if(bSeek && seekPosition!=-1){
|
|
435 fseek(file, positionTable[seekPosition], SEEK_SET);
|
|
436 bufferconsumed=0;
|
|
437 buffervalid = fread(buffer, 1, BUFFER_SIZE, file);
|
|
438 aac_ip.output->flush(seekPosition*1000);
|
|
439 seekPosition=-1;
|
|
440 }
|
|
441 */
|
|
442 if(bufferconsumed > 0){
|
|
443 memmove(buffer, &buffer[bufferconsumed], buffervalid-bufferconsumed);
|
|
444 buffervalid -= bufferconsumed;
|
|
445 buffervalid += fread(&buffer[buffervalid], 1,
|
|
446 BUFFER_SIZE-buffervalid, file);
|
|
447 bufferconsumed = 0;
|
|
448 }
|
|
449 sample_buffer = faacDecDecode(decoder, &finfo, buffer, buffervalid);
|
|
450 if(finfo.error){
|
|
451 config = faacDecGetCurrentConfiguration(decoder);
|
|
452 if(config->useOldADTSFormat != 1){
|
|
453 faacDecClose(decoder);
|
|
454 decoder = faacDecOpen();
|
|
455 config = faacDecGetCurrentConfiguration(decoder);
|
|
456 config->useOldADTSFormat = 1;
|
|
457 faacDecSetConfiguration(decoder, config);
|
|
458 finfo.bytesconsumed=0;
|
|
459 finfo.samples = 0;
|
|
460 faacDecInit(decoder,
|
|
461 buffer,
|
|
462 buffervalid,
|
|
463 &samplerate,
|
|
464 &channels);
|
|
465 }else{
|
|
466 g_print("FAAD2 Warning %s\n", faacDecGetErrorMessage(finfo.error));
|
|
467 buffervalid = 0;
|
|
468 }
|
|
469 }
|
|
470 bufferconsumed += finfo.bytesconsumed;
|
|
471 samplesdecoded = finfo.samples;
|
|
472 if((samplesdecoded<=0) && !sample_buffer){
|
|
473 g_print("AAC: error sample decoding\n");
|
|
474 continue;
|
|
475 }
|
|
476 while(bPlaying && mp4_ip.output->buffer_free() < (samplesdecoded<<1)){
|
|
477 xmms_usleep(10000);
|
|
478 }
|
|
479 mp4_ip.add_vis_pcm(mp4_ip.output->written_time(),
|
|
480 FMT_S16_LE, channels,
|
|
481 samplesdecoded<<1, sample_buffer);
|
|
482 mp4_ip.output->write_audio(sample_buffer, samplesdecoded<<1);
|
|
483 }
|
|
484 while(bPlaying && mp4_ip.output->buffer_playing()){
|
|
485 xmms_usleep(10000);
|
|
486 }
|
|
487 mp4_ip.output->buffer_free();
|
|
488 mp4_ip.output->close_audio();
|
|
489 bPlaying = FALSE;
|
|
490 g_free(buffer);
|
|
491 faacDecClose(decoder);
|
|
492 g_free(xmmstitle);
|
|
493 fclose(file);
|
|
494 seekPosition = -1;
|
|
495 /*
|
|
496 if(positionTable){
|
|
497 g_free(positionTable); positionTable=0;
|
|
498 }
|
|
499 */
|
|
500 bPlaying = FALSE;
|
|
501 pthread_mutex_unlock(&mutex);
|
|
502 pthread_exit(NULL);
|
|
503
|
|
504 }
|
|
505 }
|