diff src/aac/libmp4.c @ 1882:eed7c270e8dd

Fix MILLIONS of Makefiles.
author Jonathan Schleifer <js@h3c.de>
date Wed, 26 Sep 2007 14:53:37 +0200
parents src/aac/src/libmp4.c@f35f9d6fcb6d
children 2ebeb7816c5e
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/aac/libmp4.c	Wed Sep 26 14:53:37 2007 +0200
@@ -0,0 +1,840 @@
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <string.h>
+#include <stdlib.h>
+#include "faad.h"
+#include "mp4ff.h"
+#include "tagging.h"
+
+#include "audacious/plugin.h"
+#include "audacious/output.h"
+#include "audacious/util.h"
+#include "audacious/vfs.h"
+#include "audacious/i18n.h"
+#include "audacious/strings.h"
+#include "audacious/main.h"
+#include "audacious/tuple.h"
+#include "audacious/tuple_formatter.h"
+
+#define MP4_VERSION VERSION
+
+#define SBR_DEC
+
+extern VFSFile *vfs_buffered_file_new_from_uri(gchar *uri);
+
+/*
+ * BUFFER_SIZE is the highest amount of memory that can be pulled.
+ * We use this for sanity checks, among other things, as mp4ff needs
+ * a labotomy sometimes.
+ */
+#define BUFFER_SIZE FAAD_MIN_STREAMSIZE*64
+
+/*
+ * AAC_MAGIC is the pattern that marks the beginning of an MP4 container.
+ */
+#define AAC_MAGIC     (unsigned char [4]) { 0xFF, 0xF9, 0x5C, 0x80 }
+
+static void        mp4_init(void);
+static void        mp4_about(void);
+static int         mp4_is_our_file(char *);
+static void        mp4_play(InputPlayback *);
+static void        mp4_stop(InputPlayback *);
+static void        mp4_pause(InputPlayback *, short);
+static void        mp4_seek(InputPlayback *, int);
+static void        mp4_cleanup(void);
+static void        mp4_get_song_title_len(char *filename, char **, int *);
+static Tuple*      mp4_get_song_tuple(char *);
+static int         mp4_is_our_fd(char *, VFSFile *);
+
+static gchar *fmts[] = { "m4a", "mp4", "aac", NULL };
+
+static void *   mp4_decode(void *);
+static gchar *  mp4_get_song_title(char *filename);
+gboolean        buffer_playing;
+
+InputPlugin mp4_ip =
+{
+    .description = "MP4 Audio Plugin",
+    .init = mp4_init,
+    .about = mp4_about,
+    .is_our_file = mp4_is_our_file,
+    .play_file = mp4_play,
+    .stop = mp4_stop,
+    .pause = mp4_pause,
+    .seek = mp4_seek,
+    .cleanup = mp4_cleanup,
+    .get_song_info = mp4_get_song_title_len,
+    .get_song_tuple = mp4_get_song_tuple,
+    .is_our_file_from_vfs = mp4_is_our_fd,
+    .vfs_extensions = fmts,
+};
+
+InputPlugin *mp4_iplist[] = { &mp4_ip, NULL };
+
+DECLARE_PLUGIN(mp4, NULL, NULL, mp4_iplist, NULL, NULL, NULL, NULL, NULL);
+
+typedef struct  _mp4cfg
+{
+#define FILE_UNKNOWN    0
+#define FILE_MP4        1
+#define FILE_AAC        2
+    gshort        file_type;
+} Mp4Config;
+
+static Mp4Config mp4cfg;
+static GThread   *decodeThread;
+GStaticMutex     mutex = G_STATIC_MUTEX_INIT;
+static int       seekPosition = -1;
+
+void getMP4info(char*);
+int getAACTrack(mp4ff_t *);
+
+static guint32 mp4_read_callback(void *data, void *buffer, guint32 len)
+{
+    if (data == NULL || buffer == NULL)
+        return -1;
+
+    return vfs_fread(buffer, 1, len, (VFSFile *) data);
+}
+
+static guint32 mp4_seek_callback(void *data, guint64 pos)
+{
+    if (data == NULL)
+        return -1;
+
+    return vfs_fseek((VFSFile *) data, pos, SEEK_SET);
+}
+
+static void mp4_init(void)
+{
+    mp4cfg.file_type = FILE_UNKNOWN;
+    seekPosition = -1;
+    return;
+}
+
+static void mp4_play(InputPlayback *playback)
+{
+    buffer_playing = TRUE;
+    playback->playing = 1; //XXX should acquire lock?
+    decodeThread = g_thread_self();
+    playback->set_pb_ready(playback);
+    mp4_decode(playback);
+}
+
+static void mp4_stop(InputPlayback *playback)
+{
+    if (buffer_playing)
+    {
+        buffer_playing = FALSE;
+        playback->playing = 0; //XXX should acquire lock?
+        g_thread_join(decodeThread);
+        playback->output->close_audio();
+    }
+}
+
+/*
+ * These routines are derived from MPlayer.
+ */
+
+/// \param srate (out) sample rate
+/// \param num (out) number of audio frames in this ADTS frame
+/// \return size of the ADTS frame in bytes
+/// aac_parse_frames needs a buffer at least 8 bytes long
+int aac_parse_frame(guchar *buf, int *srate, int *num)
+{
+        int i = 0, sr, fl = 0, id;
+        static int srates[] = {96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 0, 0, 0};
+
+        if((buf[i] != 0xFF) || ((buf[i+1] & 0xF6) != 0xF0))
+                return 0;
+
+        id = (buf[i+1] >> 3) & 0x01;    //id=1 mpeg2, 0: mpeg4
+        sr = (buf[i+2] >> 2)  & 0x0F;
+        if(sr > 11)
+                return 0;
+        *srate = srates[sr];
+
+        fl = ((buf[i+3] & 0x03) << 11) | (buf[i+4] << 3) | ((buf[i+5] >> 5) & 0x07);
+        *num = (buf[i+6] & 0x02) + 1;
+
+        return fl;
+}
+
+static gboolean parse_aac_stream(VFSFile *stream)
+{
+        int cnt = 0, c, len, srate, num;
+        off_t init, probed;
+	static guchar buf[8];
+
+        init = probed = vfs_ftell(stream);
+        while(probed-init <= 32768 && cnt < 8)
+        {
+                c = 0;
+                while(probed-init <= 32768 && c != 0xFF)
+                {
+                        c = vfs_getc(stream);
+                        if(c < 0)
+                                return FALSE;
+	                probed = vfs_ftell(stream);
+                }
+                buf[0] = 0xFF;
+                if(vfs_fread(&(buf[1]), 1, 7, stream) < 7)
+                        return FALSE;
+
+                len = aac_parse_frame(buf, &srate, &num);
+                if(len > 0)
+                {
+                        cnt++;
+                        vfs_fseek(stream, len - 8, SEEK_CUR);
+                }
+                probed = vfs_ftell(stream);
+        }
+
+        if(cnt < 8)
+                return FALSE;
+
+        return TRUE;
+}
+
+static int aac_probe(unsigned char *buffer, int len)
+{
+  int i = 0, pos = 0;
+#ifdef DEBUG
+  g_print("\nAAC_PROBE: %d bytes\n", len);
+#endif
+  while(i <= len-4) {
+    if(
+       ((buffer[i] == 0xff) && ((buffer[i+1] & 0xf6) == 0xf0)) ||
+       (buffer[i] == 'A' && buffer[i+1] == 'D' && buffer[i+2] == 'I' && buffer[i+3] == 'F')
+    ) {
+      pos = i;
+      break;
+    }
+#ifdef DEBUG
+    g_print("AUDIO PAYLOAD: %x %x %x %x\n",
+	buffer[i], buffer[i+1], buffer[i+2], buffer[i+3]);
+#endif
+    i++;
+  }
+#ifdef DEBUG
+  g_print("\nAAC_PROBE: ret %d\n", pos);
+#endif
+  return pos;
+}
+
+static int mp4_is_our_file(char *filename)
+{
+  VFSFile *file;
+  gchar* extension;
+  gchar magic[8];
+
+  memset(magic, '\0', 8);
+
+  extension = strrchr(filename, '.');
+  if ((file = vfs_fopen(filename, "rb"))) {
+      vfs_fread(magic, 1, 8, file);
+      vfs_rewind(file);
+      if (parse_aac_stream(file) == TRUE) {
+           vfs_fclose(file);
+           return TRUE;
+      }
+      if (!memcmp(magic, "ID3", 3)) {       // ID3 tag bolted to the front, obfuscated magic bytes
+           vfs_fclose(file);
+           if (extension &&(
+          !strcasecmp(extension, ".mp4") || // official extension
+          !strcasecmp(extension, ".m4a") || // Apple mp4 extension
+          !strcasecmp(extension, ".aac")    // old MPEG2/4-AAC extension
+       ))
+          return 1;
+       else
+          return 0;
+      }
+      if (!memcmp(&magic[4], "ftyp", 4)) {
+           vfs_fclose(file);
+           return 1;
+      }
+      vfs_fclose(file);
+  }
+  return 0;
+}
+
+static int mp4_is_our_fd(char *filename, VFSFile* file)
+{
+  gchar* extension;
+  gchar magic[8];
+
+  extension = strrchr(filename, '.');
+  vfs_fread(magic, 1, 8, file);
+  vfs_rewind(file);
+  if (parse_aac_stream(file) == TRUE)
+    return 1;
+  if (!memcmp(&magic[4], "ftyp", 4))
+    return 1;
+  if (!memcmp(magic, "ID3", 3)) {       // ID3 tag bolted to the front, obfuscated magic bytes
+    if (extension &&(
+      !strcasecmp(extension, ".mp4") || // official extension
+      !strcasecmp(extension, ".m4a") || // Apple mp4 extension
+      !strcasecmp(extension, ".aac")    // old MPEG2/4-AAC extension
+    ))
+      return 1;
+    else
+      return 0;
+  }
+  return 0;
+}
+
+static void mp4_about(void)
+{
+    static GtkWidget *aboutbox = NULL;
+    gchar *about_text;
+
+    about_text = g_strjoin ("", _("Using libfaad2-"), FAAD2_VERSION,
+				_(" for decoding.\n"
+				  "FAAD2 AAC/HE-AAC/HE-AACv2/DRM decoder (c) Nero AG, www.nero.com\n"
+				  "Copyright (c) 2005-2006 Audacious team"), NULL);
+
+    aboutbox = audacious_info_dialog(_("About MP4 AAC player plugin"),
+				 about_text,
+				 _("Ok"), FALSE, NULL, NULL);
+
+    g_signal_connect(G_OBJECT(aboutbox), "destroy",
+                     G_CALLBACK(gtk_widget_destroyed), &aboutbox);
+    g_free(about_text);
+}
+
+static void mp4_pause(InputPlayback *playback, short flag)
+{
+    playback->output->pause(flag);
+}
+
+static void mp4_seek(InputPlayback *data, int time)
+{
+    seekPosition = time;
+    while(buffer_playing && seekPosition != -1)
+        g_usleep(10000);
+}
+
+static void mp4_cleanup(void)
+{
+}
+
+static Tuple *mp4_get_song_tuple_base(char *filename, VFSFile *mp4fh)
+{
+    mp4ff_callback_t *mp4cb = g_malloc0(sizeof(mp4ff_callback_t));
+    mp4ff_t *mp4file;
+    Tuple *ti = tuple_new_from_filename(filename);
+
+    /* check if this file is an ADTS stream, if so return a blank tuple */
+    if (parse_aac_stream(mp4fh))
+    {
+        g_free(mp4cb);
+
+        tuple_associate_string(ti, FIELD_TITLE, NULL, vfs_get_metadata(mp4fh, "track-name"));
+        tuple_associate_string(ti, FIELD_ALBUM, NULL, vfs_get_metadata(mp4fh, "stream-name"));
+
+        tuple_associate_string(ti, FIELD_CODEC, NULL, "Advanced Audio Coding (AAC)");
+        tuple_associate_string(ti, FIELD_QUALITY, NULL, "lossy");
+
+        vfs_fclose(mp4fh);
+        return ti;
+    }
+
+    vfs_rewind(mp4fh);
+
+    mp4cb->read = mp4_read_callback;
+    mp4cb->seek = mp4_seek_callback;
+    mp4cb->user_data = mp4fh;
+
+    if (!(mp4file = mp4ff_open_read(mp4cb))) {
+        g_free(mp4cb);
+        vfs_fclose(mp4fh);
+    } else {
+        gint mp4track= getAACTrack(mp4file);
+        gint numSamples = mp4ff_num_samples(mp4file, mp4track);
+        guint framesize = 1024;
+        guint samplerate = 0;
+        guchar channels = 0;
+        gint msDuration;
+        mp4AudioSpecificConfig mp4ASC;
+        gchar *tmpval;
+        guchar *buffer = NULL;
+        guint bufferSize = 0;
+        faacDecHandle decoder;
+
+        if (mp4track == -1)
+            return NULL;
+
+        decoder = faacDecOpen();
+        mp4ff_get_decoder_config(mp4file, mp4track, &buffer, &bufferSize);
+
+        if ( !buffer ) {
+            faacDecClose(decoder);
+            return FALSE;
+        }
+        if ( faacDecInit2(decoder, buffer, bufferSize,
+                  &samplerate, &channels) < 0 ) {
+            faacDecClose(decoder);
+
+            return FALSE;
+        }
+
+        /* Add some hacks for SBR profile */
+        if (AudioSpecificConfig(buffer, bufferSize, &mp4ASC) >= 0) {
+            if (mp4ASC.frameLengthFlag == 1) framesize = 960;
+            if (mp4ASC.sbr_present_flag == 1) framesize *= 2;
+        }
+
+        g_free(buffer);
+
+        faacDecClose(decoder);
+
+        msDuration = ((float)numSamples * (float)(framesize - 1.0)/(float)samplerate) * 1000;
+        tuple_associate_int(ti, FIELD_LENGTH, NULL, msDuration);
+
+        mp4ff_meta_get_title(mp4file, &tmpval);
+        if (tmpval)
+        {
+            tuple_associate_string(ti, FIELD_TITLE, NULL, tmpval);
+            free(tmpval);
+        }
+
+        mp4ff_meta_get_album(mp4file, &tmpval);
+        if (tmpval)
+        {
+            tuple_associate_string(ti, FIELD_ALBUM, NULL, tmpval);
+            free(tmpval);
+        }
+
+        mp4ff_meta_get_artist(mp4file, &tmpval);
+        if (tmpval)
+        {
+            tuple_associate_string(ti, FIELD_ARTIST, NULL, tmpval);
+            free(tmpval);
+        }
+
+        mp4ff_meta_get_genre(mp4file, &tmpval);
+        if (tmpval)
+        {
+            tuple_associate_string(ti, FIELD_GENRE, NULL, tmpval);
+            free(tmpval);
+        }
+
+        mp4ff_meta_get_date(mp4file, &tmpval);
+        if (tmpval)
+        {
+            tuple_associate_int(ti, FIELD_YEAR, NULL, atoi(tmpval));
+            free(tmpval);
+        }
+
+        tuple_associate_string(ti, FIELD_CODEC, NULL, "Advanced Audio Coding (AAC)");
+        tuple_associate_string(ti, FIELD_QUALITY, NULL, "lossy");
+
+        free (mp4cb);
+        vfs_fclose(mp4fh);
+    }
+
+    return ti;
+}
+
+static Tuple *mp4_get_song_tuple(char *filename)
+{
+    Tuple *tuple;
+    VFSFile *mp4fh;
+    gboolean remote = str_has_prefix_nocase(filename, "http:") ||
+	              str_has_prefix_nocase(filename, "https:");
+
+    mp4fh = remote ? vfs_buffered_file_new_from_uri(filename) : vfs_fopen(filename, "rb");
+
+    tuple = mp4_get_song_tuple_base(filename, mp4fh);
+
+    return tuple;
+}
+
+static void mp4_get_song_title_len(char *filename, char **title, int *len)
+{
+    (*title) = mp4_get_song_title(filename);
+    (*len) = -1;
+}
+
+static gchar   *mp4_get_song_title(char *filename)
+{
+    gchar *title;
+    Tuple *tuple = mp4_get_song_tuple(filename);
+
+    title = tuple_formatter_make_title_string(tuple, get_gentitle_format());
+
+    tuple_free(tuple);
+
+    return title;
+}
+
+static int my_decode_mp4( InputPlayback *playback, char *filename, mp4ff_t *mp4file )
+{
+    // We are reading an MP4 file
+    gint mp4track= getAACTrack(mp4file);
+    faacDecHandle   decoder;
+    mp4AudioSpecificConfig mp4ASC;
+    guchar      *buffer = NULL;
+    guint       bufferSize = 0;
+    guint       samplerate = 0;
+    guchar      channels = 0;
+    gulong      msDuration;
+    guint       numSamples;
+    gulong      sampleID = 1;
+    guint       framesize = 1024;
+
+    if (mp4track < 0)
+    {
+        g_print("Unsupported Audio track type\n");
+        return TRUE;
+    }
+
+    gchar *xmmstitle = NULL;
+    xmmstitle = mp4_get_song_title(filename);
+    if(xmmstitle == NULL)
+        xmmstitle = g_strdup(filename);
+
+    decoder = faacDecOpen();
+    mp4ff_get_decoder_config(mp4file, mp4track, &buffer, &bufferSize);
+    if ( !buffer ) {
+        faacDecClose(decoder);
+        return FALSE;
+    }
+    if ( faacDecInit2(decoder, buffer, bufferSize,
+              &samplerate, &channels) < 0 ) {
+        faacDecClose(decoder);
+
+        return FALSE;
+    }
+
+    /* Add some hacks for SBR profile */
+    if (AudioSpecificConfig(buffer, bufferSize, &mp4ASC) >= 0) {
+        if (mp4ASC.frameLengthFlag == 1) framesize = 960;
+        if (mp4ASC.sbr_present_flag == 1) framesize *= 2;
+    }
+
+    g_free(buffer);
+    if( !channels ) {
+        faacDecClose(decoder);
+
+        return FALSE;
+    }
+    numSamples = mp4ff_num_samples(mp4file, mp4track);
+    msDuration = ((float)numSamples * (float)(framesize - 1.0)/(float)samplerate) * 1000;
+    playback->output->open_audio(FMT_S16_NE, samplerate, channels);
+    playback->output->flush(0);
+
+    mp4_ip.set_info(xmmstitle, msDuration,
+            mp4ff_get_avg_bitrate( mp4file, mp4track ),
+            samplerate,channels);
+
+    while ( buffer_playing ) {
+        void*           sampleBuffer;
+        faacDecFrameInfo    frameInfo;
+        gint            rc;
+
+        /* Seek if seek position has changed */
+        if ( seekPosition!=-1 ) {
+            sampleID =  (float)seekPosition*(float)samplerate/(float)(framesize - 1.0);
+            playback->output->flush(seekPosition*1000);
+            seekPosition = -1;
+        }
+
+        /* Otherwise continue playing */
+        buffer=NULL;
+        bufferSize=0;
+
+        /* If we've run to the end of the file, we're done. */
+        if(sampleID >= numSamples){
+            /* Finish playing before we close the
+               output. */
+            while ( playback->output->buffer_playing() ) {
+                g_usleep(10000);
+            }
+
+            playback->output->flush(seekPosition*1000);
+            playback->output->close_audio();
+            faacDecClose(decoder);
+
+            g_static_mutex_lock(&mutex);
+            buffer_playing = FALSE;
+            playback->playing = 0;
+            g_static_mutex_unlock(&mutex);
+            return FALSE;
+        }
+        rc= mp4ff_read_sample(mp4file, mp4track,
+                  sampleID++, &buffer, &bufferSize);
+
+        /*g_print(":: %d/%d\n", sampleID-1, numSamples);*/
+
+        /* If we can't read the file, we're done. */
+        if((rc == 0) || (buffer== NULL) || (bufferSize == 0) || (bufferSize > BUFFER_SIZE)){
+            g_print("MP4: read error\n");
+            sampleBuffer = NULL;
+            sampleID=0;
+            playback->output->buffer_free();
+            playback->output->close_audio();
+
+            faacDecClose(decoder);
+
+            return FALSE;
+        }
+
+/*          g_print(" :: %d/%d\n", bufferSize, BUFFER_SIZE); */
+
+        sampleBuffer= faacDecDecode(decoder,
+                        &frameInfo,
+                        buffer,
+                        bufferSize);
+
+        /* If there was an error decoding, we're done. */
+        if(frameInfo.error > 0){
+            g_print("MP4: %s\n",
+                faacDecGetErrorMessage(frameInfo.error));
+            playback->output->close_audio();
+            faacDecClose(decoder);
+
+            return FALSE;
+        }
+        if(buffer){
+            g_free(buffer);
+            buffer=NULL;
+            bufferSize=0;
+        }
+        if (buffer_playing == FALSE)
+        {
+            playback->output->close_audio();
+            return FALSE;
+        }
+        produce_audio(playback->output->written_time(),
+                   FMT_S16_NE,
+                   channels,
+                   frameInfo.samples<<1,
+                   sampleBuffer, &buffer_playing);
+    }
+
+    playback->output->close_audio();
+    faacDecClose(decoder);
+
+    return TRUE;
+}
+
+void my_decode_aac( InputPlayback *playback, char *filename, VFSFile *file )
+{
+    faacDecHandle   decoder = 0;
+    guchar      streambuffer[BUFFER_SIZE];
+    gulong      bufferconsumed = 0;
+    gulong      samplerate = 0;
+    guchar      channels = 0;
+    gulong      buffervalid = 0;
+    gchar       *ttemp = NULL, *stemp = NULL;
+    gchar       *temp = g_strdup(filename);
+    gchar       *xmmstitle = NULL;
+    gboolean    remote = str_has_prefix_nocase(filename, "http:") ||
+			 str_has_prefix_nocase(filename, "https:");
+
+    vfs_rewind(file);
+    if((decoder = faacDecOpen()) == NULL){
+        g_print("AAC: Open Decoder Error\n");
+        vfs_fclose(file);
+        buffer_playing = FALSE;
+        playback->playing = 0;
+        g_static_mutex_unlock(&mutex);
+        return;
+    }
+    if((buffervalid = vfs_fread(streambuffer, 1, BUFFER_SIZE, file))==0){
+        g_print("AAC: Error reading file\n");
+        vfs_fclose(file);
+        buffer_playing = FALSE;
+        playback->playing = 0;
+        faacDecClose(decoder);
+        g_static_mutex_unlock(&mutex);
+        return;
+    }
+    if(!strncmp((char*)streambuffer, "ID3", 3)){
+        gint size = 0;
+
+        vfs_fseek(file, 0, SEEK_SET);
+        size = (streambuffer[6]<<21) | (streambuffer[7]<<14) |
+		(streambuffer[8]<<7) | streambuffer[9];
+        size+=10;
+        vfs_fread(streambuffer, 1, size, file);
+        buffervalid = vfs_fread(streambuffer, 1, BUFFER_SIZE, file);
+    }
+
+    ttemp = vfs_get_metadata(file, "stream-name");
+
+    if (ttemp != NULL)
+    {
+        xmmstitle = g_strdup(ttemp);
+        g_free(ttemp);
+    }
+    else
+        xmmstitle = g_strdup(g_basename(temp));
+
+    bufferconsumed = aac_probe(streambuffer, buffervalid);
+    if(bufferconsumed) {
+      buffervalid -= bufferconsumed;
+      memmove(streambuffer, &streambuffer[bufferconsumed], buffervalid);
+      buffervalid += vfs_fread(&streambuffer[buffervalid], 1,
+                     BUFFER_SIZE-buffervalid, file);
+      bufferconsumed = 0;
+    }
+
+    bufferconsumed = faacDecInit(decoder,
+                     streambuffer,
+                     buffervalid,
+                     &samplerate,
+                     &channels);
+#ifdef DEBUG
+    g_print("samplerate: %d, channels: %d\n", samplerate, channels);
+#endif
+    if(playback->output->open_audio(FMT_S16_NE,samplerate,channels) == FALSE){
+        g_print("AAC: Output Error\n");
+        faacDecClose(decoder);
+        vfs_fclose(file);
+        playback->output->close_audio();
+        g_free(xmmstitle);
+        buffer_playing = FALSE;
+        playback->playing = 0;
+        g_static_mutex_unlock(&mutex);
+        return;
+    }
+
+    mp4_ip.set_info(xmmstitle, -1, -1, samplerate, channels);
+    playback->output->flush(0);
+
+    while(buffer_playing && buffervalid > 0 && streambuffer != NULL)
+    {
+        faacDecFrameInfo    finfo;
+        unsigned long   samplesdecoded;
+        char*       sample_buffer = NULL;
+
+        if(bufferconsumed > 0)
+        {
+            buffervalid -= bufferconsumed;
+            memmove(streambuffer, &streambuffer[bufferconsumed], buffervalid);
+            buffervalid += vfs_fread(&streambuffer[buffervalid], 1,
+                         BUFFER_SIZE-buffervalid, file);
+            bufferconsumed = 0;
+
+            ttemp = vfs_get_metadata(file, "stream-name");
+
+            if (ttemp != NULL)
+                stemp = vfs_get_metadata(file, "track-name");
+
+            if (stemp != NULL)
+            {
+                static gchar *ostmp = NULL;
+
+                if (ostmp == NULL || g_ascii_strcasecmp(stemp, ostmp))
+                {
+                    if (xmmstitle != NULL)
+                        g_free(xmmstitle);
+
+                    xmmstitle = g_strdup_printf("%s (%s)", stemp, ttemp);
+
+                    if (ostmp != NULL)
+                        g_free(ostmp);
+
+                    ostmp = stemp;
+
+                    mp4_ip.set_info(xmmstitle, -1, -1, samplerate, channels);
+                }
+            }
+
+            g_free(ttemp);
+            ttemp = NULL;
+        }
+
+        sample_buffer = faacDecDecode(decoder, &finfo, streambuffer, buffervalid);
+
+        bufferconsumed += finfo.bytesconsumed;
+        samplesdecoded = finfo.samples;
+
+        if(finfo.error > 0 && remote != FALSE)
+        {
+	    buffervalid--;
+            memmove(streambuffer, &streambuffer[1], buffervalid);
+            if(buffervalid < BUFFER_SIZE) {
+               buffervalid +=
+                 vfs_fread(&streambuffer[buffervalid], 1, BUFFER_SIZE-buffervalid, file);
+	    }
+            bufferconsumed = aac_probe(streambuffer, buffervalid);
+            if(bufferconsumed) {
+               buffervalid -= bufferconsumed;
+               memmove(streambuffer, &streambuffer[bufferconsumed], buffervalid);
+               bufferconsumed = 0;
+            }
+            continue;
+        }
+
+        if((samplesdecoded <= 0) && !sample_buffer){
+#ifdef DEBUG
+            g_print("AAC: decoded %d samples!\n", samplesdecoded);
+#endif
+            continue;
+        }
+
+        produce_audio(playback->output->written_time(),
+                   FMT_S16_LE, channels,
+                   samplesdecoded<<1, sample_buffer, &buffer_playing);
+    }
+    playback->output->buffer_free();
+    playback->output->close_audio();
+    buffer_playing = FALSE;
+    playback->playing = 0;
+    faacDecClose(decoder);
+    g_free(xmmstitle);
+    vfs_fclose(file);
+    seekPosition = -1;
+
+    buffer_playing = FALSE;
+    playback->playing = 0;
+    g_static_mutex_unlock(&mutex);
+}
+
+static void *mp4_decode( void *args )
+{
+    mp4ff_callback_t *mp4cb = g_malloc0(sizeof(mp4ff_callback_t));
+    VFSFile *mp4fh;
+    mp4ff_t *mp4file;
+    gboolean ret;
+
+    InputPlayback *playback = args;
+    char *filename = playback->filename;
+
+    mp4fh = vfs_buffered_file_new_from_uri(filename);
+
+    g_static_mutex_lock(&mutex);
+    seekPosition= -1;
+    buffer_playing= TRUE;
+    g_static_mutex_unlock(&mutex);
+
+    if (mp4fh == NULL)
+        return NULL;
+
+    ret = parse_aac_stream(mp4fh);
+
+    if( ret == TRUE )
+        vfs_fseek(mp4fh, 0, SEEK_SET);
+    else {
+        vfs_fclose(mp4fh);
+        mp4fh = vfs_fopen(filename, "rb");
+    }
+
+    mp4cb->read = mp4_read_callback;
+    mp4cb->seek = mp4_seek_callback;
+    mp4cb->user_data = mp4fh;
+
+    mp4file= mp4ff_open_read(mp4cb);
+
+    if( ret == TRUE ) {
+        g_free(mp4cb);
+        my_decode_aac( playback, filename, mp4fh );
+    }
+    else
+        my_decode_mp4( playback, filename, mp4file );
+
+    return NULL;
+}