1 /* 2 * Copyright 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #ifndef SAMPLES_DEFAULT_DATA_CALLBACK_H 18 #define SAMPLES_DEFAULT_DATA_CALLBACK_H 19 20 #include <vector> 21 #include <oboe/AudioStreamCallback.h> 22 #include <logging_macros.h> 23 24 #include "IRenderableAudio.h" 25 #include "IRestartable.h" 26 27 /** 28 * This is a callback object which will render data from an `IRenderableAudio` source. 29 */ 30 class DefaultDataCallback : public oboe::AudioStreamDataCallback { 31 public: DefaultDataCallback()32 DefaultDataCallback() {} 33 virtual ~DefaultDataCallback() = default; 34 35 virtual oboe::DataCallbackResult onAudioReady(oboe::AudioStream * oboeStream,void * audioData,int32_t numFrames)36 onAudioReady(oboe::AudioStream *oboeStream, void *audioData, int32_t numFrames) override { 37 38 if (mIsThreadAffinityEnabled && !mIsThreadAffinitySet) { 39 setThreadAffinity(); 40 mIsThreadAffinitySet = true; 41 } 42 43 float *outputBuffer = static_cast<float *>(audioData); 44 45 std::shared_ptr<IRenderableAudio> localRenderable = mRenderable; 46 if (!localRenderable) { 47 LOGE("Renderable source not set!"); 48 return oboe::DataCallbackResult::Stop; 49 } 50 localRenderable->renderAudio(outputBuffer, numFrames); 51 return oboe::DataCallbackResult::Continue; 52 } 53 setSource(std::shared_ptr<IRenderableAudio> renderable)54 void setSource(std::shared_ptr<IRenderableAudio> renderable) { 55 mRenderable = renderable; 56 } 57 58 /** 59 * Reset the callback to its initial state. 60 */ reset()61 void reset(){ 62 mIsThreadAffinitySet = false; 63 } 64 getSource()65 std::shared_ptr<IRenderableAudio> getSource() { 66 return mRenderable; 67 } 68 69 /** 70 * Set the CPU IDs to bind the audio callback thread to 71 * 72 * @param mCpuIds - the CPU IDs to bind to 73 */ setCpuIds(std::vector<int> cpuIds)74 void setCpuIds(std::vector<int> cpuIds){ 75 mCpuIds = std::move(cpuIds); 76 } 77 78 /** 79 * Enable or disable binding the audio callback thread to specific CPU cores. The CPU core IDs 80 * can be specified using @see setCpuIds. If no CPU IDs are specified the initial core which the 81 * audio thread is called on will be used. 82 * 83 * @param isEnabled - whether the audio callback thread should be bound to specific CPU core(s) 84 */ setThreadAffinityEnabled(bool isEnabled)85 void setThreadAffinityEnabled(bool isEnabled){ 86 mIsThreadAffinityEnabled = isEnabled; 87 LOGD("Thread affinity enabled: %s", (isEnabled) ? "true" : "false"); 88 } 89 90 private: 91 std::shared_ptr<IRenderableAudio> mRenderable; 92 std::vector<int> mCpuIds; // IDs of CPU cores which the audio callback should be bound to 93 std::atomic<bool> mIsThreadAffinityEnabled { false }; 94 std::atomic<bool> mIsThreadAffinitySet { false }; 95 96 /** 97 * Set the thread affinity for the current thread to mCpuIds. This can be useful to call on the 98 * audio thread to avoid underruns caused by CPU core migrations to slower CPU cores. 99 */ setThreadAffinity()100 void setThreadAffinity() { 101 102 pid_t current_thread_id = gettid(); 103 cpu_set_t cpu_set; 104 CPU_ZERO(&cpu_set); 105 106 // If the callback cpu ids aren't specified then bind to the current cpu 107 if (mCpuIds.empty()) { 108 int current_cpu_id = sched_getcpu(); 109 LOGD("Binding to current CPU ID %d", current_cpu_id); 110 CPU_SET(current_cpu_id, &cpu_set); 111 } else { 112 LOGD("Binding to %d CPU IDs", static_cast<int>(mCpuIds.size())); 113 for (size_t i = 0; i < mCpuIds.size(); i++) { 114 int cpu_id = mCpuIds.at(i); 115 LOGD("CPU ID %d added to cores set", cpu_id); 116 CPU_SET(cpu_id, &cpu_set); 117 } 118 } 119 120 int result = sched_setaffinity(current_thread_id, sizeof(cpu_set_t), &cpu_set); 121 if (result == 0) { 122 LOGV("Thread affinity set"); 123 } else { 124 LOGW("Error setting thread affinity. Error no: %d", result); 125 } 126 127 mIsThreadAffinitySet = true; 128 } 129 130 }; 131 132 #endif //SAMPLES_DEFAULT_DATA_CALLBACK_H 133