/* * 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 #include #include #include #include #include #include #include #include #include #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_reader *fifoReader; static audio_utils_fifo_writer *fifoWriter; static audio_utils_fifo *fifo2; static short *fifo2Buffer = NULL; static audio_utils_fifo_reader *fifo2Reader; static audio_utils_fifo_writer *fifo2Writer; 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 = fifoWriter->write(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 = fifo2Writer->write(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 = fifoReader->read(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]; fifo = new audio_utils_fifo(FIFO_FRAMES, frameSize, fifoBuffer); fifoReader = new audio_utils_fifo_reader(*fifo, true /*throttlesWriter*/); fifoWriter = new audio_utils_fifo_writer(*fifo); 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]; fifo2 = new audio_utils_fifo(FIFO2_FRAMES, frameSize, fifo2Buffer); fifo2Reader = new audio_utils_fifo_reader(*fifo2, true /*throttlesWriter*/); fifo2Writer = new audio_utils_fifo_writer(*fifo2); } 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 = fifo2Reader->read(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: delete fifoWriter; fifoWriter = NULL; delete fifoReader; fifoReader = NULL; delete fifo; fifo = NULL; delete[] fifoBuffer; fifoBuffer = NULL; if (sndfile != NULL) { delete fifo2Writer; fifo2Writer = NULL; delete fifo2Reader; fifo2Reader = NULL; delete fifo2; fifo2 = NULL; delete[] fifo2Buffer; fifo2Buffer = NULL; sf_close(sndfile); sndfile = NULL; } if (NULL != playerObject) { (*playerObject)->Destroy(playerObject); } if (NULL != recorderObject) { (*recorderObject)->Destroy(recorderObject); } (*outputmixObject)->Destroy(outputmixObject); (*engineObject)->Destroy(engineObject); return EXIT_SUCCESS; }