root/src/apps/mediaplayer/supplier/ProxyAudioSupplier.cpp
/*
 * Copyright 2008 Stephan Aßmus <superstippi@gmx.de>
 * All Rights Reserved. Distributed under the terms of the MIT license.
 */


#include "ProxyAudioSupplier.h"

#include <algorithm>
#include <new>
#include <stdio.h>
#include <string.h>

#include <Autolock.h>
#include <List.h>

#include "AudioTrackSupplier.h"
#include "AudioAdapter.h"
#include "AudioVolumeConverter.h"
#include "PlaybackManager.h"

using std::nothrow;
using std::swap;


//#define TRACE_PROXY_AUDIO_SUPPLIER
#ifdef TRACE_PROXY_AUDIO_SUPPLIER
# define TRACE(x...)    printf("ProxyAudioSupplier::"); printf(x)
# define ERROR(x...)    fprintf(stderr, "ProxyAudioSupplier::"); fprintf(stderr, x)
#else
# define TRACE(x...)
# define ERROR(x...)    fprintf(stderr, "ProxyAudioSupplier::"); fprintf(stderr, x)
#endif


struct PlayingInterval {
        PlayingInterval(bigtime_t startTime, bigtime_t endTime)
                :
                start_time(startTime),
                end_time(endTime)
        {
        }

        bigtime_t       start_time;
        bigtime_t       end_time;
        bigtime_t       x_start_time;
        bigtime_t       x_end_time;
        float           speed;
};


ProxyAudioSupplier::ProxyAudioSupplier(PlaybackManager* playbackManager)
        :
        fSupplierLock("audio supplier lock"),

        fPlaybackManager(playbackManager),
        fVideoFrameRate(25.0),
        fVolume(1.0),

        fSupplier(NULL),
        fAdapter(NULL),
        fVolumeConverter(NULL),
        fAudioResampler()
{
        TRACE("ProxyAudioSupplier()\n");
}


ProxyAudioSupplier::~ProxyAudioSupplier()
{
        TRACE("~ProxyAudioSupplier()\n");
        delete fAdapter;
        delete fVolumeConverter;
}


bigtime_t
ProxyAudioSupplier::InitialLatency() const
{
        BAutolock _(fSupplierLock);

        if (fSupplier == NULL)
                return 0;

        return fSupplier->InitialLatency();
}


status_t
ProxyAudioSupplier::GetFrames(void* buffer, int64 frameCount,
        bigtime_t startTime, bigtime_t endTime)
{
        TRACE("GetFrames(%p, frameCount: %" B_PRId64 ", time interval: %"
                B_PRIdBIGTIME " - %" B_PRIdBIGTIME ")\n",
                buffer, frameCount, startTime, endTime);

        // Create a list of playing intervals which compose the supplied
        // performance time interval.
        BList playingIntervals;
        status_t error = fPlaybackManager->LockWithTimeout(10000);
        if (error == B_OK) {
                bigtime_t intervalStartTime = startTime;
                while (intervalStartTime < endTime) {
                        PlayingInterval* interval
                                = new (nothrow) PlayingInterval(intervalStartTime, endTime);
                        if (!interval) {
                                error = B_NO_MEMORY;
                                break;
                        }
                        fPlaybackManager->GetPlaylistTimeInterval(
                                interval->start_time, interval->end_time,
                                interval->x_start_time, interval->x_end_time,
                                interval->speed);
                        if (intervalStartTime == interval->end_time) {
                                delete interval;
                                error = B_ERROR;
                                ERROR("GetFrames() - zero duration audio interval! start "
                                        "time: %" B_PRIdBIGTIME "\n", intervalStartTime);
                                break;
                        }
                        if (!playingIntervals.AddItem(interval)) {
                                delete interval;
                                error = B_NO_MEMORY;
                                ERROR("GetFrames() - Out of memory\n");
                                break;
                        }
                        intervalStartTime = interval->end_time;
                }
                fPlaybackManager->SetCurrentAudioTime(endTime);
                fPlaybackManager->Unlock();
        } else if (error == B_TIMED_OUT) {
                TRACE("GetFrames() - LOCKING THE PLAYBACK MANAGER TIMED OUT!!!\n");
        }

        BAutolock _(fSupplierLock);

        if (!fSupplier)
                return B_ERROR;

        // retrieve the audio data for each interval.
        #ifdef TRACE_PROXY_AUDIO_SUPPLIER
        int32 intervalIndex = 0;
        #endif

        int64 framesRead = 0;
        while (!playingIntervals.IsEmpty()) {
                PlayingInterval* interval
                        = (PlayingInterval*)playingIntervals.RemoveItem((int32)0);
                if (error != B_OK) {
                        delete interval;
                        continue;
                }

                // get playing direction
                int32 playingDirection = 0;
                if (interval->speed > 0)
                        playingDirection = 1;
                else if (interval->speed < 0)
                        playingDirection = -1;
                float absSpeed = interval->speed * playingDirection;
                int64 framesToRead = _AudioFrameForTime(interval->end_time)
                        - _AudioFrameForTime(interval->start_time);

                TRACE("GetFrames() - interval (%ld) [%lld, %lld]: [%lld, %lld], "
                        "frames: %lld\n", intervalIndex,
                        interval->start_time, interval->end_time,
                        interval->x_start_time, interval->x_end_time,
                        framesToRead);

                // not playing
                if (absSpeed == 0)
                        _ReadSilence(buffer, framesToRead);
                // playing
                else {
                        fAudioResampler.SetInOffset(
                                _AudioFrameForTime(interval->x_start_time));
                        fAudioResampler.SetTimeScale(absSpeed);
                        error = fAudioResampler.Read(buffer, 0, framesToRead);
                        // backwards -> reverse frames
                        if (error == B_OK && interval->speed < 0)
                                _ReverseFrames(buffer, framesToRead);
                }
                // read silence on error
                if (error != B_OK) {
                        _ReadSilence(buffer, framesToRead);
                        error = B_OK;
                }
                framesRead += framesToRead;
                buffer = _SkipFrames(buffer, framesToRead);
                delete interval;

                #ifdef TRACE_PROXY_AUDIO_SUPPLIER
                intervalIndex++;
                #endif
        }
        // read silence on error
        if (error != B_OK) {
                _ReadSilence(buffer, frameCount);
                error = B_OK;
        }

        TRACE("GetFrames() done\n");

        return error;
}


void
ProxyAudioSupplier::SetFormat(const media_format& format)
{
//printf("ProxyAudioSupplier::SetFormat()\n");
        #ifdef TRACE_PROXY_AUDIO_SUPPLIER
                char string[256];
                string_for_format(format, string, 256);
                TRACE("SetFormat(%s)\n", string);
        #endif

        BAutolock _(fSupplierLock);

        fAudioResampler.SetFormat(format);

        // In case SetSupplier was called before, we need
        // to adapt to the new format, or maybe the format
        // was still invalid.
        SetSupplier(fSupplier, fVideoFrameRate);
}


const media_format&
ProxyAudioSupplier::Format() const
{
        return fAudioResampler.Format();
}


status_t
ProxyAudioSupplier::InitCheck() const
{
        status_t ret = AudioSupplier::InitCheck();
        if (ret < B_OK)
                return ret;
        return B_OK;
}


void
ProxyAudioSupplier::SetSupplier(AudioTrackSupplier* supplier,
        float videoFrameRate)
{
//printf("ProxyAudioSupplier::SetSupplier(%p, %.1f)\n", supplier,
//videoFrameRate);
        TRACE("SetSupplier(%p, %.1f)\n", supplier, videoFrameRate);

        BAutolock _(fSupplierLock);

        fSupplier = supplier;
        fVideoFrameRate = videoFrameRate;

        delete fAdapter;
        delete fVolumeConverter;

        fAdapter = new AudioAdapter(fSupplier, Format());
        fVolumeConverter = new AudioVolumeConverter(fAdapter, fVolume);

        fAudioResampler.SetSource(fVolumeConverter);
}


void
ProxyAudioSupplier::SetVolume(float volume)
{
        BAutolock _(fSupplierLock);
        fVolume = volume;
        if (fVolumeConverter)
                fVolumeConverter->SetVolume(volume);
}


float
ProxyAudioSupplier::Volume()
{
        BAutolock _(fSupplierLock);
        return fVolume;
}


// #pragma mark - audio/video/frame/time conversion


int64
ProxyAudioSupplier::_AudioFrameForVideoFrame(int64 frame) const
{
        if (!fSupplier) {
                return (int64)((double)frame * Format().u.raw_audio.frame_rate
                        / fVideoFrameRate);
        }
        const media_format& format = fSupplier->Format();
        return (int64)((double)frame * format.u.raw_audio.frame_rate
                / fVideoFrameRate);
}


int64
ProxyAudioSupplier::_VideoFrameForAudioFrame(int64 frame) const
{
        if (!fSupplier) {
                return (int64)((double)frame * fVideoFrameRate
                        / Format().u.raw_audio.frame_rate);
        }

        const media_format& format = fSupplier->Format();
        return (int64)((double)frame * fVideoFrameRate
                / format.u.raw_audio.frame_rate);
}


int64
ProxyAudioSupplier::_AudioFrameForTime(bigtime_t time) const
{
        return (int64)((double)time * Format().u.raw_audio.frame_rate
                / 1000000.0);
}


int64
ProxyAudioSupplier::_VideoFrameForTime(bigtime_t time) const
{
        return (int64)((double)time * fVideoFrameRate / 1000000.0);
}


// #pragma mark - utility


void
ProxyAudioSupplier::_ReadSilence(void* buffer, int64 frames) const
{
        memset(buffer, 0, (char*)_SkipFrames(buffer, frames) - (char*)buffer);
}


void
ProxyAudioSupplier::_ReverseFrames(void* buffer, int64 frames) const
{
        int32 sampleSize = Format().u.raw_audio.format
                & media_raw_audio_format::B_AUDIO_SIZE_MASK;
        int32 frameSize = sampleSize * Format().u.raw_audio.channel_count;
        char* front = (char*)buffer;
        char* back = (char*)buffer + (frames - 1) * frameSize;
        while (front < back) {
                for (int32 i = 0; i < frameSize; i++)
                        swap(front[i], back[i]);
                front += frameSize;
                back -= frameSize;
        }
}


void*
ProxyAudioSupplier::_SkipFrames(void* buffer, int64 frames) const
{
        int32 sampleSize = Format().u.raw_audio.format
                & media_raw_audio_format::B_AUDIO_SIZE_MASK;
        int32 frameSize = sampleSize * Format().u.raw_audio.channel_count;
        return (char*)buffer + frames * frameSize;
}