/*
 * 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.
 */

/* interactive buffer queue test program */

#ifdef ANDROID
#define USE_ANDROID_SIMPLE_BUFFER_QUEUE     // change to #undef for compatibility testing
#endif

#include <assert.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "SLES/OpenSLES.h"
#ifdef USE_ANDROID_SIMPLE_BUFFER_QUEUE
#include "SLES/OpenSLES_Android.h"
#endif

#ifdef USE_ANDROID_SIMPLE_BUFFER_QUEUE
#define DATALOCATOR_BUFFERQUEUE SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE
#define IID_BUFFERQUEUE SL_IID_ANDROIDSIMPLEBUFFERQUEUE
#define BufferQueueItf SLAndroidSimpleBufferQueueItf
#define BufferQueueState SLAndroidSimpleBufferQueueState
#define INDEX index
#else
#define DATALOCATOR_BUFFERQUEUE SL_DATALOCATOR_BUFFERQUEUE
#define IID_BUFFERQUEUE SL_IID_BUFFERQUEUE
#define BufferQueueItf SLBufferQueueItf
#define BufferQueueState SLBufferQueueState
#define INDEX playIndex
#endif

#define checkResult(r) do { if ((r) != SL_RESULT_SUCCESS) fprintf(stderr, "error %d at %s:%d\n", \
    (int) r, __FILE__, __LINE__); } while (0)

typedef struct {
    short left;
    short right;
} frame_t;

#define SINE_FRAMES (44100*5)
frame_t sine[SINE_FRAMES];

#define SQUARE_FRAMES (44100*5)
frame_t square[SQUARE_FRAMES];

#define SAWTOOTH_FRAMES (44100*5)
frame_t sawtooth[SAWTOOTH_FRAMES];

#define HALF_FRAMES (44100*5)
frame_t half[HALF_FRAMES];

BufferQueueItf expectedCaller = NULL;
void *expectedContext = NULL;

static void callback(BufferQueueItf caller, void *context)
{
    putchar('.');
    if (caller != expectedCaller)
        printf("caller %p expected %p\r\n", caller, expectedCaller);
    if (context != expectedContext)
        printf("context %p expected %p\r\n", context, expectedContext);
    fflush(stdout);
}

int main(int argc, char **argv)
{
    SLresult result;

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

    // create output mix
    SLObjectItf outputmixObject;
    result = (*engineEngine)->CreateOutputMix(engineEngine, &outputmixObject, 0, NULL, NULL);
    checkResult(result);
    result = (*outputmixObject)->Realize(outputmixObject, SL_BOOLEAN_FALSE);
    checkResult(result);

    // create audio player
    SLDataSource audiosrc;
    SLDataSink audiosnk;
    SLDataFormat_PCM pcm;
    SLDataLocator_OutputMix locator_outputmix;
    SLDataLocator_BufferQueue locator_bufferqueue;
    locator_bufferqueue.locatorType = DATALOCATOR_BUFFERQUEUE;
    locator_bufferqueue.numBuffers = 255;
    locator_outputmix.locatorType = SL_DATALOCATOR_OUTPUTMIX;
    locator_outputmix.outputMix = outputmixObject;
    pcm.formatType = SL_DATAFORMAT_PCM;
    pcm.numChannels = 2;
    pcm.samplesPerSec = SL_SAMPLINGRATE_44_1;
    pcm.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
    pcm.containerSize = 16;
    pcm.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
    pcm.endianness = SL_BYTEORDER_LITTLEENDIAN;
    audiosrc.pLocator = &locator_bufferqueue;
    audiosrc.pFormat = &pcm;
    audiosnk.pLocator = &locator_outputmix;
    audiosnk.pFormat = NULL;
    SLObjectItf playerObject;
    SLInterfaceID ids[2] = {IID_BUFFERQUEUE, SL_IID_MUTESOLO};
    SLboolean flags[2] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
    result = (*engineEngine)->CreateAudioPlayer(engineEngine, &playerObject, &audiosrc, &audiosnk,
            2, ids, flags);
    checkResult(result);
    result = (*playerObject)->Realize(playerObject, SL_BOOLEAN_FALSE);
    checkResult(result);
    SLPlayItf playerPlay;
    result = (*playerObject)->GetInterface(playerObject, SL_IID_PLAY, &playerPlay);
    checkResult(result);
    BufferQueueItf playerBufferqueue;
    result = (*playerObject)->GetInterface(playerObject, IID_BUFFERQUEUE, &playerBufferqueue);
    checkResult(result);
    SLMuteSoloItf playerMuteSolo;
    result = (*playerObject)->GetInterface(playerObject, SL_IID_MUTESOLO, &playerMuteSolo);
    checkResult(result);
    SLuint8 numChannels = 123;
    result = (*playerMuteSolo)->GetNumChannels(playerMuteSolo, &numChannels);
    assert(2 == numChannels);
    SLuint32 state;
    state = SL_PLAYSTATE_PLAYING;
    result = (*playerPlay)->SetPlayState(playerPlay, state);
    checkResult(result);

    unsigned i;
    float pi2 = 3.14*2;
    float hz = 441;
    float sr = 44100;
    for (i = 0; i < SINE_FRAMES; ++i) {
        sine[i].left = sin((float) (i  / (sr / hz)) * pi2 ) * 32000.0;
        sine[i].right = sine[i].left;
    }
    for (i = 0; i < SQUARE_FRAMES; ++i) {
        square[i].left = (i % (unsigned) (sr / hz)) < 50 ? 32767 : -32768;
        square[i].right = square[i].left;
    }
    for (i = 0; i < SAWTOOTH_FRAMES; ++i) {
        sawtooth[i].left = ((((int) (i % (unsigned) (sr / hz))) - 50) / 100.0) * 60000.0 - 30000.0;
        sawtooth[i].right = sawtooth[i].left;
    }
    for (i = 0; i < HALF_FRAMES; ++i) {
        half[i].left = sine[i].left;
        half[i].right = sawtooth[i].right / 2;
    }

    set_conio_terminal_mode();
    int in_count = 0;
    unsigned count = 0;
    for (;;) {
        usleep(10000);
        if (kbhit()) {
            frame_t *buffer;
            unsigned size;
            BufferQueueState bufqstate;
            int ch = getch();
            switch (ch) {
            case '0' ... '9':
                if (in_count) {
                    count = count * 10 + (ch - '0');
                } else {
                    count = ch - '0';
                    in_count = 1;
                }
                continue;
            case 'i':
                buffer = sine;
                size = sizeof(sine);
                goto enqueue;
            case 'q':
                buffer = square;
                size = sizeof(square);
                goto enqueue;
            case 'h':
                buffer = half;
                size = sizeof(half);
                goto enqueue;
            case 'r':
                if (in_count) {
                    expectedCaller = playerBufferqueue;
                    expectedContext = (void *) count;
                } else {
                    expectedCaller = NULL;
                    expectedContext = (void *) NULL;
                }
                result = (*playerBufferqueue)->RegisterCallback(playerBufferqueue, in_count ?
                    callback : NULL, expectedContext);
                checkResult(result);
                break;
            case 'a':
                buffer = sawtooth;
                size = sizeof(sawtooth);
enqueue:
                for (i = 0; i < (in_count ? count : 1); ++i) {
                    result = (*playerBufferqueue)->Enqueue(playerBufferqueue, buffer, size);
                    checkResult(result);
                }
                break;
            case 'c':
                result = (*playerBufferqueue)->Clear(playerBufferqueue);
                checkResult(result);
                putchar('\r');
                result = (*playerBufferqueue)->GetState(playerBufferqueue, &bufqstate);
                checkResult(result);
                if (bufqstate.count != 0)
                    printf("\rcount=%u\r\n", (unsigned) bufqstate.count);
#if 0
                putchar('\r');
                putchar('\n');
#endif
                fflush(stdout);
                break;
            case 'g':
                result = (*playerBufferqueue)->GetState(playerBufferqueue, &bufqstate);
                checkResult(result);
                printf("\rplayIndex=%u\r\n", (unsigned) bufqstate.INDEX);
                printf("count=%u\r\n", (unsigned) bufqstate.count);
                break;
            case 'p':
                state = SL_PLAYSTATE_PAUSED;
                goto setplaystate;
            case 's':
                state = SL_PLAYSTATE_STOPPED;
                goto setplaystate;
            case 'P':
                state = SL_PLAYSTATE_PLAYING;
setplaystate:
                result = (*playerPlay)->SetPlayState(playerPlay, state);
                checkResult(result);
                SLuint32 newstate;
                result = (*playerPlay)->GetPlayState(playerPlay, &newstate);
                checkResult(result);
                if (newstate != state)
                    printf("\rSetPlayState(%u) -> GetPlayState(%u)\r\n", (unsigned) state,
                        (unsigned) newstate);
#if 0
                putchar('\r');
                putchar('\n');
                fflush(stdout);
#endif
                checkResult(result);
                break;
            case 'x':
                goto out;
            default:
                putchar('?');
                fflush(stdout);
                break;
            }
            in_count = 0;
        }
    }

out:
    (*playerObject)->Destroy(playerObject);
    (*outputmixObject)->Destroy(outputmixObject);
    (*engineObject)->Destroy(engineObject);
    return EXIT_SUCCESS;
}
