| /* |
| * Copyright (C) 2015 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. |
| */ |
| |
| |
| // FIXME taken from OpenSLES_AndroidConfiguration.h |
| #define SL_ANDROID_KEY_PERFORMANCE_MODE ((const SLchar*) "androidPerformanceMode") |
| |
| //////////////////////////////////////////// |
| /// Actual sles functions. |
| |
| |
| // 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. |
| |
| #define _USE_MATH_DEFINES |
| #include <cmath> |
| #include "sles.h" |
| #include "audio_utils/atomic.h" |
| #include <stdio.h> |
| #include <assert.h> |
| #include <unistd.h> |
| #include <string.h> |
| |
| int slesInit(sles_data ** ppSles, int samplingRate, int frameCount, int micSource, |
| int performanceMode, |
| int testType, double frequency1, char* byteBufferPtr, int byteBufferLength, |
| short* loopbackTone, int maxRecordedLateCallbacks, int ignoreFirstFrames) { |
| int status = SLES_FAIL; |
| if (ppSles != NULL) { |
| sles_data * pSles = (sles_data*) malloc(sizeof(sles_data)); |
| |
| memset(pSles, 0, sizeof(sles_data)); |
| |
| SLES_PRINTF("pSles malloc %zu bytes at %p", sizeof(sles_data), pSles); |
| //__android_log_print(ANDROID_LOG_INFO, "sles_jni", |
| //"malloc %d bytes at %p", sizeof(sles_data), pSles);//Or ANDROID_LOG_INFO, ... |
| *ppSles = pSles; |
| if (pSles != NULL) |
| { |
| SLES_PRINTF("creating server. Sampling rate =%d, frame count = %d", |
| samplingRate, frameCount); |
| status = slesCreateServer(pSles, samplingRate, frameCount, micSource, |
| performanceMode, testType, |
| frequency1, byteBufferPtr, byteBufferLength, loopbackTone, |
| maxRecordedLateCallbacks, ignoreFirstFrames); |
| SLES_PRINTF("slesCreateServer =%d", status); |
| } |
| } |
| |
| return status; |
| } |
| int slesDestroy(sles_data ** ppSles) { |
| int status = SLES_FAIL; |
| if (ppSles != NULL) { |
| slesDestroyServer(*ppSles); |
| |
| if (*ppSles != NULL) |
| { |
| SLES_PRINTF("free memory at %p",*ppSles); |
| free(*ppSles); |
| *ppSles = 0; |
| } |
| status = SLES_SUCCESS; |
| } |
| return status; |
| } |
| |
| #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) |
| |
| // Called after audio recorder fills a buffer with data, then we can read from this filled buffer |
| static void recorderCallback(SLAndroidSimpleBufferQueueItf caller __unused, void *context) { |
| sles_data *pSles = (sles_data*) context; |
| if (pSles != NULL) { |
| collectBufferPeriod(&pSles->recorderBufferStats, NULL /*fdpStats*/, &pSles->recorderTimeStamps, |
| pSles->expectedBufferPeriod); |
| |
| //__android_log_print(ANDROID_LOG_INFO, "sles_jni", "in recorderCallback"); |
| SLresult result; |
| |
| //ee SLES_PRINTF("<R"); |
| |
| // We should only be called when a recording buffer is done |
| assert(pSles->rxFront <= pSles->rxBufCount); |
| assert(pSles->rxRear <= pSles->rxBufCount); |
| assert(pSles->rxFront != pSles->rxRear); |
| char *buffer = pSles->rxBuffers[pSles->rxFront]; //pSles->rxBuffers stores the data recorded |
| |
| |
| // Remove buffer from record queue |
| if (++pSles->rxFront > pSles->rxBufCount) { |
| pSles->rxFront = 0; |
| } |
| |
| if (pSles->testType == TEST_TYPE_LATENCY) { |
| // Throw out first frames |
| if (pSles->ignoreFirstFrames) { |
| int framesToErase = pSles->ignoreFirstFrames; |
| if (framesToErase > (int) pSles->bufSizeInFrames) { |
| framesToErase = pSles->bufSizeInFrames; |
| } |
| pSles->ignoreFirstFrames -= framesToErase; |
| memset(buffer, 0, framesToErase * pSles->channels * sizeof(short)); |
| } |
| |
| ssize_t actual = audio_utils_fifo_write(&(pSles->fifo), buffer, |
| (size_t) pSles->bufSizeInFrames); |
| |
| if (actual != (ssize_t) pSles->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 (pSles->fifo2Buffer != NULL) { |
| actual = audio_utils_fifo_write(&(pSles->fifo2), buffer, |
| (size_t) pSles->bufSizeInFrames); |
| if (actual != (ssize_t) pSles->bufSizeInFrames) { |
| write(1, "?", 1); |
| } |
| } |
| } else if (pSles->testType == TEST_TYPE_BUFFER_PERIOD) { |
| if (pSles->fifo2Buffer != NULL) { |
| ssize_t actual = byteBuffer_write(pSles, buffer, (size_t) pSles->bufSizeInFrames); |
| |
| //FIXME should log errors using other methods instead of printing to terminal |
| if (actual != (ssize_t) pSles->bufSizeInFrames) { |
| write(1, "?", 1); |
| } |
| } |
| } |
| |
| |
| // Enqueue this same buffer for the recorder to fill again. |
| result = (*(pSles->recorderBufferQueue))->Enqueue(pSles->recorderBufferQueue, buffer, |
| pSles->bufSizeInBytes); |
| //__android_log_print(ANDROID_LOG_INFO, "recorderCallback", "recorder buffer size: %i", |
| // pSles->bufSizeInBytes); |
| ASSERT_EQ(SL_RESULT_SUCCESS, result); |
| |
| |
| // Update our model of the record queue |
| SLuint32 rxRearNext = pSles->rxRear + 1; |
| if (rxRearNext > pSles->rxBufCount) { |
| rxRearNext = 0; |
| } |
| assert(rxRearNext != pSles->rxFront); |
| pSles->rxBuffers[pSles->rxRear] = buffer; |
| pSles->rxRear = rxRearNext; |
| |
| |
| |
| //ee SLES_PRINTF("r>"); |
| |
| } //pSles not null |
| } |
| |
| |
| // Write "count" amount of short from buffer to pSles->byteBufferPtr. This byteBuffer will read by |
| // java code. |
| ssize_t byteBuffer_write(sles_data *pSles, char *buffer, size_t count) { |
| // bytebufferSize is in byte |
| int32_t rear; // rear should not exceed 2^31 - 1, or else overflow will happen |
| memcpy(&rear, (char *) (pSles->byteBufferPtr + pSles->byteBufferLength - 4), sizeof(rear)); |
| |
| size_t frameSize = pSles->channels * sizeof(short); // only one channel |
| int32_t maxLengthInShort = (pSles->byteBufferLength - 4) / frameSize; |
| // mask the upper bits to get the correct position in the pipe |
| int32_t tempRear = rear & (maxLengthInShort - 1); |
| size_t part1 = maxLengthInShort - tempRear; |
| |
| if (part1 > count) { |
| part1 = count; |
| } |
| |
| if (part1 > 0) { |
| memcpy(pSles->byteBufferPtr + (tempRear * frameSize), buffer, |
| part1 * frameSize); |
| |
| size_t part2 = count - part1; |
| if (part2 > 0) { |
| memcpy(pSles->byteBufferPtr, (buffer + (part1 * frameSize)), |
| part2 * frameSize); |
| } |
| |
| //TODO do we need something similar to the below function call? |
| //android_atomic_release_store(audio_utils_fifo_sum(fifo, fifo->mRear, availToWrite), |
| // &fifo->mRear); |
| } |
| |
| // increase value of rear |
| int32_t* rear2 = (int32_t *) (pSles->byteBufferPtr + pSles->byteBufferLength - 4); |
| *rear2 += count; |
| return count; |
| } |
| |
| // Calculate nanosecond difference between two timespec structs from clock_gettime(CLOCK_MONOTONIC) |
| // tv_sec [0, max time_t] , tv_nsec [0, 999999999] |
| int64_t diffInNano(struct timespec previousTime, struct timespec currentTime) { |
| return (int64_t) (currentTime.tv_sec - previousTime.tv_sec) * (int64_t) NANOS_PER_SECOND + |
| currentTime.tv_nsec - previousTime.tv_nsec; |
| } |
| |
| // Called after audio player empties a buffer of data |
| static void playerCallback(SLBufferQueueItf caller __unused, void *context) { |
| sles_data *pSles = (sles_data*) context; |
| if (pSles != NULL) { |
| collectBufferPeriod(&pSles->playerBufferStats, &pSles->recorderBufferStats /*fdpStats*/, |
| &pSles->playerTimeStamps, pSles->expectedBufferPeriod); |
| SLresult result; |
| |
| //ee SLES_PRINTF("<P"); |
| |
| // Get the buffer that just finished playing |
| assert(pSles->txFront <= pSles->txBufCount); |
| assert(pSles->txRear <= pSles->txBufCount); |
| assert(pSles->txFront != pSles->txRear); |
| char *buffer = pSles->txBuffers[pSles->txFront]; |
| if (++pSles->txFront > pSles->txBufCount) { |
| pSles->txFront = 0; |
| } |
| |
| if (pSles->testType == TEST_TYPE_LATENCY) { |
| // Jitter buffer should have strictly less than 2 buffers worth of data in it. |
| // This is to prevent the test itself from adding too much latency. |
| size_t discardedInputFrames = 0; |
| for (;;) { |
| size_t availToRead = audio_utils_fifo_availToRead(&pSles->fifo); |
| if (availToRead < pSles->bufSizeInFrames * 2) { |
| break; |
| } |
| ssize_t actual = audio_utils_fifo_read(&pSles->fifo, buffer, pSles->bufSizeInFrames); |
| if (actual > 0) { |
| discardedInputFrames += actual; |
| } |
| if (actual != (ssize_t) pSles->bufSizeInFrames) { |
| break; |
| } |
| } |
| if (discardedInputFrames > 0) { |
| if (pSles->totalDiscardedInputFrames > 0) { |
| __android_log_print(ANDROID_LOG_WARN, "sles_jni", |
| "Discarded an additional %zu input frames after a total of %zu input frames" |
| " had previously been discarded", |
| discardedInputFrames, pSles->totalDiscardedInputFrames); |
| } |
| pSles->totalDiscardedInputFrames += discardedInputFrames; |
| } |
| |
| ssize_t actual = audio_utils_fifo_read(&(pSles->fifo), buffer, pSles->bufSizeInFrames); |
| if (actual != (ssize_t) pSles->bufSizeInFrames) { |
| write(1, "/", 1); |
| // on underrun from pipe, substitute silence |
| memset(buffer, 0, pSles->bufSizeInFrames * pSles->channels * sizeof(short)); |
| } |
| |
| if (pSles->injectImpulse == -1) { // here we inject pulse |
| |
| /*// 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 found to work well. |
| for (unsigned i = 0; i < pSles->bufSizeInFrames / 8; i += 8) { |
| for (int j = 0; j < 8; j++) { |
| for (unsigned k = 0; k < pSles->channels; k++) { |
| ((short *) buffer)[(i + j) * pSles->channels + k] = |
| j < 4 ? 0x7FFF : 0x8000; |
| } |
| } |
| }*/ |
| |
| //inject java generated tone |
| for (unsigned i = 0; i < pSles->bufSizeInFrames; ++i) { |
| for (unsigned k = 0; k < pSles->channels; ++k) { |
| ((short *) buffer)[i * pSles->channels + k] = pSles->loopbackTone[i]; |
| } |
| } |
| |
| pSles->injectImpulse = 0; |
| pSles->totalDiscardedInputFrames = 0; |
| } |
| } else if (pSles->testType == TEST_TYPE_BUFFER_PERIOD) { |
| double twoPi = M_PI * 2; |
| int maxShort = 32767; |
| float amplitude = 0.8; |
| short value; |
| double phaseIncrement = pSles->frequency1 / pSles->sampleRate; |
| bool isGlitchEnabled = false; |
| for (unsigned i = 0; i < pSles->bufSizeInFrames; i++) { |
| value = (short) (sin(pSles->bufferTestPhase1) * maxShort * amplitude); |
| for (unsigned k = 0; k < pSles->channels; ++k) { |
| ((short *) buffer)[i* pSles->channels + k] = value; |
| } |
| |
| pSles->bufferTestPhase1 += twoPi * phaseIncrement; |
| // insert glitches if isGlitchEnabled == true, and insert it for every second |
| if (isGlitchEnabled && (pSles->count % pSles->sampleRate == 0)) { |
| pSles->bufferTestPhase1 += twoPi * phaseIncrement; |
| } |
| |
| pSles->count++; |
| |
| while (pSles->bufferTestPhase1 > twoPi) { |
| pSles->bufferTestPhase1 -= twoPi; |
| } |
| } |
| } |
| |
| // Enqueue the filled buffer for playing |
| result = (*(pSles->playerBufferQueue))->Enqueue(pSles->playerBufferQueue, buffer, |
| pSles->bufSizeInBytes); |
| ASSERT_EQ(SL_RESULT_SUCCESS, result); |
| |
| // Update our model of the player queue |
| assert(pSles->txFront <= pSles->txBufCount); |
| assert(pSles->txRear <= pSles->txBufCount); |
| SLuint32 txRearNext = pSles->txRear + 1; |
| if (txRearNext > pSles->txBufCount) { |
| txRearNext = 0; |
| } |
| assert(txRearNext != pSles->txFront); |
| pSles->txBuffers[pSles->txRear] = buffer; |
| pSles->txRear = txRearNext; |
| |
| } //pSles not null |
| } |
| |
| // Used to set initial values for the bufferStats struct before values can be recorded. |
| void initBufferStats(bufferStats *stats) { |
| stats->buffer_period = new int[RANGE](); // initialized to zeros |
| stats->previous_time = {0,0}; |
| stats->current_time = {0,0}; |
| |
| stats->buffer_count = 0; |
| stats->max_buffer_period = 0; |
| |
| stats->measurement_count = 0; |
| stats->SDM = 0; |
| stats->var = 0; |
| } |
| |
| // Called in the beginning of playerCallback() to collect the interval between each callback. |
| // fdpStats is either NULL or a pointer to the buffer statistics for the full-duplex partner. |
| void collectBufferPeriod(bufferStats *stats, bufferStats *fdpStats, callbackTimeStamps *timeStamps, |
| short expectedBufferPeriod) { |
| clock_gettime(CLOCK_MONOTONIC, &(stats->current_time)); |
| |
| if (timeStamps->startTime.tv_sec == 0 && timeStamps->startTime.tv_nsec == 0) { |
| timeStamps->startTime = stats->current_time; |
| } |
| |
| (stats->buffer_count)++; |
| |
| if (stats->previous_time.tv_sec != 0 && stats->buffer_count > BUFFER_PERIOD_DISCARD && |
| (fdpStats == NULL || fdpStats->buffer_count > BUFFER_PERIOD_DISCARD_FULL_DUPLEX_PARTNER)) { |
| |
| int64_t callbackDuration = diffInNano(stats->previous_time, stats->current_time); |
| |
| bool outlier = updateBufferStats(stats, callbackDuration, expectedBufferPeriod); |
| |
| //recording timestamps of buffer periods not at expected buffer period |
| if (outlier) { |
| int64_t timeStamp = diffInNano(timeStamps->startTime, stats->current_time); |
| recordTimeStamp(timeStamps, callbackDuration, timeStamp); |
| } |
| } |
| |
| stats->previous_time = stats->current_time; |
| } |
| |
| // Records an outlier given the duration in nanoseconds and the number of nanoseconds |
| // between it and the start of the test. |
| void recordTimeStamp(callbackTimeStamps *timeStamps, |
| int64_t callbackDuration, int64_t timeStamp) { |
| if (timeStamps->exceededCapacity) { |
| return; |
| } |
| |
| //only marked as exceeded if attempting to record a late callback after arrays full |
| if (timeStamps->index == timeStamps->capacity){ |
| timeStamps->exceededCapacity = true; |
| } else { |
| timeStamps->callbackDurations[timeStamps->index] = |
| (short) ((callbackDuration + NANOS_PER_MILLI - 1) / NANOS_PER_MILLI); |
| timeStamps->timeStampsMs[timeStamps->index] = |
| (int) ((timeStamp + NANOS_PER_MILLI - 1) / NANOS_PER_MILLI); |
| timeStamps->index++; |
| } |
| } |
| |
| void atomicSetIfGreater(volatile int32_t *addr, int32_t val) { |
| // TODO: rewrite this to avoid the need for unbounded spinning |
| int32_t old; |
| do { |
| old = *addr; |
| if (val < old) return; |
| } while(!android_atomic_compare_exchange(&old, val, addr)); |
| } |
| |
| // Updates the stats being collected about buffer periods. Returns true if this is an outlier. |
| bool updateBufferStats(bufferStats *stats, int64_t diff_in_nano, int expectedBufferPeriod) { |
| stats->measurement_count++; |
| |
| // round up to nearest millisecond |
| int diff_in_milli = (int) ((diff_in_nano + NANOS_PER_MILLI - 1) / NANOS_PER_MILLI); |
| |
| if (diff_in_milli > stats->max_buffer_period) { |
| stats->max_buffer_period = diff_in_milli; |
| } |
| |
| // from 0 ms to 1000 ms, plus a sum of all instances > 1000ms |
| if (diff_in_milli >= (RANGE - 1)) { |
| (stats->buffer_period)[RANGE-1]++; |
| } else if (diff_in_milli >= 0) { |
| (stats->buffer_period)[diff_in_milli]++; |
| } else { // for diff_in_milli < 0 |
| __android_log_print(ANDROID_LOG_INFO, "sles_player", "Having negative BufferPeriod."); |
| } |
| |
| int64_t delta = diff_in_nano - (int64_t) expectedBufferPeriod * NANOS_PER_MILLI; |
| stats->SDM += delta * delta; |
| if (stats->measurement_count > 1) { |
| stats->var = stats->SDM / stats->measurement_count; |
| } |
| |
| // check if the lateness is so bad that a systrace should be captured |
| // TODO: replace static threshold of lateness with a dynamic determination |
| if (diff_in_milli > expectedBufferPeriod + LATE_CALLBACK_CAPTURE_THRESHOLD) { |
| // TODO: log in a non-blocking way |
| //__android_log_print(ANDROID_LOG_INFO, "buffer_stats", "Callback late by %d ms", |
| // diff_in_milli - expectedBufferPeriod); |
| atomicSetIfGreater(&(stats->captureRank), diff_in_milli - expectedBufferPeriod); |
| } |
| return diff_in_milli > expectedBufferPeriod + LATE_CALLBACK_OUTLIER_THRESHOLD; |
| } |
| |
| int slesCreateServer(sles_data *pSles, int samplingRate, int frameCount, int micSource, |
| int performanceMode, |
| int testType, double frequency1, char *byteBufferPtr, int byteBufferLength, |
| short *loopbackTone, int maxRecordedLateCallbacks, int ignoreFirstFrames) { |
| int status = SLES_FAIL; |
| |
| if (pSles != NULL) { |
| |
| // adb shell slesTest_feedback -r1 -t1 -s48000 -f240 -i300 -e3 -o/sdcard/log.wav |
| // r1 and t1 are the receive and transmit buffer counts, typically 1 |
| // s is the sample rate, typically 48000 or 44100 |
| // f is the frame count per buffer, typically 240 or 256 |
| // i is the number of milliseconds before impulse. You may need to adjust this. |
| // e is number of seconds to record |
| // o is output .wav file name |
| |
| |
| // // default values |
| // SLuint32 rxBufCount = 1; // -r# |
| // SLuint32 txBufCount = 1; // -t# |
| // SLuint32 bufSizeInFrames = 240; // -f# |
| // SLuint32 channels = 1; // -c# |
| // SLuint32 sampleRate = 48000; // -s# |
| // SLuint32 exitAfterSeconds = 3; // -e# |
| // SLuint32 freeBufCount = 0; // calculated |
| // SLuint32 bufSizeInBytes = 0; // calculated |
| // int injectImpulse = 300; // -i#i |
| // |
| // // Storage area for the buffer queues |
| // char **rxBuffers; |
| // char **txBuffers; |
| // char **freeBuffers; |
| // |
| // // Buffer indices |
| // SLuint32 rxFront; // oldest recording |
| // SLuint32 rxRear; // next to be recorded |
| // SLuint32 txFront; // oldest playing |
| // SLuint32 txRear; // next to be played |
| // SLuint32 freeFront; // oldest free |
| // SLuint32 freeRear; // next to be freed |
| // |
| // audio_utils_fifo fifo; //(*) |
| // SLAndroidSimpleBufferQueueItf recorderBufferQueue; |
| // SLBufferQueueItf playerBufferQueue; |
| |
| // default values |
| pSles->rxBufCount = 1; // -r# |
| pSles->txBufCount = 1; // -t# |
| pSles->bufSizeInFrames = frameCount;//240; // -f# |
| pSles->channels = 1; // -c# |
| pSles->sampleRate = samplingRate;//48000; // -s# |
| pSles->exitAfterSeconds = 3; // -e# |
| pSles->freeBufCount = 0; // calculated |
| pSles->bufSizeInBytes = 0; // calculated |
| pSles->injectImpulse = 300; // -i#i |
| pSles->totalDiscardedInputFrames = 0; |
| pSles->ignoreFirstFrames = ignoreFirstFrames; |
| |
| // Storage area for the buffer queues |
| // char **rxBuffers; |
| // char **txBuffers; |
| // char **freeBuffers; |
| |
| // Buffer indices |
| pSles->rxFront; // oldest recording |
| pSles->rxRear; // next to be recorded |
| pSles->txFront; // oldest playing |
| pSles->txRear; // next to be played |
| pSles->freeFront; // oldest free |
| pSles->freeRear; // next to be freed |
| |
| pSles->fifo; //(*) |
| pSles->fifo2Buffer = NULL; //this fifo is for sending data to java code (to plot it) |
| pSles->recorderBufferQueue; |
| pSles->playerBufferQueue; |
| |
| |
| |
| // compute total free buffers as -r plus -t |
| pSles->freeBufCount = pSles->rxBufCount + pSles->txBufCount; |
| // compute buffer size |
| pSles->bufSizeInBytes = pSles->channels * pSles->bufSizeInFrames * sizeof(short); |
| |
| // Initialize free buffers |
| pSles->freeBuffers = (char **) calloc(pSles->freeBufCount + 1, sizeof(char *)); |
| SLES_PRINTF(" calloc freeBuffers %zu bytes at %p",pSles->freeBufCount + 1, |
| pSles->freeBuffers); |
| unsigned j; |
| for (j = 0; j < pSles->freeBufCount; ++j) { |
| pSles->freeBuffers[j] = (char *) malloc(pSles->bufSizeInBytes); |
| SLES_PRINTF(" buff%d malloc %zu bytes at %p",j, pSles->bufSizeInBytes, |
| pSles->freeBuffers[j]); |
| } |
| pSles->freeFront = 0; |
| pSles->freeRear = pSles->freeBufCount; |
| pSles->freeBuffers[j] = NULL; |
| |
| // Initialize record queue |
| pSles->rxBuffers = (char **) calloc(pSles->rxBufCount + 1, sizeof(char *)); |
| SLES_PRINTF(" calloc rxBuffers %zu bytes at %p",pSles->rxBufCount + 1, pSles->rxBuffers); |
| pSles->rxFront = 0; |
| pSles->rxRear = 0; |
| |
| // Initialize play queue |
| pSles->txBuffers = (char **) calloc(pSles->txBufCount + 1, sizeof(char *)); |
| SLES_PRINTF(" calloc txBuffers %zu bytes at %p",pSles->txBufCount + 1, pSles->txBuffers); |
| pSles->txFront = 0; |
| pSles->txRear = 0; |
| |
| size_t frameSize = pSles->channels * sizeof(short); |
| #define FIFO_FRAMES 1024 |
| pSles->fifoBuffer = new short[FIFO_FRAMES * pSles->channels]; |
| audio_utils_fifo_init(&(pSles->fifo), FIFO_FRAMES, frameSize, pSles->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 |
| pSles->fifo2Buffer = new short[FIFO2_FRAMES * pSles->channels]; |
| audio_utils_fifo_init(&(pSles->fifo2), FIFO2_FRAMES, frameSize, pSles->fifo2Buffer); |
| // } else { |
| // fprintf(stderr, "sf_open failed\n"); |
| // } |
| // } else { |
| // sndfile = NULL; |
| // } |
| |
| initBufferStats(&pSles->recorderBufferStats); |
| initBufferStats(&pSles->playerBufferStats); |
| |
| // init other variables needed for buffer test |
| pSles->testType = testType; |
| pSles->frequency1 = frequency1; |
| pSles->bufferTestPhase1 = 0; |
| pSles->count = 0; |
| pSles->byteBufferPtr = byteBufferPtr; |
| pSles->byteBufferLength = byteBufferLength; |
| |
| //init loopback tone |
| pSles->loopbackTone = loopbackTone; |
| |
| pSles->recorderTimeStamps = { |
| new int[maxRecordedLateCallbacks], //int* timeStampsMs |
| new short[maxRecordedLateCallbacks], //short* callbackDurations |
| 0, //short index |
| {0,0}, //struct timespec startTime; |
| maxRecordedLateCallbacks, //int capacity |
| false //bool exceededCapacity |
| }; |
| |
| pSles->playerTimeStamps = { |
| new int[maxRecordedLateCallbacks], //int* timeStampsMs |
| new short[maxRecordedLateCallbacks], //short* callbackDurations; |
| 0, //short index |
| {0,0}, //struct timespec startTime; |
| maxRecordedLateCallbacks, //int capacity |
| false //bool exceededCapacity |
| }; |
| |
| pSles->expectedBufferPeriod = (short) ( |
| round(pSles->bufSizeInFrames * MILLIS_PER_SECOND / (float) pSles->sampleRate)); |
| |
| SLresult result; |
| |
| // create engine |
| pSles->engineObject; |
| result = slCreateEngine(&(pSles->engineObject), 0, NULL, 0, NULL, NULL); |
| ASSERT_EQ(SL_RESULT_SUCCESS, result); |
| result = (*(pSles->engineObject))->Realize(pSles->engineObject, SL_BOOLEAN_FALSE); |
| ASSERT_EQ(SL_RESULT_SUCCESS, result); |
| SLEngineItf engineEngine; |
| result = (*(pSles->engineObject))->GetInterface(pSles->engineObject, SL_IID_ENGINE, |
| &engineEngine); |
| ASSERT_EQ(SL_RESULT_SUCCESS, result); |
| |
| // create output mix |
| pSles->outputmixObject; |
| result = (*engineEngine)->CreateOutputMix(engineEngine, &(pSles->outputmixObject), 0, NULL, |
| NULL); |
| ASSERT_EQ(SL_RESULT_SUCCESS, result); |
| result = (*(pSles->outputmixObject))->Realize(pSles->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 = pSles->txBufCount; |
| locator_outputmix.locatorType = SL_DATALOCATOR_OUTPUTMIX; |
| locator_outputmix.outputMix = pSles->outputmixObject; |
| pcm.formatType = SL_DATAFORMAT_PCM; |
| pcm.numChannels = pSles->channels; |
| pcm.samplesPerSec = pSles->sampleRate * 1000; |
| pcm.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16; |
| pcm.containerSize = 16; |
| pcm.channelMask = pSles->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; |
| pSles->playerObject = NULL; |
| pSles->recorderObject = NULL; |
| SLInterfaceID ids_tx[2] = {SL_IID_BUFFERQUEUE, SL_IID_ANDROIDCONFIGURATION}; |
| SLboolean flags_tx[2] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE}; |
| result = (*engineEngine)->CreateAudioPlayer(engineEngine, &(pSles->playerObject), |
| &audiosrc, &audiosnk, 2, ids_tx, flags_tx); |
| if (SL_RESULT_CONTENT_UNSUPPORTED == result) { |
| fprintf(stderr, "Could not create audio player (result %x), check sample rate\n", |
| result); |
| SLES_PRINTF("ERROR: Could not create audio player (result %x), check sample rate\n", |
| result); |
| goto cleanup; |
| } |
| ASSERT_EQ(SL_RESULT_SUCCESS, result); |
| |
| { |
| /* Get the Android configuration interface which is explicit */ |
| SLAndroidConfigurationItf configItf; |
| result = (*(pSles->playerObject))->GetInterface(pSles->playerObject, |
| SL_IID_ANDROIDCONFIGURATION, (void*)&configItf); |
| ASSERT_EQ(SL_RESULT_SUCCESS, result); |
| |
| /* Use the configuration interface to configure the player before it's realized */ |
| if (performanceMode != -1) { |
| SLuint32 performanceMode32 = performanceMode; |
| result = (*configItf)->SetConfiguration(configItf, SL_ANDROID_KEY_PERFORMANCE_MODE, |
| &performanceMode32, sizeof(SLuint32)); |
| ASSERT_EQ(SL_RESULT_SUCCESS, result); |
| } |
| |
| } |
| |
| result = (*(pSles->playerObject))->Realize(pSles->playerObject, SL_BOOLEAN_FALSE); |
| ASSERT_EQ(SL_RESULT_SUCCESS, result); |
| SLPlayItf playerPlay; |
| result = (*(pSles->playerObject))->GetInterface(pSles->playerObject, SL_IID_PLAY, |
| &playerPlay); |
| ASSERT_EQ(SL_RESULT_SUCCESS, result); |
| result = (*(pSles->playerObject))->GetInterface(pSles->playerObject, SL_IID_BUFFERQUEUE, |
| &(pSles->playerBufferQueue)); |
| ASSERT_EQ(SL_RESULT_SUCCESS, result); |
| result = (*(pSles->playerBufferQueue))->RegisterCallback(pSles->playerBufferQueue, |
| playerCallback, pSles); //playerCallback is the name of callback function |
| ASSERT_EQ(SL_RESULT_SUCCESS, result); |
| |
| // Enqueue some zero buffers for the player |
| for (j = 0; j < pSles->txBufCount; ++j) { |
| |
| // allocate a free buffer |
| assert(pSles->freeFront != pSles->freeRear); |
| char *buffer = pSles->freeBuffers[pSles->freeFront]; |
| if (++pSles->freeFront > pSles->freeBufCount) { |
| pSles->freeFront = 0; |
| } |
| |
| // put on play queue |
| SLuint32 txRearNext = pSles->txRear + 1; |
| if (txRearNext > pSles->txBufCount) { |
| txRearNext = 0; |
| } |
| assert(txRearNext != pSles->txFront); |
| pSles->txBuffers[pSles->txRear] = buffer; |
| pSles->txRear = txRearNext; |
| result = (*(pSles->playerBufferQueue))->Enqueue(pSles->playerBufferQueue, |
| buffer, pSles->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 = pSles->rxBufCount; |
| |
| audiosnk.pLocator = &locator_bufferqueue_rx; |
| audiosnk.pFormat = &pcm; |
| |
| { //why brackets here? |
| SLInterfaceID ids_rx[2] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE, |
| SL_IID_ANDROIDCONFIGURATION}; |
| SLboolean flags_rx[2] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE}; |
| result = (*engineEngine)->CreateAudioRecorder(engineEngine, &(pSles->recorderObject), |
| &audiosrc, &audiosnk, 2, 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); |
| status = SLES_FAIL; |
| |
| SLES_PRINTF("ERROR: Could not create audio recorder (result %x), " |
| "check sample rate and channel count\n", result); |
| goto cleanup; |
| } |
| } |
| ASSERT_EQ(SL_RESULT_SUCCESS, result); |
| |
| { |
| /* Get the Android configuration interface which is explicit */ |
| SLAndroidConfigurationItf configItf; |
| result = (*(pSles->recorderObject))->GetInterface(pSles->recorderObject, |
| SL_IID_ANDROIDCONFIGURATION, (void*)&configItf); |
| ASSERT_EQ(SL_RESULT_SUCCESS, result); |
| |
| SLuint32 presetValue = micSource; |
| //SL_ANDROID_RECORDING_PRESET_CAMCORDER;//SL_ANDROID_RECORDING_PRESET_NONE; |
| |
| /* Use the configuration interface to configure the recorder before it's realized */ |
| if (presetValue != SL_ANDROID_RECORDING_PRESET_NONE) { |
| result = (*configItf)->SetConfiguration(configItf, SL_ANDROID_KEY_RECORDING_PRESET, |
| &presetValue, sizeof(SLuint32)); |
| ASSERT_EQ(SL_RESULT_SUCCESS, result); |
| } |
| if (performanceMode != -1) { |
| SLuint32 performanceMode32 = performanceMode; |
| result = (*configItf)->SetConfiguration(configItf, SL_ANDROID_KEY_PERFORMANCE_MODE, |
| &performanceMode32, sizeof(SLuint32)); |
| ASSERT_EQ(SL_RESULT_SUCCESS, result); |
| } |
| |
| } |
| |
| result = (*(pSles->recorderObject))->Realize(pSles->recorderObject, SL_BOOLEAN_FALSE); |
| ASSERT_EQ(SL_RESULT_SUCCESS, result); |
| |
| SLRecordItf recorderRecord; |
| result = (*(pSles->recorderObject))->GetInterface(pSles->recorderObject, SL_IID_RECORD, |
| &recorderRecord); |
| ASSERT_EQ(SL_RESULT_SUCCESS, result); |
| |
| result = (*(pSles->recorderObject))->GetInterface(pSles->recorderObject, |
| SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &(pSles->recorderBufferQueue)); |
| ASSERT_EQ(SL_RESULT_SUCCESS, result); |
| |
| result = (*(pSles->recorderBufferQueue))->RegisterCallback(pSles->recorderBufferQueue, |
| recorderCallback, pSles); |
| ASSERT_EQ(SL_RESULT_SUCCESS, result); |
| |
| // Enqueue some empty buffers for the recorder |
| for (j = 0; j < pSles->rxBufCount; ++j) { |
| |
| // allocate a free buffer |
| assert(pSles->freeFront != pSles->freeRear); |
| char *buffer = pSles->freeBuffers[pSles->freeFront]; |
| if (++pSles->freeFront > pSles->freeBufCount) { |
| pSles->freeFront = 0; |
| } |
| |
| // put on record queue |
| SLuint32 rxRearNext = pSles->rxRear + 1; |
| if (rxRearNext > pSles->rxBufCount) { |
| rxRearNext = 0; |
| } |
| assert(rxRearNext != pSles->rxFront); |
| pSles->rxBuffers[pSles->rxRear] = buffer; |
| pSles->rxRear = rxRearNext; |
| result = (*(pSles->recorderBufferQueue))->Enqueue(pSles->recorderBufferQueue, |
| buffer, pSles->bufSizeInBytes); |
| ASSERT_EQ(SL_RESULT_SUCCESS, result); |
| } |
| |
| // Kick off the recorder |
| result = (*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_RECORDING); |
| ASSERT_EQ(SL_RESULT_SUCCESS, result); |
| |
| |
| |
| // Tear down the objects and exit |
| status = SLES_SUCCESS; |
| cleanup: |
| |
| SLES_PRINTF("Finished initialization with status: %d", status); |
| |
| int xx = 1; |
| |
| } |
| return status; |
| } |
| |
| // Read data from fifo2Buffer and store into pSamples. |
| int slesProcessNext(sles_data *pSles, double *pSamples, long maxSamples) { |
| //int status = SLES_FAIL; |
| |
| SLES_PRINTF("slesProcessNext: pSles = %p, currentSample: %p, maxSamples = %ld", |
| pSles, pSamples, maxSamples); |
| |
| int samplesRead = 0; |
| |
| int currentSample = 0; |
| double *pCurrentSample = pSamples; |
| int maxValue = 32768; |
| |
| if (pSles != NULL) { |
| |
| SLresult result; |
| for (int i = 0; i < 10; i++) { |
| usleep(100000); // sleep for 0.1s |
| if (pSles->fifo2Buffer != NULL) { |
| for (;;) { |
| short buffer[pSles->bufSizeInFrames * pSles->channels]; |
| ssize_t actual = audio_utils_fifo_read(&(pSles->fifo2), buffer, |
| pSles->bufSizeInFrames); |
| if (actual <= 0) |
| break; |
| { |
| for (int jj = 0; jj < actual && currentSample < maxSamples; jj++) { |
| *(pCurrentSample++) = ((double) buffer[jj]) / maxValue; |
| currentSample++; |
| } |
| } |
| samplesRead += actual; |
| } |
| } |
| if (pSles->injectImpulse > 0) { |
| if (pSles->injectImpulse <= 100) { |
| pSles->injectImpulse = -1; |
| write(1, "I", 1); |
| } else { |
| if ((pSles->injectImpulse % 1000) < 100) { |
| write(1, "i", 1); |
| } |
| pSles->injectImpulse -= 100; |
| } |
| } else if (i == 9) { |
| write(1, ".", 1); |
| } |
| } |
| SLBufferQueueState playerBQState; |
| result = (*(pSles->playerBufferQueue))->GetState(pSles->playerBufferQueue, |
| &playerBQState); |
| ASSERT_EQ(SL_RESULT_SUCCESS, result); |
| SLAndroidSimpleBufferQueueState recorderBQState; |
| result = (*(pSles->recorderBufferQueue))->GetState(pSles->recorderBufferQueue, |
| &recorderBQState); |
| ASSERT_EQ(SL_RESULT_SUCCESS, result); |
| |
| SLES_PRINTF("End of slesProcessNext: pSles = %p, samplesRead = %d, maxSamples = %ld", |
| pSles, samplesRead, maxSamples); |
| } |
| return samplesRead; |
| } |
| |
| |
| int slesDestroyServer(sles_data *pSles) { |
| int status = SLES_FAIL; |
| |
| SLES_PRINTF("Start slesDestroyServer: pSles = %p", pSles); |
| |
| if (pSles != NULL) { |
| if (NULL != pSles->playerObject) { |
| SLES_PRINTF("stopping player..."); |
| SLPlayItf playerPlay; |
| SLresult result = (*(pSles->playerObject))->GetInterface(pSles->playerObject, |
| SL_IID_PLAY, &playerPlay); |
| |
| ASSERT_EQ(SL_RESULT_SUCCESS, result); |
| |
| //stop player and recorder if they exist |
| result = (*playerPlay)->SetPlayState(playerPlay, SL_PLAYSTATE_STOPPED); |
| ASSERT_EQ(SL_RESULT_SUCCESS, result); |
| } |
| |
| if (NULL != pSles->recorderObject) { |
| SLES_PRINTF("stopping recorder..."); |
| SLRecordItf recorderRecord; |
| SLresult result = (*(pSles->recorderObject))->GetInterface(pSles->recorderObject, |
| SL_IID_RECORD, &recorderRecord); |
| ASSERT_EQ(SL_RESULT_SUCCESS, result); |
| |
| result = (*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_STOPPED); |
| ASSERT_EQ(SL_RESULT_SUCCESS, result); |
| } |
| |
| usleep(1000); |
| |
| audio_utils_fifo_deinit(&(pSles->fifo)); |
| delete[] pSles->fifoBuffer; |
| |
| SLES_PRINTF("slesDestroyServer 2"); |
| |
| // if (sndfile != NULL) { |
| audio_utils_fifo_deinit(&(pSles->fifo2)); |
| delete[] pSles->fifo2Buffer; |
| |
| SLES_PRINTF("slesDestroyServer 3"); |
| |
| // sf_close(sndfile); |
| // } |
| if (NULL != pSles->playerObject) { |
| (*(pSles->playerObject))->Destroy(pSles->playerObject); |
| } |
| |
| SLES_PRINTF("slesDestroyServer 4"); |
| |
| if (NULL != pSles->recorderObject) { |
| (*(pSles->recorderObject))->Destroy(pSles->recorderObject); |
| } |
| |
| SLES_PRINTF("slesDestroyServer 5"); |
| |
| (*(pSles->outputmixObject))->Destroy(pSles->outputmixObject); |
| SLES_PRINTF("slesDestroyServer 6"); |
| (*(pSles->engineObject))->Destroy(pSles->engineObject); |
| SLES_PRINTF("slesDestroyServer 7"); |
| |
| //free buffers |
| if (NULL != pSles->freeBuffers) { |
| for (unsigned j = 0; j < pSles->freeBufCount; ++j) { |
| if (NULL != pSles->freeBuffers[j]) { |
| SLES_PRINTF(" free buff%d at %p",j, pSles->freeBuffers[j]); |
| free (pSles->freeBuffers[j]); |
| } |
| } |
| SLES_PRINTF(" free freeBuffers at %p", pSles->freeBuffers); |
| free(pSles->freeBuffers); |
| } else { |
| SLES_PRINTF(" freeBuffers NULL, no need to free"); |
| } |
| |
| |
| if (NULL != pSles->rxBuffers) { |
| SLES_PRINTF(" free rxBuffers at %p", pSles->rxBuffers); |
| free(pSles->rxBuffers); |
| } else { |
| SLES_PRINTF(" rxBuffers NULL, no need to free"); |
| } |
| |
| if (NULL != pSles->txBuffers) { |
| SLES_PRINTF(" free txBuffers at %p", pSles->txBuffers); |
| free(pSles->txBuffers); |
| } else { |
| SLES_PRINTF(" txBuffers NULL, no need to free"); |
| } |
| |
| |
| status = SLES_SUCCESS; |
| } |
| SLES_PRINTF("End slesDestroyServer: status = %d", status); |
| return status; |
| } |
| |
| |
| int* slesGetRecorderBufferPeriod(sles_data *pSles) { |
| return pSles->recorderBufferStats.buffer_period; |
| } |
| |
| int slesGetRecorderMaxBufferPeriod(sles_data *pSles) { |
| return pSles->recorderBufferStats.max_buffer_period; |
| } |
| |
| int64_t slesGetRecorderVarianceBufferPeriod(sles_data *pSles) { |
| return pSles->recorderBufferStats.var; |
| } |
| |
| int* slesGetPlayerBufferPeriod(sles_data *pSles) { |
| return pSles->playerBufferStats.buffer_period; |
| } |
| |
| int slesGetPlayerMaxBufferPeriod(sles_data *pSles) { |
| return pSles->playerBufferStats.max_buffer_period; |
| } |
| |
| int64_t slesGetPlayerVarianceBufferPeriod(sles_data *pSles) { |
| return pSles->playerBufferStats.var; |
| } |
| |
| int slesGetCaptureRank(sles_data *pSles) { |
| // clear the capture flags since they're being handled now |
| int recorderRank = android_atomic_exchange(0, &pSles->recorderBufferStats.captureRank); |
| int playerRank = android_atomic_exchange(0, &pSles->playerBufferStats.captureRank); |
| |
| if (recorderRank > playerRank) { |
| return recorderRank; |
| } else { |
| return playerRank; |
| } |
| } |