| /* | |
| * Copyright 2019 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. | |
| */ | |
| #ifndef SAMPLES_DEFAULT_DATA_CALLBACK_H | |
| #define SAMPLES_DEFAULT_DATA_CALLBACK_H | |
| #include <vector> | |
| #include <oboe/AudioStreamCallback.h> | |
| #include <logging_macros.h> | |
| #include "IRenderableAudio.h" | |
| #include "IRestartable.h" | |
| /** | |
| * This is a callback object which will render data from an `IRenderableAudio` source. | |
| */ | |
| class DefaultDataCallback : public oboe::AudioStreamDataCallback { | |
| public: | |
| DefaultDataCallback() {} | |
| virtual ~DefaultDataCallback() = default; | |
| virtual oboe::DataCallbackResult | |
| onAudioReady(oboe::AudioStream *oboeStream, void *audioData, int32_t numFrames) override { | |
| if (mIsThreadAffinityEnabled && !mIsThreadAffinitySet) { | |
| setThreadAffinity(); | |
| mIsThreadAffinitySet = true; | |
| } | |
| float *outputBuffer = static_cast<float *>(audioData); | |
| std::shared_ptr<IRenderableAudio> localRenderable = mRenderable; | |
| if (!localRenderable) { | |
| LOGE("Renderable source not set!"); | |
| return oboe::DataCallbackResult::Stop; | |
| } | |
| localRenderable->renderAudio(outputBuffer, numFrames); | |
| return oboe::DataCallbackResult::Continue; | |
| } | |
| void setSource(std::shared_ptr<IRenderableAudio> renderable) { | |
| mRenderable = renderable; | |
| } | |
| /** | |
| * Reset the callback to its initial state. | |
| */ | |
| void reset(){ | |
| mIsThreadAffinitySet = false; | |
| } | |
| std::shared_ptr<IRenderableAudio> getSource() { | |
| return mRenderable; | |
| } | |
| /** | |
| * Set the CPU IDs to bind the audio callback thread to | |
| * | |
| * @param mCpuIds - the CPU IDs to bind to | |
| */ | |
| void setCpuIds(std::vector<int> cpuIds){ | |
| mCpuIds = std::move(cpuIds); | |
| } | |
| /** | |
| * Enable or disable binding the audio callback thread to specific CPU cores. The CPU core IDs | |
| * can be specified using @see setCpuIds. If no CPU IDs are specified the initial core which the | |
| * audio thread is called on will be used. | |
| * | |
| * @param isEnabled - whether the audio callback thread should be bound to specific CPU core(s) | |
| */ | |
| void setThreadAffinityEnabled(bool isEnabled){ | |
| mIsThreadAffinityEnabled = isEnabled; | |
| LOGD("Thread affinity enabled: %s", (isEnabled) ? "true" : "false"); | |
| } | |
| private: | |
| std::shared_ptr<IRenderableAudio> mRenderable; | |
| std::vector<int> mCpuIds; // IDs of CPU cores which the audio callback should be bound to | |
| std::atomic<bool> mIsThreadAffinityEnabled { false }; | |
| std::atomic<bool> mIsThreadAffinitySet { false }; | |
| /** | |
| * Set the thread affinity for the current thread to mCpuIds. This can be useful to call on the | |
| * audio thread to avoid underruns caused by CPU core migrations to slower CPU cores. | |
| */ | |
| void setThreadAffinity() { | |
| pid_t current_thread_id = gettid(); | |
| cpu_set_t cpu_set; | |
| CPU_ZERO(&cpu_set); | |
| // If the callback cpu ids aren't specified then bind to the current cpu | |
| if (mCpuIds.empty()) { | |
| int current_cpu_id = sched_getcpu(); | |
| LOGD("Binding to current CPU ID %d", current_cpu_id); | |
| CPU_SET(current_cpu_id, &cpu_set); | |
| } else { | |
| LOGD("Binding to %d CPU IDs", static_cast<int>(mCpuIds.size())); | |
| for (size_t i = 0; i < mCpuIds.size(); i++) { | |
| int cpu_id = mCpuIds.at(i); | |
| LOGD("CPU ID %d added to cores set", cpu_id); | |
| CPU_SET(cpu_id, &cpu_set); | |
| } | |
| } | |
| int result = sched_setaffinity(current_thread_id, sizeof(cpu_set_t), &cpu_set); | |
| if (result == 0) { | |
| LOGV("Thread affinity set"); | |
| } else { | |
| LOGW("Error setting thread affinity. Error no: %d", result); | |
| } | |
| mIsThreadAffinitySet = true; | |
| } | |
| }; | |
| #endif //SAMPLES_DEFAULT_DATA_CALLBACK_H |