/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// Play an audio file using buffer queue

#include <assert.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <SLES/OpenSLES.h>
#ifdef ANDROID
#include "sndfile.h"
#else
#include <sndfile.h>
#endif

unsigned numBuffers = 2;
int framesPerBuffer = 512;
SNDFILE *sndfile;
SF_INFO sfinfo;
unsigned which; // which buffer to use next
SLboolean eof;  // whether we have hit EOF on input yet
short *buffers;

// This callback is called each time a buffer finishes playing

static void callback(SLBufferQueueItf bufq, void *param)
{
    if (!eof) {
        short *buffer = &buffers[framesPerBuffer * sfinfo.channels * which];
        sf_count_t count;
        count = sf_readf_short(sndfile, buffer, (sf_count_t) framesPerBuffer);
        if (0 >= count) {
            eof = SL_BOOLEAN_TRUE;
        } else {
            SLresult result = (*bufq)->Enqueue(bufq, buffer, count * sfinfo.channels *
                    sizeof(short));
            assert(SL_RESULT_SUCCESS == result);
            if (++which >= numBuffers)
                which = 0;
        }
    }
}

int main(int argc, char **argv)
{
    SLboolean enableReverb = SL_BOOLEAN_FALSE;

    // process command-line options
    int i;
    for (i = 1; i < argc; ++i) {
        char *arg = argv[i];
        if (arg[0] != '-')
            break;
        if (!strncmp(arg, "-f", 2)) {
            framesPerBuffer = atoi(&arg[2]);
        } else if (!strncmp(arg, "-n", 2)) {
            numBuffers = atoi(&arg[2]);
        } else if (!strcmp(arg, "-r")) {
            enableReverb = SL_BOOLEAN_TRUE;
        } else {
            fprintf(stderr, "option %s ignored\n", arg);
        }
    }

    if (argc - i != 1) {
        fprintf(stderr, "usage: [-r] %s filename\n", argv[0]);
        return EXIT_FAILURE;
    }

    const char *filename = argv[i];
    //memset(&sfinfo, 0, sizeof(SF_INFO));
    sfinfo.format = 0;
    sndfile = sf_open(filename, SFM_READ, &sfinfo);
    if (NULL == sndfile) {
        perror(filename);
        return EXIT_FAILURE;
    }

    // verify the file format
    switch (sfinfo.channels) {
    case 1:
    case 2:
        break;
    default:
        fprintf(stderr, "unsupported channel count %d\n", sfinfo.channels);
        break;
    }
    switch (sfinfo.samplerate) {
    case  8000:
    case 11025:
    case 12000:
    case 16000:
    case 22050:
    case 24000:
    case 32000:
    case 44100:
    case 48000:
        break;
    default:
        fprintf(stderr, "unsupported sample rate %d\n", sfinfo.samplerate);
        break;
    }
    switch (sfinfo.format & SF_FORMAT_TYPEMASK) {
    case SF_FORMAT_WAV:
        break;
    default:
        fprintf(stderr, "unsupported format type 0x%x\n", sfinfo.format & SF_FORMAT_TYPEMASK);
        break;
    }
    switch (sfinfo.format & SF_FORMAT_SUBMASK) {
    case SF_FORMAT_PCM_16:
    case SF_FORMAT_PCM_U8:
    case SF_FORMAT_ULAW:
    case SF_FORMAT_ALAW:
    case SF_FORMAT_IMA_ADPCM:
        break;
    default:
        fprintf(stderr, "unsupported sub-format 0x%x\n", sfinfo.format & SF_FORMAT_SUBMASK);
        break;
    }

    buffers = (short *) malloc(framesPerBuffer * sfinfo.channels * sizeof(short) * numBuffers);

    // create engine
    SLresult result;
    SLObjectItf engineObject;
    result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
    assert(SL_RESULT_SUCCESS == result);
    SLEngineItf engineEngine;
    result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
    assert(SL_RESULT_SUCCESS == result);
    result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);
    assert(SL_RESULT_SUCCESS == result);

    // create output mix
    SLObjectItf outputMixObject;
    SLInterfaceID ids[1] = {SL_IID_ENVIRONMENTALREVERB};
    SLboolean req[1] = {SL_BOOLEAN_TRUE};
    result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, enableReverb ? 1 : 0,
            ids, req);
    assert(SL_RESULT_SUCCESS == result);
    result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
    assert(SL_RESULT_SUCCESS == result);

    // configure environmental reverb on output mix
    SLEnvironmentalReverbItf mixEnvironmentalReverb = NULL;
    if (enableReverb) {
        result = (*outputMixObject)->GetInterface(outputMixObject, SL_IID_ENVIRONMENTALREVERB,
                &mixEnvironmentalReverb);
        assert(SL_RESULT_SUCCESS == result);
        SLEnvironmentalReverbSettings settings = SL_I3DL2_ENVIRONMENT_PRESET_STONECORRIDOR;
        result = (*mixEnvironmentalReverb)->SetEnvironmentalReverbProperties(mixEnvironmentalReverb,
                &settings);
        assert(SL_RESULT_SUCCESS == result);
    }

    // configure audio source
    SLDataLocator_BufferQueue loc_bufq;
    loc_bufq.locatorType = SL_DATALOCATOR_BUFFERQUEUE;
    loc_bufq.numBuffers = numBuffers;
    SLDataFormat_PCM format_pcm;
    format_pcm.formatType = SL_DATAFORMAT_PCM;
    format_pcm.numChannels = sfinfo.channels;
    format_pcm.samplesPerSec = sfinfo.samplerate * 1000;
    format_pcm.bitsPerSample = 16;
    format_pcm.containerSize = format_pcm.bitsPerSample;
    format_pcm.channelMask = 1 == format_pcm.numChannels ? SL_SPEAKER_FRONT_CENTER :
            SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
    format_pcm.endianness = SL_BYTEORDER_LITTLEENDIAN;
    SLDataSource audioSrc;
    audioSrc.pLocator = &loc_bufq;
    audioSrc.pFormat = &format_pcm;

    // configure audio sink
    SLDataLocator_OutputMix loc_outmix;
    loc_outmix.locatorType = SL_DATALOCATOR_OUTPUTMIX;
    loc_outmix.outputMix = outputMixObject;
    SLDataSink audioSnk;
    audioSnk.pLocator = &loc_outmix;
    audioSnk.pFormat = NULL;

    // create audio player
    SLInterfaceID ids2[2] = {SL_IID_BUFFERQUEUE, SL_IID_EFFECTSEND};
    SLboolean req2[2] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
    SLObjectItf playerObject;
    result = (*engineEngine)->CreateAudioPlayer(engineEngine, &playerObject, &audioSrc,
            &audioSnk, enableReverb ? 2 : 1, ids2, req2);
    assert(SL_RESULT_SUCCESS == result);

    // realize the player
    result = (*playerObject)->Realize(playerObject, SL_BOOLEAN_FALSE);
    assert(SL_RESULT_SUCCESS == result);

    // get the effect send interface and enable effect send reverb for this player
    if (enableReverb) {
        SLEffectSendItf playerEffectSend;
        result = (*playerObject)->GetInterface(playerObject, SL_IID_EFFECTSEND, &playerEffectSend);
        assert(SL_RESULT_SUCCESS == result);
        result = (*playerEffectSend)->EnableEffectSend(playerEffectSend, mixEnvironmentalReverb,
                SL_BOOLEAN_TRUE, (SLmillibel) 0);
        assert(SL_RESULT_SUCCESS == result);
    }

    // get the play interface
    SLPlayItf playerPlay;
    result = (*playerObject)->GetInterface(playerObject, SL_IID_PLAY, &playerPlay);
    assert(SL_RESULT_SUCCESS == result);

    // get the buffer queue interface
    SLBufferQueueItf playerBufferQueue;
    result = (*playerObject)->GetInterface(playerObject, SL_IID_BUFFERQUEUE,
            &playerBufferQueue);
    assert(SL_RESULT_SUCCESS == result);

    // loop until EOF or no more buffers
    for (which = 0; which < numBuffers; ++which) {
        short *buffer = &buffers[framesPerBuffer * sfinfo.channels * which];
        sf_count_t frames = framesPerBuffer;
        sf_count_t count;
        count = sf_readf_short(sndfile, buffer, frames);
        if (0 >= count) {
            eof = SL_BOOLEAN_TRUE;
            break;
        }

        // enqueue a buffer
        result = (*playerBufferQueue)->Enqueue(playerBufferQueue, buffer, count * sfinfo.channels *
                sizeof(short));
        assert(SL_RESULT_SUCCESS == result);
    }
    if (which >= numBuffers)
        which = 0;

    // register a callback on the buffer queue
    result = (*playerBufferQueue)->RegisterCallback(playerBufferQueue, callback, NULL);
    assert(SL_RESULT_SUCCESS == result);

    // set the player's state to playing
    result = (*playerPlay)->SetPlayState(playerPlay, SL_PLAYSTATE_PLAYING);
    assert(SL_RESULT_SUCCESS == result);

    // wait until the buffer queue is empty
    SLBufferQueueState bufqstate;
    for (;;) {
        result = (*playerBufferQueue)->GetState(playerBufferQueue, &bufqstate);
        assert(SL_RESULT_SUCCESS == result);
        if (0 >= bufqstate.count) {
            break;
        }
        sleep(1);
    }

    // destroy audio player
    (*playerObject)->Destroy(playerObject);

    // destroy output mix
    (*outputMixObject)->Destroy(outputMixObject);

    // destroy engine
    (*engineObject)->Destroy(engineObject);

    return EXIT_SUCCESS;
}
