• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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_AUDIO_STREAM_CALLBACK_H
18 #define SAMPLES_DEFAULT_AUDIO_STREAM_CALLBACK_H
19 
20 
21 #include <vector>
22 #include <oboe/AudioStreamCallback.h>
23 #include <logging_macros.h>
24 
25 #include "IRenderableAudio.h"
26 #include "IRestartable.h"
27 
28 /**
29  * This is a callback object which will render data from an `IRenderableAudio` source. It is
30  * constructed using an `IRestartable` which allows it to automatically restart the parent object
31  * if the stream is disconnected (for example, when headphones are attached).
32  *
33  * @param IRestartable - the object which should be restarted when the stream is disconnected
34  */
35 class DefaultAudioStreamCallback : public oboe::AudioStreamCallback {
36 public:
DefaultAudioStreamCallback(IRestartable & parent)37     DefaultAudioStreamCallback(IRestartable &parent): mParent(parent) {}
38     virtual ~DefaultAudioStreamCallback() = default;
39 
40     virtual oboe::DataCallbackResult
onAudioReady(oboe::AudioStream * oboeStream,void * audioData,int32_t numFrames)41     onAudioReady(oboe::AudioStream *oboeStream, void *audioData, int32_t numFrames) override {
42 
43         if (mIsThreadAffinityEnabled && !mIsThreadAffinitySet) {
44             setThreadAffinity();
45             mIsThreadAffinitySet = true;
46         }
47 
48         float *outputBuffer = static_cast<float *>(audioData);
49 
50         std::shared_ptr<IRenderableAudio> localRenderable = mRenderable;
51         if (!localRenderable) {
52             LOGE("Renderable source not set!");
53             return oboe::DataCallbackResult::Stop;
54         }
55         localRenderable->renderAudio(outputBuffer, numFrames);
56         return oboe::DataCallbackResult::Continue;
57     }
58 
onErrorAfterClose(oboe::AudioStream * oboeStream,oboe::Result error)59     virtual void onErrorAfterClose(oboe::AudioStream *oboeStream, oboe::Result error) override {
60         // Restart the stream when it errors out with disconnect
61         if (error == oboe::Result::ErrorDisconnected) {
62             LOGE("Restarting AudioStream after disconnect");
63             mParent.restart();
64         } else {
65             LOGE("Unknown error");
66         }
67         mIsThreadAffinitySet = false;
68     }
69 
setSource(std::shared_ptr<IRenderableAudio> renderable)70     void setSource(std::shared_ptr<IRenderableAudio> renderable) {
71         mRenderable = renderable;
72     }
73 
getSource()74     std::shared_ptr<IRenderableAudio> getSource() {
75         return mRenderable;
76     }
77 
78     /**
79      * Set the CPU IDs to bind the audio callback thread to
80      *
81      * @param mCpuIds - the CPU IDs to bind to
82      */
setCpuIds(std::vector<int> cpuIds)83     void setCpuIds(std::vector<int> cpuIds){
84         mCpuIds = std::move(cpuIds);
85     }
86 
87     /**
88      * Enable or disable binding the audio callback thread to specific CPU cores. The CPU core IDs
89      * can be specified using @see setCpuIds. If no CPU IDs are specified the initial core which the
90      * audio thread is called on will be used.
91      *
92      * @param isEnabled - whether the audio callback thread should be bound to specific CPU core(s)
93      */
setThreadAffinityEnabled(bool isEnabled)94     void setThreadAffinityEnabled(bool isEnabled){
95         mIsThreadAffinityEnabled = isEnabled;
96         LOGD("Thread affinity enabled: %s", (isEnabled) ? "true" : "false");
97     }
98 
99 private:
100     std::shared_ptr<IRenderableAudio> mRenderable;
101     IRestartable &mParent;
102     std::vector<int> mCpuIds; // IDs of CPU cores which the audio callback should be bound to
103     std::atomic<bool> mIsThreadAffinityEnabled { false };
104     std::atomic<bool> mIsThreadAffinitySet { false };
105 
106     /**
107      * Set the thread affinity for the current thread to mCpuIds. This can be useful to call on the
108      * audio thread to avoid underruns caused by CPU core migrations to slower CPU cores.
109      */
setThreadAffinity()110     void setThreadAffinity() {
111 
112         pid_t current_thread_id = gettid();
113         cpu_set_t cpu_set;
114         CPU_ZERO(&cpu_set);
115 
116         // If the callback cpu ids aren't specified then bind to the current cpu
117         if (mCpuIds.empty()) {
118             int current_cpu_id = sched_getcpu();
119             LOGD("Binding to current CPU ID %d", current_cpu_id);
120             CPU_SET(current_cpu_id, &cpu_set);
121         } else {
122             LOGD("Binding to %d CPU IDs", static_cast<int>(mCpuIds.size()));
123             for (size_t i = 0; i < mCpuIds.size(); i++) {
124                 int cpu_id = mCpuIds.at(i);
125                 LOGD("CPU ID %d added to cores set", cpu_id);
126                 CPU_SET(cpu_id, &cpu_set);
127             }
128         }
129 
130         int result = sched_setaffinity(current_thread_id, sizeof(cpu_set_t), &cpu_set);
131         if (result == 0) {
132             LOGV("Thread affinity set");
133         } else {
134             LOGW("Error setting thread affinity. Error no: %d", result);
135         }
136 
137         mIsThreadAffinitySet = true;
138     }
139 
140 };
141 
142 #endif //SAMPLES_DEFAULT_AUDIO_STREAM_CALLBACK_H
143