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

// Test program to record from default audio input and playback to default audio output.
// It will generate feedback (Larsen effect) if played through on-device speakers,
// or acts as a delay if played through headset.

#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
#include <assert.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <audio_utils/fifo.h>
#include <audio_utils/sndfile.h>

#define ASSERT_EQ(x, y) do { if ((x) == (y)) ; else { fprintf(stderr, "0x%x != 0x%x\n", \
    (unsigned) (x), (unsigned) (y)); assert((x) == (y)); } } while (0)

// default values
static SLuint32 rxBufCount = 2;     // -r#
static SLuint32 txBufCount = 2;     // -t#
static SLuint32 bufSizeInFrames = 240;  // -f#
static SLuint32 channels = 1;       // -c#
static SLuint32 sampleRate = 48000; // -s#
static SLuint32 exitAfterSeconds = 60; // -e#
static SLuint32 freeBufCount = 0;   // calculated
static SLuint32 bufSizeInBytes = 0; // calculated

// Storage area for the buffer queues
static char **rxBuffers;
static char **txBuffers;
static char **freeBuffers;

// Buffer indices
static SLuint32 rxFront;    // oldest recording
static SLuint32 rxRear;     // next to be recorded
static SLuint32 txFront;    // oldest playing
static SLuint32 txRear;     // next to be played
static SLuint32 freeFront;  // oldest free
static SLuint32 freeRear;   // next to be freed

static SLAndroidSimpleBufferQueueItf recorderBufferQueue;
static SLBufferQueueItf playerBufferQueue;

static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

static audio_utils_fifo fifo;

static audio_utils_fifo fifo2;
static short *fifo2Buffer = NULL;

static int injectImpulse;

// Called after audio recorder fills a buffer with data
static void recorderCallback(SLAndroidSimpleBufferQueueItf caller __unused, void *context __unused)
{
    SLresult result;

    pthread_mutex_lock(&mutex);

    // We should only be called when a recording buffer is done
    assert(rxFront <= rxBufCount);
    assert(rxRear <= rxBufCount);
    assert(rxFront != rxRear);
    char *buffer = rxBuffers[rxFront];

    // Remove buffer from record queue
    if (++rxFront > rxBufCount) {
        rxFront = 0;
    }

#if 1
    ssize_t actual = audio_utils_fifo_write(&fifo, buffer, (size_t) bufSizeInFrames);
    if (actual != (ssize_t) bufSizeInFrames) {
        write(1, "?", 1);
    }

    // This is called by a realtime (SCHED_FIFO) thread,
    // and it is unsafe to do I/O as it could block for unbounded time.
    // Flash filesystem is especially notorious for blocking.
    if (fifo2Buffer != NULL) {
        actual = audio_utils_fifo_write(&fifo2, buffer, (size_t) bufSizeInFrames);
        if (actual != (ssize_t) bufSizeInFrames) {
            write(1, "?", 1);
        }
    }

    // Enqueue this same buffer for the recorder to fill again.
    result = (*recorderBufferQueue)->Enqueue(recorderBufferQueue, buffer, bufSizeInBytes);
    ASSERT_EQ(SL_RESULT_SUCCESS, result);

    // Update our model of the record queue
    SLuint32 rxRearNext = rxRear+1;
    if (rxRearNext > rxBufCount) {
        rxRearNext = 0;
    }
    assert(rxRearNext != rxFront);
    rxBuffers[rxRear] = buffer;
    rxRear = rxRearNext;

#else
    // Enqueue the just-filled buffer for the player
    result = (*playerBufferQueue)->Enqueue(playerBufferQueue, buffer, bufSizeInBytes);
    if (SL_RESULT_SUCCESS == result) {

        // There was room in the play queue, update our model of it
        assert(txFront <= txBufCount);
        assert(txRear <= txBufCount);
        SLuint32 txRearNext = txRear+1;
        if (txRearNext > txBufCount) {
            txRearNext = 0;
        }
        assert(txRearNext != txFront);
        txBuffers[txRear] = buffer;
        txRear = txRearNext;

    } else {

        // Here if record has a filled buffer to play, but play queue is full.
        assert(SL_RESULT_BUFFER_INSUFFICIENT == result);
        write(1, "?", 1);

        // We could either try again later, or discard. For now we discard and re-use buffer.
        // Enqueue this same buffer for the recorder to fill again.
        result = (*recorderBufferQueue)->Enqueue(recorderBufferQueue, buffer, bufSizeInBytes);
        ASSERT_EQ(SL_RESULT_SUCCESS, result);

        // Update our model of the record queue
        SLuint32 rxRearNext = rxRear+1;
        if (rxRearNext > rxBufCount) {
            rxRearNext = 0;
        }
        assert(rxRearNext != rxFront);
        rxBuffers[rxRear] = buffer;
        rxRear = rxRearNext;

    }
#endif

    pthread_mutex_unlock(&mutex);
}


// Called after audio player empties a buffer of data
static void playerCallback(SLBufferQueueItf caller __unused, void *context __unused)
{
    SLresult result;

    pthread_mutex_lock(&mutex);

    // Get the buffer that just finished playing
    assert(txFront <= txBufCount);
    assert(txRear <= txBufCount);
    assert(txFront != txRear);
    char *buffer = txBuffers[txFront];
    if (++txFront > txBufCount) {
        txFront = 0;
    }

#if 1
    ssize_t actual = audio_utils_fifo_read(&fifo, buffer, bufSizeInFrames);
    if (actual != (ssize_t) bufSizeInFrames) {
        write(1, "/", 1);
        // on underrun from pipe, substitute silence
        memset(buffer, 0, bufSizeInFrames * channels * sizeof(short));
    }

    if (injectImpulse == -1) {
        // Experimentally, a single frame impulse was insufficient to trigger feedback.
        // Also a Nyquist frequency signal was also insufficient, probably because
        // the response of output and/or input path was not adequate at high frequencies.
        // This short burst of a few cycles of square wave at Nyquist/4 was found to work well.
        for (unsigned i = 0; i < bufSizeInFrames / 8; i += 8) {
            for (int j = 0; j < 8; j++) {
                for (unsigned k = 0; k < channels; k++) {
                    ((short *)buffer)[(i+j)*channels+k] = j < 4 ? 0x7FFF : 0x8000;
                }
            }
        }
        injectImpulse = 0;
    }

    // Enqueue the filled buffer for playing
    result = (*playerBufferQueue)->Enqueue(playerBufferQueue, buffer, bufSizeInBytes);
    ASSERT_EQ(SL_RESULT_SUCCESS, result);

    // Update our model of the player queue
    assert(txFront <= txBufCount);
    assert(txRear <= txBufCount);
    SLuint32 txRearNext = txRear+1;
    if (txRearNext > txBufCount) {
        txRearNext = 0;
    }
    assert(txRearNext != txFront);
    txBuffers[txRear] = buffer;
    txRear = txRearNext;

#else
    // First try to enqueue the free buffer for recording
    result = (*recorderBufferQueue)->Enqueue(recorderBufferQueue, buffer, bufSizeInBytes);
    if (SL_RESULT_SUCCESS == result) {

        // There was room in the record queue, update our model of it
        assert(rxFront <= rxBufCount);
        assert(rxRear <= rxBufCount);
        SLuint32 rxRearNext = rxRear+1;
        if (rxRearNext > rxBufCount) {
            rxRearNext = 0;
        }
        assert(rxRearNext != rxFront);
        rxBuffers[rxRear] = buffer;
        rxRear = rxRearNext;

    } else {

        // Here if record queue is full
        assert(SL_RESULT_BUFFER_INSUFFICIENT == result);

        // Instead enqueue the free buffer on the free queue
        assert(freeFront <= freeBufCount);
        assert(freeRear <= freeBufCount);
        SLuint32 freeRearNext = freeRear+1;
        if (freeRearNext > freeBufCount) {
            freeRearNext = 0;
        }
        // There must always be room in the free queue
        assert(freeRearNext != freeFront);
        freeBuffers[freeRear] = buffer;
        freeRear = freeRearNext;

    }
#endif

    pthread_mutex_unlock(&mutex);
}

// Main program
int main(int argc, char **argv)
{
    const char *outFileName = NULL;
    // process command-line options
    int i;
    for (i = 1; i < argc; ++i) {
        char *arg = argv[i];
        if (arg[0] != '-') {
            break;
        }
        // -r# number of slots in receive buffer queue
        if (!strncmp(arg, "-r", 2)) {
            rxBufCount = atoi(&arg[2]);
            if (rxBufCount < 1 || rxBufCount > 16) {
                fprintf(stderr, "%s: unusual receive buffer queue size (%u buffers)\n", argv[0],
                    (unsigned) rxBufCount);
            }
        // -t# number of slots in transmit buffer queue
        } else if (!strncmp(arg, "-t", 2)) {
            txBufCount = atoi(&arg[2]);
            if (txBufCount < 1 || txBufCount > 16) {
                fprintf(stderr, "%s: unusual transmit buffer queue size (%u buffers)\n", argv[0],
                    (unsigned) txBufCount);
            }
        // -f# size of each buffer in frames
        } else if (!strncmp(arg, "-f", 2)) {
            bufSizeInFrames = atoi(&arg[2]);
            if (bufSizeInFrames == 0) {
                fprintf(stderr, "%s: unusual buffer size (%u frames)\n", argv[0],
                    (unsigned) bufSizeInFrames);
            }
        // -c1 mono or -c2 stereo
        } else if (!strncmp(arg, "-c", 2)) {
            channels = atoi(&arg[2]);
            if (channels < 1 || channels > 2) {
                fprintf(stderr, "%s: unusual channel count ignored (%u)\n", argv[0],
                    (unsigned) channels);
                channels = 2;
            }
        // -s# sample rate in Hz
        } else if (!strncmp(arg, "-s", 2)) {
            sampleRate = atoi(&arg[2]);
            switch (sampleRate) {
            case 8000:
            case 11025:
            case 12000:
            case 16000:
            case 22050:
            case 24000:
            case 32000:
            case 44100:
            case 48000:
                break;
            default:
                fprintf(stderr, "%s: unusual sample rate (%u Hz)\n", argv[0],
                    (unsigned) sampleRate);
                break;
            }
        // -e# exit after this many seconds
        } else if (!strncmp(arg, "-e", 2)) {
            exitAfterSeconds = atoi(&arg[2]);
        // -ofile log to output file also
        } else if (!strncmp(arg, "-o", 2)) {
            outFileName = &arg[2];
        // -i# inject an impulse after # milliseconds
        } else if (!strncmp(arg, "-i", 2)) {
            injectImpulse = atoi(&arg[2]);
        } else
            fprintf(stderr, "%s: unknown option %s\n", argv[0], arg);
    }
    // no other arguments allowed
    if (i < argc) {
        fprintf(stderr, "usage: %s -r# -t# -f# -s# -c# -i# -ofile\n", argv[0]);
        fprintf(stderr, "  -r# receive buffer queue count for microphone input, default 1\n");
        fprintf(stderr, "  -t# transmit buffer queue count for speaker output, default 2\n");
        fprintf(stderr, "  -f# number of frames per buffer, default 512\n");
        fprintf(stderr, "  -s# sample rate in Hz, default 44100\n");
        fprintf(stderr, "  -c1 mono\n");
        fprintf(stderr, "  -c2 stereo, default\n");
        fprintf(stderr, "  -i# inject impulse after # milliseconds\n");
        fprintf(stderr, "  -ofile log input to specified .wav file also\n");
    }

    // compute total free buffers as -r plus -t
    freeBufCount = rxBufCount + txBufCount;
    // compute buffer size
    bufSizeInBytes = channels * bufSizeInFrames * sizeof(short);

    // Initialize free buffers
    freeBuffers = (char **) calloc(freeBufCount+1, sizeof(char *));
    unsigned j;
    for (j = 0; j < freeBufCount; ++j) {
        freeBuffers[j] = (char *) malloc(bufSizeInBytes);
    }
    freeFront = 0;
    freeRear = freeBufCount;
    freeBuffers[j] = NULL;

    // Initialize record queue
    rxBuffers = (char **) calloc(rxBufCount+1, sizeof(char *));
    rxFront = 0;
    rxRear = 0;

    // Initialize play queue
    txBuffers = (char **) calloc(txBufCount+1, sizeof(char *));
    txFront = 0;
    txRear = 0;

    size_t frameSize = channels * sizeof(short);
#define FIFO_FRAMES 1024
    short *fifoBuffer = new short[FIFO_FRAMES * channels];
    audio_utils_fifo_init(&fifo, FIFO_FRAMES, frameSize, fifoBuffer);

    SNDFILE *sndfile;
    if (outFileName != NULL) {
        // create .wav writer
        SF_INFO info;
        info.frames = 0;
        info.samplerate = sampleRate;
        info.channels = channels;
        info.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
        sndfile = sf_open(outFileName, SFM_WRITE, &info);
        if (sndfile != NULL) {
#define FIFO2_FRAMES 65536
            fifo2Buffer = new short[FIFO2_FRAMES * channels];
            audio_utils_fifo_init(&fifo2, FIFO2_FRAMES, frameSize, fifo2Buffer);
        } else {
            fprintf(stderr, "sf_open failed\n");
        }
    } else {
        sndfile = NULL;
    }

    SLresult result;

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

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

    // create an audio player with buffer queue source and output mix sink
    SLDataSource audiosrc;
    SLDataSink audiosnk;
    SLDataFormat_PCM pcm;
    SLDataLocator_OutputMix locator_outputmix;
    SLDataLocator_BufferQueue locator_bufferqueue_tx;
    locator_bufferqueue_tx.locatorType = SL_DATALOCATOR_BUFFERQUEUE;
    locator_bufferqueue_tx.numBuffers = txBufCount;
    locator_outputmix.locatorType = SL_DATALOCATOR_OUTPUTMIX;
    locator_outputmix.outputMix = outputmixObject;
    pcm.formatType = SL_DATAFORMAT_PCM;
    pcm.numChannels = channels;
    pcm.samplesPerSec = sampleRate * 1000;
    pcm.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
    pcm.containerSize = 16;
    pcm.channelMask = channels == 1 ? SL_SPEAKER_FRONT_CENTER :
        (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT);
    pcm.endianness = SL_BYTEORDER_LITTLEENDIAN;
    audiosrc.pLocator = &locator_bufferqueue_tx;
    audiosrc.pFormat = &pcm;
    audiosnk.pLocator = &locator_outputmix;
    audiosnk.pFormat = NULL;
    SLObjectItf playerObject = NULL;
    SLObjectItf recorderObject = NULL;
    SLInterfaceID ids_tx[1] = {SL_IID_BUFFERQUEUE};
    SLboolean flags_tx[1] = {SL_BOOLEAN_TRUE};
    result = (*engineEngine)->CreateAudioPlayer(engineEngine, &playerObject, &audiosrc, &audiosnk,
        1, ids_tx, flags_tx);
    if (SL_RESULT_CONTENT_UNSUPPORTED == result) {
        fprintf(stderr, "Could not create audio player (result %x), check sample rate\n", result);
        goto cleanup;
    }
    ASSERT_EQ(SL_RESULT_SUCCESS, result);
    result = (*playerObject)->Realize(playerObject, SL_BOOLEAN_FALSE);
    ASSERT_EQ(SL_RESULT_SUCCESS, result);
    SLPlayItf playerPlay;
    result = (*playerObject)->GetInterface(playerObject, SL_IID_PLAY, &playerPlay);
    ASSERT_EQ(SL_RESULT_SUCCESS, result);
    result = (*playerObject)->GetInterface(playerObject, SL_IID_BUFFERQUEUE, &playerBufferQueue);
    ASSERT_EQ(SL_RESULT_SUCCESS, result);
    result = (*playerBufferQueue)->RegisterCallback(playerBufferQueue, playerCallback, NULL);
    ASSERT_EQ(SL_RESULT_SUCCESS, result);

    // Enqueue some zero buffers for the player
    for (j = 0; j < txBufCount; ++j) {

        // allocate a free buffer
        assert(freeFront != freeRear);
        char *buffer = freeBuffers[freeFront];
        if (++freeFront > freeBufCount) {
            freeFront = 0;
        }

        // put on play queue
        SLuint32 txRearNext = txRear + 1;
        if (txRearNext > txBufCount) {
            txRearNext = 0;
        }
        assert(txRearNext != txFront);
        txBuffers[txRear] = buffer;
        txRear = txRearNext;
        result = (*playerBufferQueue)->Enqueue(playerBufferQueue,
            buffer, bufSizeInBytes);
        ASSERT_EQ(SL_RESULT_SUCCESS, result);
    }

    result = (*playerPlay)->SetPlayState(playerPlay, SL_PLAYSTATE_PLAYING);
    ASSERT_EQ(SL_RESULT_SUCCESS, result);

    // Create an audio recorder with microphone device source and buffer queue sink.
    // The buffer queue as sink is an Android-specific extension.

    SLDataLocator_IODevice locator_iodevice;
    SLDataLocator_AndroidSimpleBufferQueue locator_bufferqueue_rx;
    locator_iodevice.locatorType = SL_DATALOCATOR_IODEVICE;
    locator_iodevice.deviceType = SL_IODEVICE_AUDIOINPUT;
    locator_iodevice.deviceID = SL_DEFAULTDEVICEID_AUDIOINPUT;
    locator_iodevice.device = NULL;
    audiosrc.pLocator = &locator_iodevice;
    audiosrc.pFormat = NULL;
    locator_bufferqueue_rx.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE;
    locator_bufferqueue_rx.numBuffers = rxBufCount;
    audiosnk.pLocator = &locator_bufferqueue_rx;
    audiosnk.pFormat = &pcm;
    {
    SLInterfaceID ids_rx[1] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE};
    SLboolean flags_rx[1] = {SL_BOOLEAN_TRUE};
    result = (*engineEngine)->CreateAudioRecorder(engineEngine, &recorderObject, &audiosrc,
        &audiosnk, 1, ids_rx, flags_rx);
    if (SL_RESULT_SUCCESS != result) {
        fprintf(stderr, "Could not create audio recorder (result %x), "
                "check sample rate and channel count\n", result);
        goto cleanup;
    }
    }
    ASSERT_EQ(SL_RESULT_SUCCESS, result);
    result = (*recorderObject)->Realize(recorderObject, SL_BOOLEAN_FALSE);
    ASSERT_EQ(SL_RESULT_SUCCESS, result);
    SLRecordItf recorderRecord;
    result = (*recorderObject)->GetInterface(recorderObject, SL_IID_RECORD, &recorderRecord);
    ASSERT_EQ(SL_RESULT_SUCCESS, result);
    result = (*recorderObject)->GetInterface(recorderObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
        &recorderBufferQueue);
    ASSERT_EQ(SL_RESULT_SUCCESS, result);
    result = (*recorderBufferQueue)->RegisterCallback(recorderBufferQueue, recorderCallback, NULL);
    ASSERT_EQ(SL_RESULT_SUCCESS, result);

    // Enqueue some empty buffers for the recorder
    for (j = 0; j < rxBufCount; ++j) {

        // allocate a free buffer
        assert(freeFront != freeRear);
        char *buffer = freeBuffers[freeFront];
        if (++freeFront > freeBufCount) {
            freeFront = 0;
        }

        // put on record queue
        SLuint32 rxRearNext = rxRear + 1;
        if (rxRearNext > rxBufCount) {
            rxRearNext = 0;
        }
        assert(rxRearNext != rxFront);
        rxBuffers[rxRear] = buffer;
        rxRear = rxRearNext;
        result = (*recorderBufferQueue)->Enqueue(recorderBufferQueue,
            buffer, bufSizeInBytes);
        ASSERT_EQ(SL_RESULT_SUCCESS, result);
    }

    // Kick off the recorder
    result = (*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_RECORDING);
    ASSERT_EQ(SL_RESULT_SUCCESS, result);

#if 0
    // give recorder a head start so that the pipe is initially filled
    sleep(1);
#endif

    // Wait patiently
    do {
        for (int i = 0; i < 10; i++) {
            usleep(100000);
            if (fifo2Buffer != NULL) {
                for (;;) {
                    short buffer[bufSizeInFrames * channels];
                    ssize_t actual = audio_utils_fifo_read(&fifo2, buffer, bufSizeInFrames);
                    if (actual <= 0)
                        break;
                    (void) sf_writef_short(sndfile, buffer, (sf_count_t) actual);
                }
            }
            if (injectImpulse > 0) {
                if (injectImpulse <= 100) {
                    injectImpulse = -1;
                    write(1, "I", 1);
                } else {
                    if ((injectImpulse % 1000) < 100) {
                        write(1, "i", 1);
                    }
                    injectImpulse -= 100;
                }
            } else if (i == 9) {
                write(1, ".", 1);
            }
        }
        SLBufferQueueState playerBQState;
        result = (*playerBufferQueue)->GetState(playerBufferQueue, &playerBQState);
        ASSERT_EQ(SL_RESULT_SUCCESS, result);
        SLAndroidSimpleBufferQueueState recorderBQState;
        result = (*recorderBufferQueue)->GetState(recorderBufferQueue, &recorderBQState);
        ASSERT_EQ(SL_RESULT_SUCCESS, result);
    } while (--exitAfterSeconds);

    // Tear down the objects and exit
cleanup:
    audio_utils_fifo_deinit(&fifo);
    delete[] fifoBuffer;

    if (sndfile != NULL) {
        audio_utils_fifo_deinit(&fifo2);
        delete[] fifo2Buffer;
        sf_close(sndfile);
    }
    if (NULL != playerObject) {
        (*playerObject)->Destroy(playerObject);
    }
    if (NULL != recorderObject) {
        (*recorderObject)->Destroy(recorderObject);
    }
    (*outputmixObject)->Destroy(outputmixObject);
    (*engineObject)->Destroy(engineObject);

    return EXIT_SUCCESS;
}