shithub: aacdec

ref: 94641c20ec3a009c19c256d7a3f751bef7eca4dc
dir: /plugins/foo_mp4/foo_mp4.cpp/

View raw version
/*
** FAAD - Freeware Advanced Audio Decoder
** Copyright (C) 2002-2003 M. Bakker
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
**
** $Id: foo_mp4.cpp,v 1.18 2003/04/26 10:02:42 menno Exp $
**/

#include <mp4.h>
#include <faad.h>
#include "pfc/pfc.h"
#include "foobar2000/SDK/input.h"
#include "foobar2000/SDK/console.h"
#include "foobar2000/SDK/componentversion.h"

char *STRIP_REVISION(const char *str)
{
    char *tmp = strchr(str, ' ');
    tmp[strlen(tmp)-2] = '\0';
    return tmp;
}

DECLARE_COMPONENT_VERSION ("MPEG-4 AAC decoder",
                           STRIP_REVISION("$Revision: 1.18 $"),
                           "Based on FAAD2 v" FAAD2_VERSION "\nCopyright (C) 2002-2003 http://www.audiocoding.com" );

class input_mp4 : public input
{
public:

    virtual int test_filename(const char * fn,const char * ext)
    {
        int is_mp4 = !stricmp(ext,"MP4");
        int is_aac = !stricmp(ext,"AAC");

        if (is_aac)
            m_stream_type = 1;
        else if (is_mp4)
            m_stream_type = 0;

        return is_mp4 || is_aac;
    }

    virtual int open(reader *r, file_info *info, int full_open)
    {
        faacDecConfigurationPtr config;

        m_reader = r;

        hDecoder = faacDecOpen();
        if (!hDecoder)
        {
            console::error("Failed to open FAAD2 library.", "foo_mp4");
            return 0;
        }

        config = faacDecGetCurrentConfiguration(hDecoder);
        config->outputFormat = FAAD_FMT_DOUBLE;
        faacDecSetConfiguration(hDecoder, config);

        if (m_stream_type == 0)
            return open_mp4(info, full_open);
        else if (m_stream_type == 1)
            return open_aac(info, full_open);
        else
            return 0;
    }

    input_mp4()
    {
        m_stream_type = -1;
        hFile = MP4_INVALID_FILE_HANDLE;
        hDecoder = NULL;
        m_aac_buffer = NULL;
    }

    ~input_mp4()
    {
        if (hFile != MP4_INVALID_FILE_HANDLE)
            MP4Close(hFile);
        if (hDecoder)
            faacDecClose(hDecoder);
        if (m_aac_buffer)
            free(m_aac_buffer);
    }

    virtual int run(audio_chunk * chunk)
    {
        if (m_stream_type == 0)
            return decode_chunk_mp4(chunk);
        else if (m_stream_type == 1)
            return decode_chunk_aac(chunk);
        else
            return 0;
    }

    virtual int set_info(reader *r,const file_info * info)
    {
        m_reader = r;

        if (m_stream_type == 0)
            return set_info_mp4(info);
//        else if (m_stream_type == 1)
//            return set_info_aac(info);
        else
            return 0;
    }

    virtual int seek(double seconds)
    {
        if (m_stream_type == 0)
        {
            MP4Duration duration;

            duration = MP4ConvertToTrackDuration(hFile,
                track, seconds, MP4_SECS_TIME_SCALE);
            sampleId = MP4GetSampleIdFromTime(hFile,
                track, duration, 0);

            return 1;
        } else {
            return 0;
        }
    }
    
    virtual int is_our_content_type(const char *url, const char *type)
    {
        return !strcmp(type, "audio/mp4") || !strcmp(type, "audio/x-mp4") ||
            !strcmp(type, "audio/aac") || !strcmp(type, "audio/x-aac");
    }

private:

    reader *m_reader;
    int m_stream_type;

    faacDecHandle hDecoder;

    /* MP4 file stuff */
    MP4FileHandle hFile;
    MP4SampleId sampleId, numSamples;
    MP4TrackId track;
    /* end MP4 file stuff */

    /* AAC file stuff */
    long m_aac_bytes_read;
    long m_aac_bytes_into_buffer;
    long m_aac_bytes_consumed;
    unsigned char *m_aac_buffer;
    int m_at_eof;
    /* end AAC file stuff */

    int open_mp4(file_info *info, int full_open)
    {
        unsigned __int8 *buffer;
        unsigned __int32 buffer_size;
        unsigned __int8 channels;
        unsigned __int32 samplerate;

        hFile = MP4ReadCb(0, open_cb, close_cb, read_cb, write_cb,
            setpos_cb, getpos_cb, filesize_cb, (void*)m_reader);
        if (hFile == MP4_INVALID_FILE_HANDLE)
        {
            console::error("Failed to open MP4 file.", "foo_mp4");
            return 0;
        }

        track = GetAACTrack(hFile);
        if (track < 1)
        {
            console::error("No valid AAC track found.", "foo_mp4");
            return 0;
        }

        buffer = NULL;
        buffer_size = 0;
        MP4GetTrackESConfiguration(hFile, track, &buffer, &buffer_size);
        if (!buffer)
        {
            console::error("Unable to read track specific configuration.", "foo_mp4");
            return 0;
        }

        int rc = faacDecInit2(hDecoder, (unsigned char*)buffer, buffer_size,
            (unsigned long*)&samplerate, (unsigned char*)&channels);
        if (buffer) free(buffer);
        if (rc < 0)
        {
            console::error("Unable to initialise FAAD2 library.", "foo_mp4");
            return 0;
        }

        numSamples = MP4GetTrackNumberOfSamples(hFile, track);
        sampleId = 1;

        unsigned __int64 length = MP4GetTrackDuration(hFile, track);
        __int64 msDuration = MP4ConvertFromTrackDuration(hFile, track,
            length, MP4_MSECS_TIME_SCALE);
        info->set_length((double)msDuration/1000.0);

        info->info_set_int("bitrate",(__int64)(1.0/1000.0 *
            (double)(__int64)MP4GetTrackIntegerProperty(hFile,
            track, "mdia.minf.stbl.stsd.mp4a.esds.decConfigDescr.avgBitrate")) + 0.5);
        info->info_set_int("channels", (__int64)channels);
        info->info_set_int("samplerate", (__int64)samplerate);

        ReadMP4Tag(info);

        return 1;
    }

    int open_aac(file_info *info, int full_open)
    {
        int tagsize = 0, tmp = 0;
        int bread = 0;
        unsigned char channels = 0;
        unsigned long samplerate = 0;

        m_at_eof = 0;

        if (!(m_aac_buffer = (unsigned char*)malloc(768*6)))
        {
            console::error("Memory allocation error.", "foo_mp4");
            return 0;
        }
        memset(m_aac_buffer, 0, 768*6);

        bread = m_reader->read(m_aac_buffer, 768*6);
        m_aac_bytes_read = bread;
        m_aac_bytes_into_buffer = bread;

        if (bread != 768*6)
            m_at_eof = 1;

        if (!stricmp((const char*)m_aac_buffer, "ID3"))
        {
            /* high bit is not used */
            tagsize = (m_aac_buffer[6] << 21) | (m_aac_buffer[7] << 14) |
                (m_aac_buffer[8] <<  7) | (m_aac_buffer[9] <<  0);

            tagsize += 10;
        }

        if ((m_aac_bytes_consumed = faacDecInit(hDecoder,
            m_aac_buffer+tagsize, m_aac_bytes_into_buffer,
            &samplerate, &channels)) < 0)
        {
            console::error("Can't initialize decoder library.", "foo_mp4");
            return 0;
        }
        m_aac_bytes_consumed += tagsize;
        m_aac_bytes_into_buffer -= m_aac_bytes_consumed;

        info->set_length(0);

        info->info_set_int("bitrate", 0);
        info->info_set_int("channels", (__int64)channels);
        info->info_set_int("samplerate", (__int64)samplerate);

        return 1;
    }

    int decode_chunk_mp4(audio_chunk * chunk)
    {
        faacDecFrameInfo frameInfo;
        unsigned char *buffer;
        unsigned __int32 buffer_size;
        void *sample_buffer;

        if (sampleId == MP4_INVALID_SAMPLE_ID)
        {
            console::error("Invalid sampleId.", "foo_mp4");
            return 0;
        }

        do {
            buffer = NULL;
            buffer_size = 0;

            MP4ReadSample(hFile, track, sampleId,
                (unsigned __int8**)&buffer, &buffer_size,
                NULL, NULL, NULL, NULL);
            sampleId++;

            sample_buffer = faacDecDecode(hDecoder, &frameInfo, buffer, buffer_size);

            if (buffer) free(buffer);

        } while ((frameInfo.error == 0) && (frameInfo.samples == 0));

        if (frameInfo.error || (sampleId > numSamples))
        {
            if (frameInfo.error)
                console::error(faacDecGetErrorMessage(frameInfo.error), "foo_mp4");
            return 0;
        }

        chunk->data = (audio_sample*)sample_buffer;
        chunk->samples = frameInfo.samples/frameInfo.channels;
        chunk->nch = frameInfo.channels;
        chunk->srate = frameInfo.samplerate;

        return 1;
    }

    int decode_chunk_aac(audio_chunk * chunk)
    {
        int bread = 0;
        faacDecFrameInfo frameInfo;
        void *sample_buffer;

        do
        {
            if (m_aac_bytes_consumed > 0)
            {
                if (m_aac_bytes_into_buffer)
                {
                    memmove((void*)m_aac_buffer, (void*)(m_aac_buffer + m_aac_bytes_consumed),
                        m_aac_bytes_into_buffer*sizeof(unsigned char));
                }

                if (!m_at_eof)
                {
                    bread = m_reader->read((void*)(m_aac_buffer + m_aac_bytes_into_buffer),
                        m_aac_bytes_consumed);

                    if (bread != m_aac_bytes_consumed)
                        m_at_eof = 1;

                    m_aac_bytes_read += bread;
                    m_aac_bytes_into_buffer += bread;
                }

                m_aac_bytes_consumed = 0;
            }

            sample_buffer = faacDecDecode(hDecoder, &frameInfo,
                m_aac_buffer, m_aac_bytes_into_buffer);

            m_aac_bytes_consumed += frameInfo.bytesconsumed;
            m_aac_bytes_into_buffer -= frameInfo.bytesconsumed;

        } while (!frameInfo.samples && !frameInfo.error);

        if (frameInfo.error || (m_aac_bytes_into_buffer == 0))
        {
            if (frameInfo.error)
                console::error(faacDecGetErrorMessage(frameInfo.error), "foo_mp4");
            return 0;
        }

        chunk->data = (audio_sample*)sample_buffer;
        chunk->samples = frameInfo.samples/frameInfo.channels;
        chunk->nch = frameInfo.channels;
        chunk->srate = frameInfo.samplerate;

        return 1;
    }

    int set_info_mp4(const file_info * info)
    {
        hFile = MP4ModifyCb(0, 0, open_cb, close_cb, read_cb, write_cb,
            setpos_cb, getpos_cb, filesize_cb, (void*)m_reader);
        if (hFile == MP4_INVALID_FILE_HANDLE) return 0;

        track = GetAACTrack(hFile);
        if (track < 1)
        {
            console::error("No valid AAC track found.", "foo_mp4");
            return 0;
        }

        MP4TagDelete(hFile, track);

        /* replay gain writing */
        const char *p = NULL;

        p = info->info_get("REPLAYGAIN_TRACK_PEAK");
        if (p)
            MP4TagAddEntry(hFile, track, "REPLAYGAIN_TRACK_PEAK", p);
        p = info->info_get("REPLAYGAIN_TRACK_GAIN");
        if (p)
            MP4TagAddEntry(hFile, track, "REPLAYGAIN_TRACK_GAIN", p);
        p = info->info_get("REPLAYGAIN_ALBUM_PEAK");
        if (p)
            MP4TagAddEntry(hFile, track, "REPLAYGAIN_ALBUM_PEAK", p);
        p = info->info_get("REPLAYGAIN_ALBUM_GAIN");
        if (p)
            MP4TagAddEntry(hFile, track, "REPLAYGAIN_ALBUM_GAIN", p);

        int numItems = info->meta_get_count();
        if (numItems > 0)
        {
            for (int i = 0; i < numItems; i++)
            {
                const char *n = info->meta_enum_name(i);
                const char *v = info->meta_enum_value(i);
                MP4TagAddEntry(hFile, track, n, v);
            }
        }

        numItems = MP4TagGetNumEntries(hFile, track);
        if (numItems == 0)
            MP4TagDelete(hFile, track);

        /* end */
        return 1;
    }

    int ReadMP4Tag(file_info *info)
    {
        int numItems = MP4TagGetNumEntries(hFile, track);

        for (int i = 0; i < numItems; i++)
        {
            float f = 0.0;
            const char *n = NULL, *v = NULL;

            MP4TagGetEntry(hFile, track, i, &n, &v);

            if (!strcmp(n, "REPLAYGAIN_TRACK_PEAK"))
            {
                sscanf(v, "%f", &f);
                info->info_set_replaygain_track_peak((double)f);
            } else if (!strcmp(n, "REPLAYGAIN_TRACK_GAIN")) {
                sscanf(v, "%f", &f);
                info->info_set_replaygain_track_gain((double)f);
            } else if (!strcmp(n, "REPLAYGAIN_ALBUM_PEAK")) {
                sscanf(v, "%f", &f);
                info->info_set_replaygain_album_peak((double)f);
            } else if (!strcmp(n, "REPLAYGAIN_ALBUM_GAIN")) {
                sscanf(v, "%f", &f);
                info->info_set_replaygain_album_gain((double)f);
            } else {
                info->meta_add(n, v);
            }
        }

        return 1;
    }

    int GetAACTrack(MP4FileHandle infile)
    {
        /* find AAC track */
        int i, rc;
        int numTracks = MP4GetNumberOfTracks(infile, NULL, /* subType */ 0);

        for (i = 0; i < numTracks; i++)
        {
            MP4TrackId trackId = MP4FindTrackId(infile, i, NULL, /* subType */ 0);
            const char* trackType = MP4GetTrackType(infile, trackId);

            if (!strcmp(trackType, MP4_AUDIO_TRACK_TYPE))
            {
                unsigned char *buff = NULL;
                int buff_size = 0;
                mp4AudioSpecificConfig mp4ASC;

                MP4GetTrackESConfiguration(infile, trackId,
                    (unsigned __int8**)&buff, (unsigned __int32*)&buff_size);

                if (buff)
                {
                    rc = AudioSpecificConfig(buff, buff_size, &mp4ASC);
                    free(buff);

                    if (rc < 0)
                        return -1;
                    return trackId;
                }
            }
        }

        /* can't decode this */
        return -1;
    }

    /* file callback stuff */
    static unsigned __int32 open_cb(const char *pName,
        const char *mode, void *userData)
    {
        return 1;
    }

    static void close_cb(void *userData)
    {
        return;
    }

    static unsigned __int32 read_cb(void *pBuffer, unsigned int nBytesToRead,
        void *userData)
    {
        reader *r = (reader*)userData;
        return r->read(pBuffer, nBytesToRead);
    }

    static unsigned __int32 write_cb(void *pBuffer, unsigned int nBytesToWrite,
        void *userData)
    {
        reader *r = (reader*)userData;
        return r->write(pBuffer, nBytesToWrite);
    }

    static __int64 getpos_cb(void *userData)
    {
        reader *r = (reader*)userData;
        return r->get_position();
    }

    static __int32 setpos_cb(unsigned __int32 pos, void *userData)
    {
        reader *r = (reader*)userData;
        return !(r->seek(pos));
    }

    static __int64 filesize_cb(void *userData)
    {
        reader *r = (reader*)userData;
        return r->get_length();
    }
};

static service_factory_t<input,input_mp4> foo;