/******************************************************************************\
 * Copyright (c) 2004-2015
 *
 * Author(s):
 *  Volker Fischer
 *
 ******************************************************************************
 *
 * 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
 *
\******************************************************************************/

#include "sound.h"


/* Implementation *************************************************************/
CSound::CSound ( void (*fpNewProcessCallback) ( CVector<short>& psData, void* arg ), void* arg ) :
    CSoundBase ( "OpenSL", true, fpNewProcessCallback, arg )
{

}

void CSound::InitializeOpenSL()
{
    // set up stream formats for input and output
    SLDataFormat_PCM inStreamFormat;
    inStreamFormat.formatType    = SL_DATAFORMAT_PCM;
    inStreamFormat.numChannels   = 1;
    inStreamFormat.samplesPerSec = SL_SAMPLINGRATE_16;
    inStreamFormat.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
    inStreamFormat.containerSize = 16;
    inStreamFormat.channelMask   = SL_SPEAKER_FRONT_CENTER;
    inStreamFormat.endianness    = SL_BYTEORDER_LITTLEENDIAN;

    SLDataFormat_PCM outStreamFormat;
    outStreamFormat.formatType    = SL_DATAFORMAT_PCM;
    outStreamFormat.numChannels   = 2;
    outStreamFormat.samplesPerSec = SYSTEM_SAMPLE_RATE_HZ * 1000; // unit is mHz
    outStreamFormat.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
    outStreamFormat.containerSize = 16;
    outStreamFormat.channelMask   = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
    outStreamFormat.endianness    = SL_BYTEORDER_LITTLEENDIAN;

    // create the OpenSL root engine object
    slCreateEngine ( &engineObject,
                     0,
                     nullptr,
                     0,
                     nullptr,
                     nullptr );

    // realize the engine
    (*engineObject)->Realize ( engineObject,
                               SL_BOOLEAN_FALSE );

    // get the engine interface (required to create other objects)
    (*engineObject)->GetInterface ( engineObject,
                                    SL_IID_ENGINE,
                                    &engine );

    // create the main output mix
    (*engine)->CreateOutputMix ( engine,
                                 &outputMixObject,
                                 0,
                                 nullptr,
                                 nullptr );

    // realize the output mix
    (*outputMixObject)->Realize ( outputMixObject,
                                  SL_BOOLEAN_FALSE );

    // configure the audio (data) source for input
    SLDataLocator_IODevice micLocator;
    micLocator.locatorType = SL_DATALOCATOR_IODEVICE;
    micLocator.deviceType  = SL_IODEVICE_AUDIOINPUT;
    micLocator.deviceID    = SL_DEFAULTDEVICEID_AUDIOINPUT;
    micLocator.device      = nullptr;

    SLDataSource inDataSource;
    inDataSource.pLocator = &micLocator;
    inDataSource.pFormat  = nullptr;

    // configure the input buffer queue
    SLDataLocator_AndroidSimpleBufferQueue inBufferQueue;
    inBufferQueue.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE;
    inBufferQueue.numBuffers  = 2; // max number of buffers in queue

    // configure the audio (data) sink for input
    SLDataSink inDataSink;
    inDataSink.pLocator = &inBufferQueue;
    inDataSink.pFormat  = &inStreamFormat;

    // create the audio recorder
    const SLInterfaceID recorderIds[] = { SL_IID_ANDROIDSIMPLEBUFFERQUEUE };
    const SLboolean recorderReq[]     = { SL_BOOLEAN_TRUE };

    (*engine)->CreateAudioRecorder ( engine,
                                     &recorderObject,
                                     &inDataSource,
                                     &inDataSink,
                                     1,
                                     recorderIds,
                                     recorderReq );

    // realize the audio recorder
    (*recorderObject)->Realize ( recorderObject,
                                 SL_BOOLEAN_FALSE );

    // get the audio recorder interface
    (*recorderObject)->GetInterface ( recorderObject,
                                      SL_IID_RECORD,
                                      &recorder );

    // get the audio recorder simple buffer queue interface
    (*recorderObject)->GetInterface ( recorderObject,
                                      SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
                                      &recorderSimpleBufQueue );

    // register the audio input callback
    (*recorderSimpleBufQueue)->RegisterCallback ( recorderSimpleBufQueue,
                                                  processInput,
                                                  this );

    // configure the output buffer queue
    SLDataLocator_AndroidSimpleBufferQueue outBufferQueue;
    outBufferQueue.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE;
    outBufferQueue.numBuffers  = 2; // max number of buffers in queue

    // configure the audio (data) source for output
    SLDataSource outDataSource;
    outDataSource.pLocator = &outBufferQueue;
    outDataSource.pFormat  = &outStreamFormat;

    // configure the output mix
    SLDataLocator_OutputMix outputMix;
    outputMix.locatorType = SL_DATALOCATOR_OUTPUTMIX;
    outputMix.outputMix   = outputMixObject;

    // configure the audio (data) sink for output
    SLDataSink outDataSink;
    outDataSink.pLocator = &outputMix;
    outDataSink.pFormat  = nullptr;

    // create the audio player
    const SLInterfaceID playerIds[] = { SL_IID_ANDROIDSIMPLEBUFFERQUEUE };
    const SLboolean playerReq[]     = { SL_BOOLEAN_TRUE };

    (*engine)->CreateAudioPlayer ( engine,
                                   &playerObject,
                                   &outDataSource,
                                   &outDataSink,
                                   1,
                                   playerIds,
                                   playerReq );

    // realize the audio player
    (*playerObject)->Realize ( playerObject,
                               SL_BOOLEAN_FALSE );

    // get the audio player interface
    (*playerObject)->GetInterface ( playerObject,
                                    SL_IID_PLAY,
                                    &player );

    // get the audio player simple buffer queue interface
    (*playerObject)->GetInterface ( playerObject,
                                    SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
                                    &playerSimpleBufQueue );

    // register the audio output callback
    (*playerSimpleBufQueue)->RegisterCallback ( playerSimpleBufQueue,
                                                processOutput,
                                                this );
}

void CSound::CloseOpenSL()
{
    // clean up
    (*recorderObject)->Destroy ( recorderObject );
    (*playerObject)->Destroy ( playerObject );
    (*outputMixObject)->Destroy ( outputMixObject );
    (*engineObject)->Destroy ( engineObject );
}

void CSound::Start()
{
    InitializeOpenSL();

// TEST We have to supply the interface with initial buffers, otherwise
// the rendering will not start.
// Note that the number of buffers enqueued here must match the maximum
// numbers of buffers configured in the constructor of this class.
vecsTmpAudioSndCrdStereo.Reset ( 0 );

    // enqueue initial buffers for record
    (*recorderSimpleBufQueue)->Enqueue ( recorderSimpleBufQueue,
                                         &vecsTmpAudioSndCrdStereo[0],
                                         iOpenSLBufferSizeStereo * 2 /* 2 bytes */ );

    (*recorderSimpleBufQueue)->Enqueue ( recorderSimpleBufQueue,
                                         &vecsTmpAudioSndCrdStereo[0],
                                         iOpenSLBufferSizeStereo * 2 /* 2 bytes */ );

    // enqueue initial buffers for playback
    (*playerSimpleBufQueue)->Enqueue ( playerSimpleBufQueue,
                                       &vecsTmpAudioSndCrdStereo[0],
                                       iOpenSLBufferSizeStereo * 2 /* 2 bytes */ );

    (*playerSimpleBufQueue)->Enqueue ( playerSimpleBufQueue,
                                       &vecsTmpAudioSndCrdStereo[0],
                                       iOpenSLBufferSizeStereo * 2 /* 2 bytes */ );

    // start the rendering
    (*recorder)->SetRecordState ( recorder, SL_RECORDSTATE_RECORDING );
    (*player)->SetPlayState ( player, SL_PLAYSTATE_PLAYING );

    // call base class
    CSoundBase::Start();
}

void CSound::Stop()
{
    // stop the audio stream
    (*recorder)->SetRecordState ( recorder, SL_RECORDSTATE_STOPPED );
    (*player)->SetPlayState ( player, SL_PLAYSTATE_STOPPED );

    // clear the buffers
    (*recorderSimpleBufQueue)->Clear ( recorderSimpleBufQueue );
    (*playerSimpleBufQueue)->Clear ( playerSimpleBufQueue );

    // call base class
    CSoundBase::Stop();

    CloseOpenSL();
}

int CSound::Init ( const int iNewPrefMonoBufferSize )
{


// TODO make use of the following:
// String sampleRate = am.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE));
// String framesPerBuffer = am.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER));
/*
// get the Audio IO DEVICE CAPABILITIES interface
SLAudioIODeviceCapabilitiesItf audioCapabilities;

(*engineObject)->GetInterface ( engineObject,
    SL_IID_AUDIOIODEVICECAPABILITIES,
    &audioCapabilities );

(*audioCapabilities)->QueryAudioInputCapabilities ( audioCapabilities,
                                                    inputDeviceIDs[i],
                                                    &audioInputDescriptor );
*/


    // store buffer size
    iOpenSLBufferSizeMono = iNewPrefMonoBufferSize;

    // init base class
    CSoundBase::Init ( iOpenSLBufferSizeMono );

    // set internal buffer size value and calculate stereo buffer size
    iOpenSLBufferSizeStereo = 2 * iOpenSLBufferSizeMono;

    // create memory for intermediate audio buffer
    vecsTmpAudioSndCrdStereo.Init ( iOpenSLBufferSizeStereo );


// TEST
#if ( SYSTEM_SAMPLE_RATE_HZ != 48000 )
# error "Only a system sample rate of 48 kHz is supported by this module"
#endif
// audio interface number of channels is 1 and the sample rate
// is 16 kHz -> just copy samples and perform no filtering as a
// first simple solution
// 48 kHz / 16 kHz = factor 3 (note that the buffer size mono might
// be divisible by three, therefore we will get a lot of drop outs)
iModifiedInBufSize = iOpenSLBufferSizeMono / 3;
vecsTmpAudioInSndCrd.Init ( iModifiedInBufSize );


    return iOpenSLBufferSizeMono;
}

void CSound::processInput ( SLAndroidSimpleBufferQueueItf bufferQueue,
                            void*                         instance )
{
    CSound* pSound = static_cast<CSound*> ( instance );

    // only process if we are running
    if ( !pSound->bRun )
    {
        return;
    }

    QMutexLocker locker ( &pSound->Mutex );

    // enqueue the buffer for record
    (*bufferQueue)->Enqueue ( bufferQueue,
                              &pSound->vecsTmpAudioInSndCrd[0],
                              pSound->iModifiedInBufSize * 2 /* 2 bytes */ );

// upsampling (without filtering) and channel management
pSound->vecsTmpAudioSndCrdStereo.Reset ( 0 );
for ( int i = 0; i < pSound->iModifiedInBufSize; i++ )
{
    pSound->vecsTmpAudioSndCrdStereo[6 * i] =
        pSound->vecsTmpAudioSndCrdStereo[6 * i + 1] =
            pSound->vecsTmpAudioInSndCrd[i];
}

}

void CSound::processOutput ( SLAndroidSimpleBufferQueueItf bufferQueue,
                             void*                         instance )
{
    CSound* pSound = static_cast<CSound*> ( instance );

    // only process if we are running
    if ( !pSound->bRun )
    {
        return;
    }

    QMutexLocker locker ( &pSound->Mutex );

    // call processing callback function
    pSound->ProcessCallback ( pSound->vecsTmpAudioSndCrdStereo );

    // enqueue the buffer for playback
    (*bufferQueue)->Enqueue ( bufferQueue,
                              &pSound->vecsTmpAudioSndCrdStereo[0],
                              pSound->iOpenSLBufferSizeStereo * 2 /* 2 bytes */ );
}