webrtc_m130/sdk/objc/native/src/objc_audio_device_delegate.mm
Yury Yaroshevich 9a0a6a198e Reland "ObjC ADM: record/play implementation via RTCAudioDevice [3/3]"
This is a reland of commit 2b9aaad58f56744f5c573c3b918fe072566598a5

Original change's description:
> ObjC ADM: record/play implementation via RTCAudioDevice [3/3]
>
> # Overview
> This CL chain exposes new API from ObjC WebRTC SDK to inject custom
> means to play and record audio. The goal of CLs is achieved by having
> additional implementation of `webrtc::AudioDeviceModule`
> called `ObjCAudioDeviceModule`. The feature
> of `ObjCAudioDeviceModule` is that it does not directly use any
> of OS-provided audio APIs like AudioUnit, AVAudioEngine, AudioQueue,
> AVCaptureSession etc. Instead it delegates communication with specific
> system audio API to user-injectable audio device instance which
> implements `RTCAudioDevice` protocol.
> `RTCAudioDevice` is new API added to ObC WebRTC SDK in the CL chain.
>
> # AudioDeviceBuffer
> `ObjCAudioDeviceModule` does conform to heavy `AudioDeviceModule`
> interface providing stubs for unrelated methods. It also implements
> common low-level management of audio device buffer, which glues audio
> PCM flow to/from WebRTC.
> `ObjCAudioDeviceModule` owns single `webrtc::AudioDeviceBuffer` which
> with the help of two `FineAudioBuffer` (one for recording and one for
> playout) is exchanged audio PCMs with user-provided `RTCAudioDevice`
> instance.
> `webrtc::AudioDeviceBuffer` is configured to work with specific audio:
> it has to know sample rate and channels count of audio being played and
> recorded. These formats could be different between playout and
> recording. `ObjCAudioDeviceModule` stores current audio  parameters
> applied  to `webrtc::AudioDeviceBuffer` as fields of
> type `webrtc::AudioParameters`. `RTCAudioDevice` has it's own variable
> audio parameters like sample rate, channels  count and IO buffer
> duration. The audio parameters of `RTCAudioDevice` must be kept in sync
> with audio parameters applied to `webrtc::AudioDeviceBuffer`, otherwise
> audio playout and recording will be corrupted: audio is sent only
> partially over the wire and/or audio is played with artifacts.
> `ObjCAudioDeviceModule` reads current `RTCAudioDevice` audio parameters
> when playout or recording is initialized. Whenever `RTCAudioDevice`
> audio parameters parameters are changed, there must be a notification to
> `ObjCAudioDeviceModule` to allow it to reconfigure
> it's `webrtc::AudioDeviceBuffer`. The notification is performed
> via `RTCAudioDeviceDelegate` object, which is provided
> by `ObjCAudioDeviceModule` during initialization of `RTCAudioDevice`.
>
> # Threading
> `ObjCAudioDeviceModule` is stick to same thread between initialization
> and termination. The only exception is two IO functions invoked by SDK
> user code presumably from real-time audio IO thread.
> Implementation of `RTCAudioDevice` may rely on the fact that all the
> methods of `RTCAudioDevice` are called on the same thread between
> initialization and termination. `ObjCAudioDeviceModule` is also expect
> that the implementation of `RTCAudioDevice` will call methods related
> to notification of audio parameters changes and audio interruption are
> invoked on `ObjCAudioDeviceModule` thread. To facilitate this
> requirement `RTCAudioDeviceDelegate` provides two functions to execute
> sync and async block on `ObjCAudioDeviceModule` thread.
> Async block could be useful when handling audio session notifications to
> dispatch whole block re-configuring audio objects used
> by `RTCAudioDevice` implementation.
> Sync block could be used to make sure changes to audio parameters
> of ADB owned by `ObjCAudioDeviceModule` are notified, before interrupted
> playout/recording restarted.
>
> Bug: webrtc:14193
> Change-Id: I5587ec6bbee3cf02bad70dd59b822feb0ada7f86
> Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/269006
> Reviewed-by: Henrik Andreasson <henrika@google.com>
> Commit-Queue: Yury Yarashevich <yura.yaroshevich@gmail.com>
> Reviewed-by: Peter Hanspers <peterhanspers@webrtc.org>
> Reviewed-by: Henrik Andreassson <henrika@webrtc.org>
> Reviewed-by: Tomas Gunnarsson <tommi@webrtc.org>
> Cr-Commit-Position: refs/heads/main@{#37928}

Bug: webrtc:14193
Change-Id: Iaf950d24bb2394a20e50421d5122f72ce46ae840
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/273380
Commit-Queue: Tomas Gunnarsson <tommi@webrtc.org>
Reviewed-by: Tomas Gunnarsson <tommi@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#37946}
2022-08-30 11:26:41 +00:00

195 lines
6.6 KiB
Plaintext

/*
* Copyright (c) 2022 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#import <AudioUnit/AudioUnit.h>
#import <Foundation/Foundation.h>
#import "objc_audio_device.h"
#import "objc_audio_device_delegate.h"
#include "api/make_ref_counted.h"
#include "api/ref_counted_base.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include "rtc_base/thread.h"
namespace {
constexpr double kPreferredInputSampleRate = 48000.0;
constexpr double kPreferredOutputSampleRate = 48000.0;
// WebRTC processes audio in chunks of 10ms. Preferring 20ms audio chunks
// is a compromize between performance and power consumption.
constexpr NSTimeInterval kPeferredInputIOBufferDuration = 0.02;
constexpr NSTimeInterval kPeferredOutputIOBufferDuration = 0.02;
class AudioDeviceDelegateImpl final : public rtc::RefCountedNonVirtual<AudioDeviceDelegateImpl> {
public:
AudioDeviceDelegateImpl(
rtc::scoped_refptr<webrtc::objc_adm::ObjCAudioDeviceModule> audio_device_module,
rtc::Thread* thread)
: audio_device_module_(audio_device_module), thread_(thread) {
RTC_DCHECK(audio_device_module_);
RTC_DCHECK(thread_);
}
webrtc::objc_adm::ObjCAudioDeviceModule* audio_device_module() const {
return audio_device_module_.get();
}
rtc::Thread* thread() const { return thread_; }
void reset_audio_device_module() { audio_device_module_ = nullptr; }
private:
rtc::scoped_refptr<webrtc::objc_adm::ObjCAudioDeviceModule> audio_device_module_;
rtc::Thread* thread_;
};
} // namespace
@implementation ObjCAudioDeviceDelegate {
rtc::scoped_refptr<AudioDeviceDelegateImpl> impl_;
}
@synthesize getPlayoutData = getPlayoutData_;
@synthesize deliverRecordedData = deliverRecordedData_;
@synthesize preferredInputSampleRate = preferredInputSampleRate_;
@synthesize preferredInputIOBufferDuration = preferredInputIOBufferDuration_;
@synthesize preferredOutputSampleRate = preferredOutputSampleRate_;
@synthesize preferredOutputIOBufferDuration = preferredOutputIOBufferDuration_;
- (instancetype)initWithAudioDeviceModule:
(rtc::scoped_refptr<webrtc::objc_adm::ObjCAudioDeviceModule>)audioDeviceModule
audioDeviceThread:(rtc::Thread*)thread {
RTC_DCHECK_RUN_ON(thread);
if (self = [super init]) {
impl_ = rtc::make_ref_counted<AudioDeviceDelegateImpl>(audioDeviceModule, thread);
preferredInputSampleRate_ = kPreferredInputSampleRate;
preferredInputIOBufferDuration_ = kPeferredInputIOBufferDuration;
preferredOutputSampleRate_ = kPreferredOutputSampleRate;
preferredOutputIOBufferDuration_ = kPeferredOutputIOBufferDuration;
rtc::scoped_refptr<AudioDeviceDelegateImpl> playout_delegate = impl_;
getPlayoutData_ = ^OSStatus(AudioUnitRenderActionFlags* _Nonnull actionFlags,
const AudioTimeStamp* _Nonnull timestamp,
NSInteger inputBusNumber,
UInt32 frameCount,
AudioBufferList* _Nonnull outputData) {
webrtc::objc_adm::ObjCAudioDeviceModule* audio_device =
playout_delegate->audio_device_module();
if (audio_device) {
return audio_device->OnGetPlayoutData(
actionFlags, timestamp, inputBusNumber, frameCount, outputData);
} else {
*actionFlags |= kAudioUnitRenderAction_OutputIsSilence;
RTC_LOG(LS_VERBOSE) << "No alive audio device";
return noErr;
}
};
rtc::scoped_refptr<AudioDeviceDelegateImpl> record_delegate = impl_;
deliverRecordedData_ =
^OSStatus(AudioUnitRenderActionFlags* _Nonnull actionFlags,
const AudioTimeStamp* _Nonnull timestamp,
NSInteger inputBusNumber,
UInt32 frameCount,
const AudioBufferList* _Nullable inputData,
void* renderContext,
RTC_OBJC_TYPE(RTCAudioDeviceRenderRecordedDataBlock) _Nullable renderBlock) {
webrtc::objc_adm::ObjCAudioDeviceModule* audio_device =
record_delegate->audio_device_module();
if (audio_device) {
return audio_device->OnDeliverRecordedData(actionFlags,
timestamp,
inputBusNumber,
frameCount,
inputData,
renderContext,
renderBlock);
} else {
RTC_LOG(LS_VERBOSE) << "No alive audio device";
return noErr;
}
};
}
return self;
}
- (void)notifyAudioInputParametersChange {
RTC_DCHECK_RUN_ON(impl_->thread());
webrtc::objc_adm::ObjCAudioDeviceModule* audio_device_module = impl_->audio_device_module();
if (audio_device_module) {
audio_device_module->HandleAudioInputParametersChange();
}
}
- (void)notifyAudioOutputParametersChange {
RTC_DCHECK_RUN_ON(impl_->thread());
webrtc::objc_adm::ObjCAudioDeviceModule* audio_device_module = impl_->audio_device_module();
if (audio_device_module) {
audio_device_module->HandleAudioOutputParametersChange();
}
}
- (void)notifyAudioInputInterrupted {
RTC_DCHECK_RUN_ON(impl_->thread());
webrtc::objc_adm::ObjCAudioDeviceModule* audio_device_module = impl_->audio_device_module();
if (audio_device_module) {
audio_device_module->HandleAudioInputInterrupted();
}
}
- (void)notifyAudioOutputInterrupted {
RTC_DCHECK_RUN_ON(impl_->thread());
webrtc::objc_adm::ObjCAudioDeviceModule* audio_device_module = impl_->audio_device_module();
if (audio_device_module) {
audio_device_module->HandleAudioOutputInterrupted();
}
}
- (void)dispatchAsync:(dispatch_block_t)block {
rtc::Thread* thread = impl_->thread();
RTC_DCHECK(thread);
thread->PostTask([block] {
@autoreleasepool {
block();
}
});
}
- (void)dispatchSync:(dispatch_block_t)block {
rtc::Thread* thread = impl_->thread();
RTC_DCHECK(thread);
if (thread->IsCurrent()) {
@autoreleasepool {
block();
}
} else {
thread->Invoke<void>(RTC_FROM_HERE, [block] {
@autoreleasepool {
block();
}
});
}
}
- (void)resetAudioDeviceModule {
RTC_DCHECK_RUN_ON(impl_->thread());
impl_->reset_audio_device_module();
}
@end