root/src/add-ons/media/plugins/ape_reader/APEReader.cpp
/* Copyright 2005-2009 SHINTA
 * Distributed under the terms of the MIT license
 */


#include <InterfaceDefs.h>
#include <MediaIO.h>

#include "APEReader.h"
#include "MACLib.h"


static const char*      kCopyrightString
        = "Copyright " B_UTF8_COPYRIGHT " 2005-2009 by SHINTA";


TAPEReader::TAPEReader()
        : SUPER()
{
        mDecodedData = NULL;
        mDecomp = NULL;
        Unset();
}


TAPEReader::~TAPEReader()
{
}


status_t
TAPEReader::AllocateCookie(int32 oStreamNumber, void** oCookie)
{
        *oCookie = NULL;
        return B_OK;
}


const char*
TAPEReader::Copyright()
{
        return kCopyrightString;
}


bigtime_t
TAPEReader::CurrentTime() const
{
        return mDecomp->GetInfo(APE_DECOMPRESS_CURRENT_MS)
                * static_cast<bigtime_t>(1000);
}


status_t
TAPEReader::FreeCookie(void* oCookie)
{
        return B_OK;
}


void
TAPEReader::GetFileFormatInfo(media_file_format* oMFF)
{
        oMFF->capabilities = media_file_format::B_READABLE
                        | media_file_format::B_PERFECTLY_SEEKABLE
//                      | media_file_format::B_IMPERFECTLY_SEEKABLE
                        | media_file_format::B_KNOWS_RAW_AUDIO
                        | media_file_format::B_KNOWS_ENCODED_AUDIO;
        oMFF->family = B_ANY_FORMAT_FAMILY;
        oMFF->version = MEDIA_FILE_FORMAT_VERSION;
        strlcpy(oMFF->mime_type, MIME_TYPE_APE, sizeof(oMFF->mime_type));
        strlcpy(oMFF->pretty_name, MIME_TYPE_APE_LONG_DESCRIPTION, sizeof(oMFF->pretty_name));
        strlcpy(oMFF->short_name, MIME_TYPE_APE_SHORT_DESCRIPTION, sizeof(oMFF->short_name));
        strlcpy(oMFF->file_extension, MIME_TYPE_APE_EXTENSION, sizeof(oMFF->file_extension));
}


status_t
TAPEReader::GetNextChunk(void* oCookie, const void** oChunkBuffer,
        size_t* oChunkSize, media_header* oMediaHeader)
{
        int64           aOutSize;

        // check whether song is finished or not
        if (mReadPosTotal - mReadPos + mPlayPos >= mDataSize)
                return B_ERROR;

        // reading data
        if (mPlayPos >= mReadPos )
                ReadBlocks();

        // passing data
        if (mReadPos-mPlayPos >= BUFFER_SIZE)
                aOutSize = BUFFER_SIZE;
        else
                aOutSize = mReadPos-mPlayPos;

        *oChunkBuffer = &mDecodedData[mPlayPos];
        mPlayPos += aOutSize;

        // passing info
        *oChunkSize = aOutSize;
        oMediaHeader->start_time = CurrentTime();
        oMediaHeader->file_pos = mPlayPos;
        return B_OK;
}


status_t
TAPEReader::GetStreamInfo(void* oCookie, int64* oFrameCount,
        bigtime_t* oDuration, media_format* oFormat, const void** oInfoBuffer,
        size_t* oInfoSize)
{
        if (LoadAPECheck() != B_OK)
                return LoadAPECheck();

        *oFrameCount = mDataSize / (mDecomp->GetInfo(APE_INFO_BITS_PER_SAMPLE) / 8
                        * mDecomp->GetInfo(APE_INFO_CHANNELS));
        *oDuration = mDecomp->GetInfo(APE_INFO_LENGTH_MS)
                * static_cast<bigtime_t>(1000);
        // media_format
        oFormat->type = B_MEDIA_RAW_AUDIO;
        oFormat->u.raw_audio.frame_rate = mDecomp->GetInfo(APE_INFO_SAMPLE_RATE);
        oFormat->u.raw_audio.channel_count = mDecomp->GetInfo(APE_INFO_CHANNELS);
        if ( mDecomp->GetInfo(APE_INFO_BITS_PER_SAMPLE) == 16 )
                oFormat->u.raw_audio.format = media_raw_audio_format::B_AUDIO_SHORT;
        else
                oFormat->u.raw_audio.format = media_raw_audio_format::B_AUDIO_UCHAR;

        oFormat->u.raw_audio.byte_order = B_MEDIA_LITTLE_ENDIAN;
        oFormat->u.raw_audio.buffer_size = BUFFER_SIZE;
        oInfoBuffer = NULL;
        oInfoSize = NULL;
        return B_OK;
}


status_t
TAPEReader::LoadAPECheck() const
{
        return mLoadAPECheck;
}


status_t
TAPEReader::ReadBlocks()
{
        int aBlocksRead;
        int aRetVal = 0;

        aRetVal = mDecomp->GetData(reinterpret_cast<char*>(mDecodedData),
                BLOCK_COUNT, &aBlocksRead);
        if (aRetVal != ERROR_SUCCESS)
                return B_ERROR;

        mPlayPos = 0;
        mReadPos = aBlocksRead*mDecomp->GetInfo(APE_INFO_BLOCK_ALIGN);
        mReadPosTotal += mReadPos;
        return B_OK;
}


status_t
TAPEReader::FindKeyFrame(void* cookie, uint32 flags, int64* frame,
        bigtime_t* time)
{
        if (flags & B_MEDIA_SEEK_TO_FRAME) {
                *time = *frame * 1000 /  mDecomp->GetInfo(APE_DECOMPRESS_TOTAL_BLOCKS)
                        * mDecomp->GetInfo(APE_DECOMPRESS_LENGTH_MS);
                printf("FindKeyFrame for frame %lld: %lld\n", *frame, *time);
        } else if (flags & B_MEDIA_SEEK_TO_TIME) {
                *frame = (*time) / 1000 * mDecomp->GetInfo(APE_DECOMPRESS_TOTAL_BLOCKS)
                        / mDecomp->GetInfo(APE_DECOMPRESS_LENGTH_MS);
                printf("FindKeyFrame for time %lld: %lld\n", *time, *frame);
        } else
                return B_ERROR;

        return B_OK;
}


status_t
TAPEReader::Seek(void *cookie, uint32 flags, int64 *frame, bigtime_t *time)
{
        int32 aNewBlock;

        if (flags & B_MEDIA_SEEK_TO_FRAME) {
                printf("Seek to frame %lld\n", *frame);
                aNewBlock = *frame;
        } else if (flags & B_MEDIA_SEEK_TO_TIME) {
                printf("Seek for time %lld\n", *time);
                aNewBlock = (*time) / 1000 * mDecomp->GetInfo(APE_DECOMPRESS_TOTAL_BLOCKS)
                        / mDecomp->GetInfo(APE_DECOMPRESS_LENGTH_MS);
        } else
                return B_ERROR;

        int64 aNewTime = aNewBlock * mDecomp->GetInfo(APE_INFO_BLOCK_ALIGN);
        if (mReadPosTotal - mReadPos < aNewTime && mReadPosTotal > aNewTime) {
                // Requested seek frame is already in the current buffer, no need to
                // actually seek, just set the play position
                mPlayPos = aNewTime - mReadPosTotal + mReadPos;
        } else {
                mReadPosTotal = aNewBlock * mDecomp->GetInfo(APE_INFO_BLOCK_ALIGN);
                mDecomp->Seek(aNewBlock);
                ReadBlocks();
        }
        return B_OK;
}


status_t
TAPEReader::Sniff(int32* oStreamCount)
{
        Unset();
        // prepare about file
        mSrcPIO = dynamic_cast<BPositionIO*>(Source());
        if (mSrcPIO == NULL)
                return B_ERROR;

        BMediaIO* mediaIO = dynamic_cast<BMediaIO*>(Source());
        if (mediaIO != NULL) {
                int32 flags = 0;
                mediaIO->GetFlags(&flags);
                // This plugin doesn't support streamed data.
                // The APEHeader::FindDescriptor function always
                // analyze the whole file to find the APE_DESCRIPTOR.
                if ((flags & B_MEDIA_STREAMING) == true)
                        return B_ERROR;
        }

        int nFunctionRetVal = ERROR_SUCCESS;
        mPositionBridgeIO.SetPositionIO(mSrcPIO);

        mDecomp = CreateIAPEDecompressEx(&mPositionBridgeIO, &nFunctionRetVal);
        if (mDecomp == NULL || nFunctionRetVal != ERROR_SUCCESS)
                return B_ERROR;

        // prepare about data
        mDataSize = static_cast<int64>(mDecomp->GetInfo(APE_DECOMPRESS_TOTAL_BLOCKS))
                        *mDecomp->GetInfo(APE_INFO_BLOCK_ALIGN);
        mDecodedData = new char [max_c(BUFFER_SIZE*mDecomp->GetInfo(APE_INFO_CHANNELS),
                        BLOCK_COUNT*mDecomp->GetInfo(APE_INFO_BLOCK_ALIGN))];
        mLoadAPECheck = B_OK;
        *oStreamCount = 1;
        return B_OK;
}


void
TAPEReader::Unset()
{
        mLoadAPECheck = B_NO_INIT;
        // about file
        mPositionBridgeIO.SetPositionIO(NULL);
        mSrcPIO = NULL;
        delete mDecomp;
        // about data
        mDataSize = 0;
        mReadPos = 0;
        mReadPosTotal = 0;
        mPlayPos = 0;
        delete [] mDecodedData;
}


TAPEReaderPlugin::TAPEReaderPlugin()
{
}


TAPEReaderPlugin::~TAPEReaderPlugin()
{
}


Reader*
TAPEReaderPlugin::NewReader()
{
        return new TAPEReader();
}


MediaPlugin*
instantiate_plugin()
{
        return new TAPEReaderPlugin();
}