Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(400)

Side by Side Diff: webrtc/modules/audio_device/ios/voice_processing_audio_unit.mm

Issue 1809343002: Refactor AudioUnit code into its own class. (Closed) Base URL: https://chromium.googlesource.com/external/webrtc.git@master
Patch Set: CR comments Created 4 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « webrtc/modules/audio_device/ios/voice_processing_audio_unit.h ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 /*
2 * Copyright 2016 The WebRTC Project Authors. All rights reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
11 #import "webrtc/modules/audio_device/ios/voice_processing_audio_unit.h"
12
13 #include "webrtc/base/checks.h"
14
15 #import "webrtc/base/objc/RTCLogging.h"
16 #import "webrtc/modules/audio_device/ios/objc/RTCAudioSessionConfiguration.h"
17
18 #if !defined(NDEBUG)
19 static void LogStreamDescription(AudioStreamBasicDescription description) {
20 char formatIdString[5];
21 UInt32 formatId = CFSwapInt32HostToBig(description.mFormatID);
22 bcopy(&formatId, formatIdString, 4);
23 formatIdString[4] = '\0';
24 RTCLog(@"AudioStreamBasicDescription: {\n"
25 " mSampleRate: %.2f\n"
26 " formatIDString: %s\n"
27 " mFormatFlags: 0x%X\n"
28 " mBytesPerPacket: %u\n"
29 " mFramesPerPacket: %u\n"
30 " mBytesPerFrame: %u\n"
31 " mChannelsPerFrame: %u\n"
32 " mBitsPerChannel: %u\n"
33 " mReserved: %u\n}",
34 description.mSampleRate, formatIdString,
35 static_cast<unsigned int>(description.mFormatFlags),
36 static_cast<unsigned int>(description.mBytesPerPacket),
37 static_cast<unsigned int>(description.mFramesPerPacket),
38 static_cast<unsigned int>(description.mBytesPerFrame),
39 static_cast<unsigned int>(description.mChannelsPerFrame),
40 static_cast<unsigned int>(description.mBitsPerChannel),
41 static_cast<unsigned int>(description.mReserved));
42 }
43 #endif
44
45 namespace webrtc {
46
47 // Calls to AudioUnitInitialize() can fail if called back-to-back on different
48 // ADM instances. A fall-back solution is to allow multiple sequential calls
49 // with as small delay between each. This factor sets the max number of allowed
50 // initialization attempts.
51 static const int kMaxNumberOfAudioUnitInitializeAttempts = 5;
52 // A VP I/O unit's bus 1 connects to input hardware (microphone).
53 static const AudioUnitElement kInputBus = 1;
54 // A VP I/O unit's bus 0 connects to output hardware (speaker).
55 static const AudioUnitElement kOutputBus = 0;
56
57 VoiceProcessingAudioUnit::VoiceProcessingAudioUnit(
58 VoiceProcessingAudioUnitObserver* observer)
59 : observer_(observer), vpio_unit_(nullptr) {
60 RTC_DCHECK(observer);
61 }
62
63 VoiceProcessingAudioUnit::~VoiceProcessingAudioUnit() {
64 DisposeAudioUnit();
65 }
66
67 const UInt32 VoiceProcessingAudioUnit::kBytesPerSample = 2;
68
69 bool VoiceProcessingAudioUnit::Init() {
70 RTC_DCHECK(!vpio_unit_) << "Already called Init().";
71
72 // Create an audio component description to identify the Voice Processing
73 // I/O audio unit.
74 AudioComponentDescription vpio_unit_description;
75 vpio_unit_description.componentType = kAudioUnitType_Output;
76 vpio_unit_description.componentSubType = kAudioUnitSubType_VoiceProcessingIO;
77 vpio_unit_description.componentManufacturer = kAudioUnitManufacturer_Apple;
78 vpio_unit_description.componentFlags = 0;
79 vpio_unit_description.componentFlagsMask = 0;
80
81 // Obtain an audio unit instance given the description.
82 AudioComponent found_vpio_unit_ref =
83 AudioComponentFindNext(nullptr, &vpio_unit_description);
84
85 // Create a Voice Processing IO audio unit.
86 OSStatus result = noErr;
87 result = AudioComponentInstanceNew(found_vpio_unit_ref, &vpio_unit_);
88 if (result != noErr) {
89 vpio_unit_ = nullptr;
90 RTCLogError(@"AudioComponentInstanceNew failed. Error=%ld.", (long)result);
91 return false;
92 }
93
94 // Enable input on the input scope of the input element.
95 UInt32 enable_input = 1;
96 result = AudioUnitSetProperty(vpio_unit_, kAudioOutputUnitProperty_EnableIO,
97 kAudioUnitScope_Input, kInputBus, &enable_input,
98 sizeof(enable_input));
99 if (result != noErr) {
100 DisposeAudioUnit();
101 RTCLogError(@"Failed to enable input on input scope of input element. "
102 "Error=%ld.",
103 (long)result);
104 return false;
105 }
106
107 // Enable output on the output scope of the output element.
108 UInt32 enable_output = 1;
109 result = AudioUnitSetProperty(vpio_unit_, kAudioOutputUnitProperty_EnableIO,
110 kAudioUnitScope_Output, kOutputBus,
111 &enable_output, sizeof(enable_output));
112 if (result != noErr) {
113 DisposeAudioUnit();
114 RTCLogError(@"Failed to enable output on output scope of output element. "
115 "Error=%ld.",
116 (long)result);
117 return false;
118 }
119
120 // Specify the callback function that provides audio samples to the audio
121 // unit.
122 AURenderCallbackStruct render_callback;
123 render_callback.inputProc = OnGetPlayoutData;
124 render_callback.inputProcRefCon = this;
125 result = AudioUnitSetProperty(
126 vpio_unit_, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input,
127 kOutputBus, &render_callback, sizeof(render_callback));
128 if (result != noErr) {
129 DisposeAudioUnit();
130 RTCLogError(@"Failed to specify the render callback on the output bus. "
131 "Error=%ld.",
132 (long)result);
133 return false;
134 }
135
136 // Disable AU buffer allocation for the recorder, we allocate our own.
137 // TODO(henrika): not sure that it actually saves resource to make this call.
138 UInt32 flag = 0;
139 result = AudioUnitSetProperty(
140 vpio_unit_, kAudioUnitProperty_ShouldAllocateBuffer,
141 kAudioUnitScope_Output, kInputBus, &flag, sizeof(flag));
142 if (result != noErr) {
143 DisposeAudioUnit();
144 RTCLogError(@"Failed to disable buffer allocation on the input bus. "
145 "Error=%ld.",
146 (long)result);
147 return false;
148 }
149
150 // Specify the callback to be called by the I/O thread to us when input audio
151 // is available. The recorded samples can then be obtained by calling the
152 // AudioUnitRender() method.
153 AURenderCallbackStruct input_callback;
154 input_callback.inputProc = OnDeliverRecordedData;
155 input_callback.inputProcRefCon = this;
156 result = AudioUnitSetProperty(vpio_unit_,
157 kAudioOutputUnitProperty_SetInputCallback,
158 kAudioUnitScope_Global, kInputBus,
159 &input_callback, sizeof(input_callback));
160 if (result != noErr) {
161 DisposeAudioUnit();
162 RTCLogError(@"Failed to specify the input callback on the input bus. "
163 "Error=%ld.",
164 (long)result);
165 return false;
166 }
167
168 return true;
169 }
170
171 bool VoiceProcessingAudioUnit::Initialize(Float64 sample_rate) {
172 RTC_DCHECK(vpio_unit_) << "Init() not called.";
173 RTCLog(@"Initializing audio unit.");
174
175 OSStatus result = noErr;
176 AudioStreamBasicDescription format = GetFormat(sample_rate);
177 UInt32 size = sizeof(format);
178 #if !defined(NDEBUG)
179 LogStreamDescription(format);
180 #endif
181
182 // Set the format on the output scope of the input element/bus.
183 result =
184 AudioUnitSetProperty(vpio_unit_, kAudioUnitProperty_StreamFormat,
185 kAudioUnitScope_Output, kInputBus, &format, size);
186 if (result != noErr) {
187 RTCLogError(@"Failed to set format on output scope of input bus. "
188 "Error=%ld.",
189 (long)result);
190 return false;
191 }
192
193 // Set the format on the input scope of the output element/bus.
194 result =
195 AudioUnitSetProperty(vpio_unit_, kAudioUnitProperty_StreamFormat,
196 kAudioUnitScope_Input, kOutputBus, &format, size);
197 if (result != noErr) {
198 RTCLogError(@"Failed to set format on input scope of output bus. "
199 "Error=%ld.",
200 (long)result);
201 return false;
202 }
203
204 // Initialize the Voice Processing I/O unit instance.
205 // Calls to AudioUnitInitialize() can fail if called back-to-back on
206 // different ADM instances. The error message in this case is -66635 which is
207 // undocumented. Tests have shown that calling AudioUnitInitialize a second
208 // time, after a short sleep, avoids this issue.
209 // See webrtc:5166 for details.
210 int failed_initalize_attempts = 0;
211 result = AudioUnitInitialize(vpio_unit_);
212 while (result != noErr) {
213 RTCLogError(@"Failed to initialize the Voice Processing I/O unit. "
214 "Error=%ld.",
215 (long)result);
216 ++failed_initalize_attempts;
217 if (failed_initalize_attempts == kMaxNumberOfAudioUnitInitializeAttempts) {
218 // Max number of initialization attempts exceeded, hence abort.
219 RTCLogError(@"Too many initialization attempts.");
220 return false;
221 }
222 RTCLog(@"Pause 100ms and try audio unit initialization again...");
223 [NSThread sleepForTimeInterval:0.1f];
224 result = AudioUnitInitialize(vpio_unit_);
225 }
226 RTCLog(@"Voice Processing I/O unit is now initialized.");
227 return true;
228 }
229
230 bool VoiceProcessingAudioUnit::Start() {
231 RTC_DCHECK(vpio_unit_) << "Init() not called.";
232 RTCLog(@"Starting audio unit.");
233
234 OSStatus result = AudioOutputUnitStart(vpio_unit_);
235 if (result != noErr) {
236 RTCLogError(@"Failed to start audio unit. Error=%ld", (long)result);
237 return false;
238 }
239 return true;
240 }
241
242 bool VoiceProcessingAudioUnit::Stop() {
243 RTC_DCHECK(vpio_unit_) << "Init() not called.";
244 RTCLog(@"Stopping audio unit.");
245
246 OSStatus result = AudioOutputUnitStop(vpio_unit_);
247 if (result != noErr) {
248 RTCLogError(@"Failed to stop audio unit. Error=%ld", (long)result);
249 return false;
250 }
251 return true;
252 }
253
254 bool VoiceProcessingAudioUnit::Uninitialize() {
255 RTC_DCHECK(vpio_unit_) << "Init() not called.";
256 RTCLog(@"Unintializing audio unit.");
257
258 OSStatus result = AudioUnitUninitialize(vpio_unit_);
259 if (result != noErr) {
260 RTCLogError(@"Failed to uninitialize audio unit. Error=%ld", (long)result);
261 return false;
262 }
263 return true;
264 }
265
266 OSStatus VoiceProcessingAudioUnit::Render(AudioUnitRenderActionFlags* flags,
267 const AudioTimeStamp* time_stamp,
268 UInt32 output_bus_number,
269 UInt32 num_frames,
270 AudioBufferList* io_data) {
271 RTC_DCHECK(vpio_unit_) << "Init() not called.";
272
273 OSStatus result = AudioUnitRender(vpio_unit_, flags, time_stamp,
274 output_bus_number, num_frames, io_data);
275 if (result != noErr) {
276 RTCLogError(@"Failed to render audio unit. Error=%ld", (long)result);
277 }
278 return result;
279 }
280
281 OSStatus VoiceProcessingAudioUnit::OnGetPlayoutData(
282 void* in_ref_con,
283 AudioUnitRenderActionFlags* flags,
284 const AudioTimeStamp* time_stamp,
285 UInt32 bus_number,
286 UInt32 num_frames,
287 AudioBufferList* io_data) {
288 VoiceProcessingAudioUnit* audio_unit =
289 static_cast<VoiceProcessingAudioUnit*>(in_ref_con);
290 return audio_unit->NotifyGetPlayoutData(flags, time_stamp, bus_number,
291 num_frames, io_data);
292 }
293
294 OSStatus VoiceProcessingAudioUnit::OnDeliverRecordedData(
295 void* in_ref_con,
296 AudioUnitRenderActionFlags* flags,
297 const AudioTimeStamp* time_stamp,
298 UInt32 bus_number,
299 UInt32 num_frames,
300 AudioBufferList* io_data) {
301 VoiceProcessingAudioUnit* audio_unit =
302 static_cast<VoiceProcessingAudioUnit*>(in_ref_con);
303 return audio_unit->NotifyDeliverRecordedData(flags, time_stamp, bus_number,
304 num_frames, io_data);
305 }
306
307 OSStatus VoiceProcessingAudioUnit::NotifyGetPlayoutData(
308 AudioUnitRenderActionFlags* flags,
309 const AudioTimeStamp* time_stamp,
310 UInt32 bus_number,
311 UInt32 num_frames,
312 AudioBufferList* io_data) {
313 return observer_->OnGetPlayoutData(flags, time_stamp, bus_number, num_frames,
314 io_data);
315 }
316
317 OSStatus VoiceProcessingAudioUnit::NotifyDeliverRecordedData(
318 AudioUnitRenderActionFlags* flags,
319 const AudioTimeStamp* time_stamp,
320 UInt32 bus_number,
321 UInt32 num_frames,
322 AudioBufferList* io_data) {
323 return observer_->OnDeliverRecordedData(flags, time_stamp, bus_number,
324 num_frames, io_data);
325 }
326
327 AudioStreamBasicDescription VoiceProcessingAudioUnit::GetFormat(
328 Float64 sample_rate) const {
329 // Set the application formats for input and output:
330 // - use same format in both directions
331 // - avoid resampling in the I/O unit by using the hardware sample rate
332 // - linear PCM => noncompressed audio data format with one frame per packet
333 // - no need to specify interleaving since only mono is supported
334 AudioStreamBasicDescription format = {0};
335 RTC_DCHECK_EQ(1, kRTCAudioSessionPreferredNumberOfChannels);
336 format.mSampleRate = sample_rate;
337 format.mFormatID = kAudioFormatLinearPCM;
338 format.mFormatFlags =
339 kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked;
340 format.mBytesPerPacket = kBytesPerSample;
341 format.mFramesPerPacket = 1; // uncompressed.
342 format.mBytesPerFrame = kBytesPerSample;
343 format.mChannelsPerFrame = kRTCAudioSessionPreferredNumberOfChannels;
344 format.mBitsPerChannel = 8 * kBytesPerSample;
345 return format;
346 }
347
348 void VoiceProcessingAudioUnit::DisposeAudioUnit() {
349 if (vpio_unit_) {
350 OSStatus result = AudioComponentInstanceDispose(vpio_unit_);
351 if (result != noErr) {
352 RTCLogError(@"AudioComponentInstanceDispose failed. Error=%ld.",
353 (long)result);
354 }
355 vpio_unit_ = nullptr;
356 }
357 }
358
359 } // namespace webrtc
OLDNEW
« no previous file with comments | « webrtc/modules/audio_device/ios/voice_processing_audio_unit.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698