/* * Copyright (C) 2017 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 sine waves using an AAudio callback. #ifndef AAUDIO_SIMPLE_PLAYER_H #define AAUDIO_SIMPLE_PLAYER_H #include #include #include #include "AAudioArgsParser.h" #include "SineGenerator.h" //#define SHARING_MODE AAUDIO_SHARING_MODE_EXCLUSIVE #define SHARING_MODE AAUDIO_SHARING_MODE_SHARED #define PERFORMANCE_MODE AAUDIO_PERFORMANCE_MODE_NONE // Arbitrary period for glitches #define FORCED_UNDERRUN_PERIOD_FRAMES (2 * 48000) #define MAX_TIMESTAMPS 16 typedef struct Timestamp { int64_t position; int64_t nanoseconds; } Timestamp; static constexpr int32_t kWorkloadScaler = 500; // Linear congruential random number generator. static uint32_t s_random16() { static uint32_t seed = 1234; seed = ((seed * 31421) + 6927) & 0x0FFFF; return seed; } /** * The random number generator is good for burning CPU because the compiler cannot * easily optimize away the computation. * @param workload number of times to execute the loop * @return a white noise value between -1.0 and +1.0 */ static float s_burnCPU(int32_t workload) { uint32_t random = 0; for (int32_t i = 0; i < workload; i++) { for (int32_t j = 0; j < 10; j++) { random = random ^ s_random16(); } } return (random - 32768) * (1.0 / 32768); } /** * Simple wrapper for AAudio that opens an output stream either in callback or blocking write mode. */ class AAudioSimplePlayer { public: AAudioSimplePlayer() {} ~AAudioSimplePlayer() { close(); }; /** * Call this before calling open(). * @param requestedSharingMode */ void setSharingMode(aaudio_sharing_mode_t requestedSharingMode) { mRequestedSharingMode = requestedSharingMode; } /** * Call this before calling open(). * @param requestedPerformanceMode */ void setPerformanceMode(aaudio_performance_mode_t requestedPerformanceMode) { mRequestedPerformanceMode = requestedPerformanceMode; } // TODO Extract a common base class for record and playback. /** * Only call this after open() has been called. */ int32_t getSampleRate() const { if (mStream == nullptr) { return AAUDIO_ERROR_INVALID_STATE; } return AAudioStream_getSampleRate(mStream); } /** * Only call this after open() has been called. */ int32_t getChannelCount() { if (mStream == nullptr) { return AAUDIO_ERROR_INVALID_STATE; } return AAudioStream_getChannelCount(mStream); } /** * Open a stream */ aaudio_result_t open(const AAudioParameters ¶meters, AAudioStream_dataCallback dataCallback = nullptr, AAudioStream_errorCallback errorCallback = nullptr, void *userContext = nullptr) { aaudio_result_t result = AAUDIO_OK; // Use an AAudioStreamBuilder to contain requested parameters. AAudioStreamBuilder *builder = nullptr; result = AAudio_createStreamBuilder(&builder); if (result != AAUDIO_OK) return result; parameters.applyParameters(builder); // apply args AAudioStreamBuilder_setDirection(builder, AAUDIO_DIRECTION_OUTPUT); if (dataCallback != nullptr) { AAudioStreamBuilder_setDataCallback(builder, dataCallback, userContext); } if (errorCallback != nullptr) { AAudioStreamBuilder_setErrorCallback(builder, errorCallback, userContext); } //AAudioStreamBuilder_setFramesPerDataCallback(builder, CALLBACK_SIZE_FRAMES); //AAudioStreamBuilder_setBufferCapacityInFrames(builder, 48 * 8); // Open an AAudioStream using the Builder. result = AAudioStreamBuilder_openStream(builder, &mStream); if (result == AAUDIO_OK) { int32_t sizeInBursts = parameters.getNumberOfBursts(); int32_t framesPerBurst = AAudioStream_getFramesPerBurst(mStream); int32_t bufferSizeFrames = sizeInBursts * framesPerBurst; AAudioStream_setBufferSizeInFrames(mStream, bufferSizeFrames); } AAudioStreamBuilder_delete(builder); return result; } aaudio_result_t open(int channelCount, int sampSampleRate, aaudio_format_t format, AAudioStream_dataCallback dataProc, AAudioStream_errorCallback errorProc, void *userContext) { aaudio_result_t result = AAUDIO_OK; // Use an AAudioStreamBuilder to contain requested parameters. AAudioStreamBuilder *builder = nullptr; result = AAudio_createStreamBuilder(&builder); if (result != AAUDIO_OK) return result; AAudioStreamBuilder_setDirection(builder, AAUDIO_DIRECTION_OUTPUT); AAudioStreamBuilder_setPerformanceMode(builder, mRequestedPerformanceMode); AAudioStreamBuilder_setSharingMode(builder, mRequestedSharingMode); AAudioStreamBuilder_setChannelCount(builder, channelCount); AAudioStreamBuilder_setSampleRate(builder, sampSampleRate); AAudioStreamBuilder_setFormat(builder, format); if (dataProc != nullptr) { AAudioStreamBuilder_setDataCallback(builder, dataProc, userContext); } if (errorProc != nullptr) { AAudioStreamBuilder_setErrorCallback(builder, errorProc, userContext); } //AAudioStreamBuilder_setFramesPerDataCallback(builder, CALLBACK_SIZE_FRAMES); //AAudioStreamBuilder_setBufferCapacityInFrames(builder, 48 * 8); // Open an AAudioStream using the Builder. result = AAudioStreamBuilder_openStream(builder, &mStream); AAudioStreamBuilder_delete(builder); return result; } aaudio_result_t close() { if (mStream != nullptr) { AAudioStream_close(mStream); mStream = nullptr; } return AAUDIO_OK; } // Write zero data to fill up the buffer and prevent underruns. aaudio_result_t prime() { int32_t samplesPerFrame = AAudioStream_getChannelCount(mStream); const int numFrames = 32; float zeros[numFrames * samplesPerFrame]; memset(zeros, 0, sizeof(zeros)); aaudio_result_t result = numFrames; while (result == numFrames) { result = AAudioStream_write(mStream, zeros, numFrames, 0); } return result; } // Start the stream. AAudio will start calling your callback function. aaudio_result_t start() { aaudio_result_t result = AAudioStream_requestStart(mStream); if (result != AAUDIO_OK) { printf("ERROR - AAudioStream_requestStart(output) returned %d %s\n", result, AAudio_convertResultToText(result)); } return result; } // Stop the stream. AAudio will stop calling your callback function. aaudio_result_t stop() { aaudio_result_t result = AAudioStream_requestStop(mStream); if (result != AAUDIO_OK) { printf("ERROR - AAudioStream_requestStop(output) returned %d %s\n", result, AAudio_convertResultToText(result)); } int32_t xRunCount = AAudioStream_getXRunCount(mStream); printf("AAudioStream_getXRunCount %d\n", xRunCount); return result; } // Pause the stream. AAudio will stop calling your callback function. aaudio_result_t pause() { aaudio_result_t result = AAudioStream_requestPause(mStream); if (result != AAUDIO_OK) { printf("ERROR - AAudioStream_requestPause(output) returned %d %s\n", result, AAudio_convertResultToText(result)); } int32_t xRunCount = AAudioStream_getXRunCount(mStream); printf("AAudioStream_getXRunCount %d\n", xRunCount); return result; } aaudio_result_t waitUntilPaused() { aaudio_result_t result = AAUDIO_OK; aaudio_stream_state_t currentState = AAudioStream_getState(mStream); aaudio_stream_state_t inputState = AAUDIO_STREAM_STATE_PAUSING; while (result == AAUDIO_OK && currentState == AAUDIO_STREAM_STATE_PAUSING) { result = AAudioStream_waitForStateChange(mStream, inputState, ¤tState, NANOS_PER_SECOND); inputState = currentState; } if (result != AAUDIO_OK) { return result; } return (currentState == AAUDIO_STREAM_STATE_PAUSED) ? AAUDIO_OK : AAUDIO_ERROR_INVALID_STATE; } // Flush the stream. AAudio will stop calling your callback function. aaudio_result_t flush() { aaudio_result_t result = AAudioStream_requestFlush(mStream); if (result != AAUDIO_OK) { printf("ERROR - AAudioStream_requestFlush(output) returned %d %s\n", result, AAudio_convertResultToText(result)); } return result; } AAudioStream *getStream() const { return mStream; } private: AAudioStream *mStream = nullptr; aaudio_sharing_mode_t mRequestedSharingMode = SHARING_MODE; aaudio_performance_mode_t mRequestedPerformanceMode = PERFORMANCE_MODE; }; typedef struct SineThreadedData_s { SineGenerator sineOscillators[MAX_CHANNELS]; Timestamp timestamps[MAX_TIMESTAMPS]; int64_t framesTotal = 0; int64_t nextFrameToGlitch = FORCED_UNDERRUN_PERIOD_FRAMES; int32_t minNumFrames = INT32_MAX; int32_t maxNumFrames = 0; int32_t timestampCount = 0; // in timestamps int32_t sampleRate = 48000; int32_t prefixToneFrames = 0; double workload = 0.0; bool sweepSetup = false; int scheduler = 0; bool schedulerChecked = false; int32_t hangTimeMSec = 0; int cpuAffinity = -1; AAudioSimplePlayer simplePlayer; int32_t callbackCount = 0; WakeUp waker{AAUDIO_OK}; /** * Set sampleRate first. */ void setupSineBlip() { for (int i = 0; i < MAX_CHANNELS; ++i) { double centerFrequency = 880.0 * (i + 2); sineOscillators[i].setup(centerFrequency, sampleRate); sineOscillators[i].setSweep(centerFrequency, centerFrequency, 0.0); } } void setupSineSweeps() { for (int i = 0; i < MAX_CHANNELS; ++i) { double centerFrequency = 220.0 * (i + 2); sineOscillators[i].setup(centerFrequency, sampleRate); double minFrequency = centerFrequency * 2.0 / 3.0; // Change range slightly so they will go out of phase. double maxFrequency = centerFrequency * 3.0 / 2.0; double sweepSeconds = 5.0 + i; sineOscillators[i].setSweep(minFrequency, maxFrequency, sweepSeconds); } sweepSetup = true; } } SineThreadedData_t; int setCpuAffinity(int cpuIndex) { cpu_set_t cpu_set; CPU_ZERO(&cpu_set); CPU_SET(cpuIndex, &cpu_set); int err = sched_setaffinity((pid_t) 0, sizeof(cpu_set_t), &cpu_set); return err == 0 ? 0 : -errno; } // Callback function that fills the audio output buffer. aaudio_data_callback_result_t SimplePlayerDataCallbackProc( AAudioStream *stream, void *userData, void *audioData, int32_t numFrames ) { // should not happen but just in case... if (userData == nullptr) { printf("ERROR - SimplePlayerDataCallbackProc needs userData\n"); return AAUDIO_CALLBACK_RESULT_STOP; } SineThreadedData_t *sineData = (SineThreadedData_t *) userData; if (sineData->cpuAffinity >= 0) { setCpuAffinity(sineData->cpuAffinity); sineData->cpuAffinity = -1; } // Play an initial high tone so we can tell whether the beginning was truncated. if (!sineData->sweepSetup && sineData->framesTotal >= sineData->prefixToneFrames) { sineData->setupSineSweeps(); } if (sineData->hangTimeMSec > 0) { if (sineData->framesTotal > sineData->nextFrameToGlitch) { usleep(sineData->hangTimeMSec * 1000); printf("Hang callback at %lld frames for %d msec\n", (long long) sineData->framesTotal, sineData->hangTimeMSec); sineData->nextFrameToGlitch += FORCED_UNDERRUN_PERIOD_FRAMES; } } if (!sineData->schedulerChecked) { sineData->scheduler = sched_getscheduler(gettid()); sineData->schedulerChecked = true; } if (sineData->timestampCount < MAX_TIMESTAMPS) { Timestamp *timestamp = &sineData->timestamps[sineData->timestampCount]; aaudio_result_t result = AAudioStream_getTimestamp(stream, CLOCK_MONOTONIC, ×tamp->position, ×tamp->nanoseconds); if (result == AAUDIO_OK && // valid? (sineData->timestampCount == 0 || // first one? (timestamp->position != (timestamp - 1)->position))) { // advanced position? sineData->timestampCount++; // keep this one } } if (numFrames > sineData->maxNumFrames) { sineData->maxNumFrames = numFrames; } if (numFrames < sineData->minNumFrames) { sineData->minNumFrames = numFrames; } int32_t samplesPerFrame = AAudioStream_getChannelCount(stream); int numActiveOscillators = std::min(samplesPerFrame, MAX_CHANNELS); switch (AAudioStream_getFormat(stream)) { case AAUDIO_FORMAT_PCM_I16: { int16_t *audioBuffer = (int16_t *) audioData; for (int i = 0; i < numActiveOscillators; ++i) { sineData->sineOscillators[i].render(&audioBuffer[i], samplesPerFrame, numFrames); } } break; case AAUDIO_FORMAT_PCM_FLOAT: { float *audioBuffer = (float *) audioData; for (int i = 0; i < numActiveOscillators; ++i) { sineData->sineOscillators[i].render(&audioBuffer[i], samplesPerFrame, numFrames); } } break; case AAUDIO_FORMAT_PCM_I24_PACKED: { uint8_t *audioBuffer = (uint8_t *) audioData; for (int i = 0; i < numActiveOscillators; ++i) { static const int bytesPerSample = getBytesPerSample(AAUDIO_FORMAT_PCM_I24_PACKED); sineData->sineOscillators[i].render24(&audioBuffer[i * bytesPerSample], samplesPerFrame, numFrames); } } break; case AAUDIO_FORMAT_PCM_I32: { int32_t *audioBuffer = (int32_t *) audioData; for (int i = 0; i < numActiveOscillators; ++i) { sineData->sineOscillators[i].render(&audioBuffer[i], samplesPerFrame, numFrames); } } break; default: return AAUDIO_CALLBACK_RESULT_STOP; } s_burnCPU((int32_t)(sineData->workload * kWorkloadScaler * numFrames)); sineData->callbackCount++; sineData->framesTotal += numFrames; return AAUDIO_CALLBACK_RESULT_CONTINUE; } void SimplePlayerErrorCallbackProc( AAudioStream *stream __unused, void *userData __unused, aaudio_result_t error) { // should not happen but just in case... if (userData == nullptr) { printf("ERROR - MyPlayerErrorCallbackProc needs userData\n"); return; } SineThreadedData_t *sineData = (SineThreadedData_t *) userData; android::status_t ret = sineData->waker.wake(error); printf("Error Callback, error: %d, futex wake returns %d\n", error, ret); } #endif //AAUDIO_SIMPLE_PLAYER_H