Repo created

This commit is contained in:
Fr4nz D13trich 2025-11-22 14:04:28 +01:00
parent 81b91f4139
commit f8c34fa5ee
22732 changed files with 4815320 additions and 2 deletions

View file

@ -0,0 +1,48 @@
#ifndef TGCALLS_CALL_AUDIO_TONE_H_
#define TGCALLS_CALL_AUDIO_TONE_H_
#include <vector>
namespace tgcalls {
class CallAudioTone {
public:
CallAudioTone(std::vector<int16_t> &&samples, int sampleRate, int loopCount) :
_samples(std::move(samples)), _sampleRate(sampleRate), _loopCount(loopCount) {
}
public:
std::vector<int16_t> const samples() const {
return _samples;
}
int sampleRate() const {
return _sampleRate;
}
int loopCount() const {
return _loopCount;
}
void setLoopCount(int loopCount) {
_loopCount = loopCount;
}
size_t offset() const {
return _offset;
}
void setOffset(size_t offset) {
_offset = offset;
}
private:
std::vector<int16_t> _samples;
int _sampleRate = 48000;
int _loopCount = 1;
size_t _offset = 0;
};
}
#endif // TGCALLS_CALL_AUDIO_TONE_H_

View file

@ -0,0 +1,180 @@
/*
* Copyright 2016 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 "RTCAudioSession+Private.h"
#import "RTCAudioSessionConfiguration.h"
#import "base/RTCLogging.h"
@implementation RTC_OBJC_TYPE (RTCAudioSession)
(Configuration)
- (BOOL)setConfiguration : (RTC_OBJC_TYPE(RTCAudioSessionConfiguration) *)configuration error
: (NSError **)outError disableRecording:(BOOL)disableRecording {
return [self setConfiguration:configuration
active:NO
shouldSetActive:NO
error:outError
disableRecording:disableRecording];
}
- (BOOL)setConfiguration:(RTC_OBJC_TYPE(RTCAudioSessionConfiguration) *)configuration
active:(BOOL)active
error:(NSError **)outError disableRecording:(BOOL)disableRecording {
return [self setConfiguration:configuration
active:active
shouldSetActive:YES
error:outError
disableRecording:disableRecording];
}
#pragma mark - Private
- (BOOL)setConfiguration:(RTC_OBJC_TYPE(RTCAudioSessionConfiguration) *)configuration
active:(BOOL)active
shouldSetActive:(BOOL)shouldSetActive
error:(NSError **)outError disableRecording:(BOOL)disableRecording {
NSParameterAssert(configuration);
if (outError) {
*outError = nil;
}
// Provide an error even if there isn't one so we can log it. We will not
// return immediately on error in this function and instead try to set
// everything we can.
NSError *error = nil;
if (!disableRecording) {
if (self.category != configuration.category ||
self.categoryOptions != configuration.categoryOptions) {
NSError *categoryError = nil;
if (![self setCategory:configuration.category
withOptions:configuration.categoryOptions
error:&categoryError]) {
RTCLogError(@"Failed to set category: %@",
categoryError.localizedDescription);
error = categoryError;
} else {
RTCLog(@"Set category to: %@", configuration.category);
}
}
if (self.mode != configuration.mode) {
NSError *modeError = nil;
if (![self setMode:configuration.mode error:&modeError]) {
RTCLogError(@"Failed to set mode: %@",
modeError.localizedDescription);
error = modeError;
} else {
RTCLog(@"Set mode to: %@", configuration.mode);
}
}
// Sometimes category options don't stick after setting mode.
if (self.categoryOptions != configuration.categoryOptions) {
NSError *categoryError = nil;
if (![self setCategory:configuration.category
withOptions:configuration.categoryOptions
error:&categoryError]) {
RTCLogError(@"Failed to set category options: %@",
categoryError.localizedDescription);
error = categoryError;
} else {
RTCLog(@"Set category options to: %ld",
(long)configuration.categoryOptions);
}
}
}
if (self.preferredSampleRate != configuration.sampleRate) {
NSError *sampleRateError = nil;
if (![self setPreferredSampleRate:configuration.sampleRate
error:&sampleRateError]) {
RTCLogError(@"Failed to set preferred sample rate: %@",
sampleRateError.localizedDescription);
if (!self.ignoresPreferredAttributeConfigurationErrors) {
error = sampleRateError;
}
} else {
RTCLog(@"Set preferred sample rate to: %.2f",
configuration.sampleRate);
}
}
if (self.preferredIOBufferDuration != configuration.ioBufferDuration) {
NSError *bufferDurationError = nil;
if (![self setPreferredIOBufferDuration:configuration.ioBufferDuration
error:&bufferDurationError]) {
RTCLogError(@"Failed to set preferred IO buffer duration: %@",
bufferDurationError.localizedDescription);
if (!self.ignoresPreferredAttributeConfigurationErrors) {
error = bufferDurationError;
}
} else {
RTCLog(@"Set preferred IO buffer duration to: %f",
configuration.ioBufferDuration);
}
}
if (shouldSetActive) {
NSError *activeError = nil;
if (![self setActive:active error:&activeError]) {
RTCLogError(@"Failed to setActive to %d: %@",
active, activeError.localizedDescription);
error = activeError;
}
}
if (self.isActive &&
// TODO(tkchin): Figure out which category/mode numChannels is valid for.
[self.mode isEqualToString:AVAudioSessionModeVoiceChat]) {
// Try to set the preferred number of hardware audio channels. These calls
// must be done after setting the audio sessions category and mode and
// activating the session.
NSInteger inputNumberOfChannels = configuration.inputNumberOfChannels;
if (self.inputNumberOfChannels != inputNumberOfChannels) {
NSError *inputChannelsError = nil;
if (![self setPreferredInputNumberOfChannels:inputNumberOfChannels
error:&inputChannelsError]) {
RTCLogError(@"Failed to set preferred input number of channels: %@",
inputChannelsError.localizedDescription);
if (!self.ignoresPreferredAttributeConfigurationErrors) {
error = inputChannelsError;
}
} else {
RTCLog(@"Set input number of channels to: %ld",
(long)inputNumberOfChannels);
}
}
NSInteger outputNumberOfChannels = configuration.outputNumberOfChannels;
if (self.outputNumberOfChannels != outputNumberOfChannels) {
NSError *outputChannelsError = nil;
if (![self setPreferredOutputNumberOfChannels:outputNumberOfChannels
error:&outputChannelsError]) {
RTCLogError(@"Failed to set preferred output number of channels: %@",
outputChannelsError.localizedDescription);
if (!self.ignoresPreferredAttributeConfigurationErrors) {
error = outputChannelsError;
}
} else {
RTCLog(@"Set output number of channels to: %ld",
(long)outputNumberOfChannels);
}
}
}
if (outError) {
*outError = error;
}
return error == nil;
}
@end

View file

@ -0,0 +1,95 @@
/*
* Copyright 2016 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 "RTCAudioSession.h"
NS_ASSUME_NONNULL_BEGIN
@class RTC_OBJC_TYPE(RTCAudioSessionConfiguration);
@interface RTC_OBJC_TYPE (RTCAudioSession)
()
/** Number of times setActive:YES has succeeded without a balanced call to
* setActive:NO.
*/
@property(nonatomic, readonly) int activationCount;
/** The number of times `beginWebRTCSession` was called without a balanced call
* to `endWebRTCSession`.
*/
@property(nonatomic, readonly) int webRTCSessionCount;
/** Convenience BOOL that checks useManualAudio and isAudioEnebled. */
@property(readonly) BOOL canPlayOrRecord;
/** Tracks whether we have been sent an interruption event that hasn't been matched by either an
* interrupted end event or a foreground event.
*/
@property(nonatomic, assign) BOOL isInterrupted;
/** Adds the delegate to the list of delegates, and places it at the front of
* the list. This delegate will be notified before other delegates of
* audio events.
*/
- (void)pushDelegate:(id<RTC_OBJC_TYPE(RTCAudioSessionDelegate)>)delegate;
/** Signals RTCAudioSession that a WebRTC session is about to begin and
* audio configuration is needed. Will configure the audio session for WebRTC
* if not already configured and if configuration is not delayed.
* Successful calls must be balanced by a call to endWebRTCSession.
*/
- (BOOL)beginWebRTCSession:(NSError **)outError;
/** Signals RTCAudioSession that a WebRTC session is about to end and audio
* unconfiguration is needed. Will unconfigure the audio session for WebRTC
* if this is the last unmatched call and if configuration is not delayed.
*/
- (BOOL)endWebRTCSession:(NSError **)outError;
/** Configure the audio session for WebRTC. This call will fail if the session
* is already configured. On other failures, we will attempt to restore the
* previously used audio session configuration.
* `lockForConfiguration` must be called first.
* Successful calls to configureWebRTCSession must be matched by calls to
* `unconfigureWebRTCSession`.
*/
- (BOOL)configureWebRTCSession:(NSError **)outError disableRecording:(BOOL)disableRecording;
/** Unconfigures the session for WebRTC. This will attempt to restore the
* audio session to the settings used before `configureWebRTCSession` was
* called.
* `lockForConfiguration` must be called first.
*/
- (BOOL)unconfigureWebRTCSession:(NSError **)outError;
/** Returns a configuration error with the given description. */
- (NSError *)configurationErrorWithDescription:(NSString *)description;
/** Notifies the receiver that a playout glitch was detected. */
- (void)notifyDidDetectPlayoutGlitch:(int64_t)totalNumberOfGlitches;
/** Notifies the receiver that there was an error when starting an audio unit. */
- (void)notifyAudioUnitStartFailedWithError:(OSStatus)error;
// Properties and methods for tests.
- (void)notifyDidBeginInterruption;
- (void)notifyDidEndInterruptionWithShouldResumeSession:(BOOL)shouldResumeSession;
- (void)notifyDidChangeRouteWithReason:(AVAudioSessionRouteChangeReason)reason
previousRoute:(AVAudioSessionRouteDescription *)previousRoute;
- (void)notifyMediaServicesWereLost;
- (void)notifyMediaServicesWereReset;
- (void)notifyDidChangeCanPlayOrRecord:(BOOL)canPlayOrRecord;
- (void)notifyDidStartPlayOrRecord;
- (void)notifyDidStopPlayOrRecord;
@end
NS_ASSUME_NONNULL_END

View file

@ -0,0 +1,266 @@
/*
* Copyright 2016 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 <AVFoundation/AVFoundation.h>
#import <Foundation/Foundation.h>
#import "RTCMacros.h"
NS_ASSUME_NONNULL_BEGIN
extern NSString *const kRTCAudioSessionErrorDomain;
/** Method that requires lock was called without lock. */
extern NSInteger const kRTCAudioSessionErrorLockRequired;
/** Unknown configuration error occurred. */
extern NSInteger const kRTCAudioSessionErrorConfiguration;
@class RTC_OBJC_TYPE(RTCAudioSession);
@class RTC_OBJC_TYPE(RTCAudioSessionConfiguration);
// Surfaces AVAudioSession events. WebRTC will listen directly for notifications
// from AVAudioSession and handle them before calling these delegate methods,
// at which point applications can perform additional processing if required.
RTC_OBJC_EXPORT
@protocol RTC_OBJC_TYPE
(RTCAudioSessionDelegate)<NSObject>
@optional
/** Called on a system notification thread when AVAudioSession starts an
* interruption event.
*/
- (void)audioSessionDidBeginInterruption:(RTC_OBJC_TYPE(RTCAudioSession) *)session;
/** Called on a system notification thread when AVAudioSession ends an
* interruption event.
*/
- (void)audioSessionDidEndInterruption:(RTC_OBJC_TYPE(RTCAudioSession) *)session
shouldResumeSession:(BOOL)shouldResumeSession;
/** Called on a system notification thread when AVAudioSession changes the
* route.
*/
- (void)audioSessionDidChangeRoute:(RTC_OBJC_TYPE(RTCAudioSession) *)session
reason:(AVAudioSessionRouteChangeReason)reason
previousRoute:(AVAudioSessionRouteDescription *)previousRoute;
/** Called on a system notification thread when AVAudioSession media server
* terminates.
*/
- (void)audioSessionMediaServerTerminated:(RTC_OBJC_TYPE(RTCAudioSession) *)session;
/** Called on a system notification thread when AVAudioSession media server
* restarts.
*/
- (void)audioSessionMediaServerReset:(RTC_OBJC_TYPE(RTCAudioSession) *)session;
// TODO(tkchin): Maybe handle SilenceSecondaryAudioHintNotification.
- (void)audioSession:(RTC_OBJC_TYPE(RTCAudioSession) *)session
didChangeCanPlayOrRecord:(BOOL)canPlayOrRecord;
/** Called on a WebRTC thread when the audio device is notified to begin
* playback or recording.
*/
- (void)audioSessionDidStartPlayOrRecord:(RTC_OBJC_TYPE(RTCAudioSession) *)session;
/** Called on a WebRTC thread when the audio device is notified to stop
* playback or recording.
*/
- (void)audioSessionDidStopPlayOrRecord:(RTC_OBJC_TYPE(RTCAudioSession) *)session;
/** Called when the AVAudioSession output volume value changes. */
- (void)audioSession:(RTC_OBJC_TYPE(RTCAudioSession) *)audioSession
didChangeOutputVolume:(float)outputVolume;
/** Called when the audio device detects a playout glitch. The argument is the
* number of glitches detected so far in the current audio playout session.
*/
- (void)audioSession:(RTC_OBJC_TYPE(RTCAudioSession) *)audioSession
didDetectPlayoutGlitch:(int64_t)totalNumberOfGlitches;
/** Called when the audio session is about to change the active state.
*/
- (void)audioSession:(RTC_OBJC_TYPE(RTCAudioSession) *)audioSession willSetActive:(BOOL)active;
/** Called after the audio session sucessfully changed the active state.
*/
- (void)audioSession:(RTC_OBJC_TYPE(RTCAudioSession) *)audioSession didSetActive:(BOOL)active;
/** Called after the audio session failed to change the active state.
*/
- (void)audioSession:(RTC_OBJC_TYPE(RTCAudioSession) *)audioSession
failedToSetActive:(BOOL)active
error:(NSError *)error;
- (void)audioSession:(RTC_OBJC_TYPE(RTCAudioSession) *)audioSession
audioUnitStartFailedWithError:(NSError *)error;
@end
/** This is a protocol used to inform RTCAudioSession when the audio session
* activation state has changed outside of RTCAudioSession. The current known use
* case of this is when CallKit activates the audio session for the application
*/
RTC_OBJC_EXPORT
@protocol RTC_OBJC_TYPE
(RTCAudioSessionActivationDelegate)<NSObject>
/** Called when the audio session is activated outside of the app by iOS. */
- (void)audioSessionDidActivate : (AVAudioSession *)session;
/** Called when the audio session is deactivated outside of the app by iOS. */
- (void)audioSessionDidDeactivate:(AVAudioSession *)session;
@end
/** Proxy class for AVAudioSession that adds a locking mechanism similar to
* AVCaptureDevice. This is used to that interleaving configurations between
* WebRTC and the application layer are avoided.
*
* RTCAudioSession also coordinates activation so that the audio session is
* activated only once. See `setActive:error:`.
*/
RTC_OBJC_EXPORT
@interface RTC_OBJC_TYPE (RTCAudioSession) : NSObject <RTC_OBJC_TYPE(RTCAudioSessionActivationDelegate)>
/** Convenience property to access the AVAudioSession singleton. Callers should
* not call setters on AVAudioSession directly, but other method invocations
* are fine.
*/
@property(nonatomic, readonly) AVAudioSession *session;
/** Our best guess at whether the session is active based on results of calls to
* AVAudioSession.
*/
@property(nonatomic, readonly) BOOL isActive;
/** If YES, WebRTC will not initialize the audio unit automatically when an
* audio track is ready for playout or recording. Instead, applications should
* call setIsAudioEnabled. If NO, WebRTC will initialize the audio unit
* as soon as an audio track is ready for playout or recording.
*/
@property(nonatomic, assign) BOOL useManualAudio;
/** This property is only effective if useManualAudio is YES.
* Represents permission for WebRTC to initialize the VoIP audio unit.
* When set to NO, if the VoIP audio unit used by WebRTC is active, it will be
* stopped and uninitialized. This will stop incoming and outgoing audio.
* When set to YES, WebRTC will initialize and start the audio unit when it is
* needed (e.g. due to establishing an audio connection).
* This property was introduced to work around an issue where if an AVPlayer is
* playing audio while the VoIP audio unit is initialized, its audio would be
* either cut off completely or played at a reduced volume. By preventing
* the audio unit from being initialized until after the audio has completed,
* we are able to prevent the abrupt cutoff.
*/
@property(nonatomic, assign) BOOL isAudioEnabled;
// Proxy properties.
@property(readonly) NSString *category;
@property(readonly) AVAudioSessionCategoryOptions categoryOptions;
@property(readonly) NSString *mode;
@property(readonly) BOOL secondaryAudioShouldBeSilencedHint;
@property(readonly) AVAudioSessionRouteDescription *currentRoute;
@property(readonly) NSInteger maximumInputNumberOfChannels;
@property(readonly) NSInteger maximumOutputNumberOfChannels;
@property(readonly) float inputGain;
@property(readonly) BOOL inputGainSettable;
@property(readonly) BOOL inputAvailable;
@property(readonly, nullable) NSArray<AVAudioSessionDataSourceDescription *> *inputDataSources;
@property(readonly, nullable) AVAudioSessionDataSourceDescription *inputDataSource;
@property(readonly, nullable) NSArray<AVAudioSessionDataSourceDescription *> *outputDataSources;
@property(readonly, nullable) AVAudioSessionDataSourceDescription *outputDataSource;
@property(readonly) double sampleRate;
@property(readonly) double preferredSampleRate;
@property(readonly) NSInteger inputNumberOfChannels;
@property(readonly) NSInteger outputNumberOfChannels;
@property(readonly) float outputVolume;
@property(readonly) NSTimeInterval inputLatency;
@property(readonly) NSTimeInterval outputLatency;
@property(readonly) NSTimeInterval IOBufferDuration;
@property(readonly) NSTimeInterval preferredIOBufferDuration;
/**
When YES, calls to -setConfiguration:error: and -setConfiguration:active:error: ignore errors in
configuring the audio session's "preferred" attributes (e.g. preferredInputNumberOfChannels).
Typically, configurations to preferred attributes are optimizations, and ignoring this type of
configuration error allows code flow to continue along the happy path when these optimization are
not available. The default value of this property is NO.
*/
@property(nonatomic) BOOL ignoresPreferredAttributeConfigurationErrors;
/** Default constructor. */
+ (instancetype)sharedInstance;
- (instancetype)init NS_UNAVAILABLE;
/** Adds a delegate, which is held weakly. */
- (void)addDelegate:(id<RTC_OBJC_TYPE(RTCAudioSessionDelegate)>)delegate;
/** Removes an added delegate. */
- (void)removeDelegate:(id<RTC_OBJC_TYPE(RTCAudioSessionDelegate)>)delegate;
/** Request exclusive access to the audio session for configuration. This call
* will block if the lock is held by another object.
*/
- (void)lockForConfiguration;
/** Relinquishes exclusive access to the audio session. */
- (void)unlockForConfiguration;
/** If `active`, activates the audio session if it isn't already active.
* Successful calls must be balanced with a setActive:NO when activation is no
* longer required. If not `active`, deactivates the audio session if one is
* active and this is the last balanced call. When deactivating, the
* AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation option is passed to
* AVAudioSession.
*/
- (BOOL)setActive:(BOOL)active error:(NSError **)outError;
// The following methods are proxies for the associated methods on
// AVAudioSession. `lockForConfiguration` must be called before using them
// otherwise they will fail with kRTCAudioSessionErrorLockRequired.
- (BOOL)setCategory:(NSString *)category
withOptions:(AVAudioSessionCategoryOptions)options
error:(NSError **)outError;
- (BOOL)setMode:(NSString *)mode error:(NSError **)outError;
- (BOOL)setInputGain:(float)gain error:(NSError **)outError;
- (BOOL)setPreferredSampleRate:(double)sampleRate error:(NSError **)outError;
- (BOOL)setPreferredIOBufferDuration:(NSTimeInterval)duration error:(NSError **)outError;
- (BOOL)setPreferredInputNumberOfChannels:(NSInteger)count error:(NSError **)outError;
- (BOOL)setPreferredOutputNumberOfChannels:(NSInteger)count error:(NSError **)outError;
- (BOOL)overrideOutputAudioPort:(AVAudioSessionPortOverride)portOverride error:(NSError **)outError;
- (BOOL)setPreferredInput:(AVAudioSessionPortDescription *)inPort error:(NSError **)outError;
- (BOOL)setInputDataSource:(AVAudioSessionDataSourceDescription *)dataSource
error:(NSError **)outError;
- (BOOL)setOutputDataSource:(AVAudioSessionDataSourceDescription *)dataSource
error:(NSError **)outError;
@end
@interface RTC_OBJC_TYPE (RTCAudioSession)
(Configuration)
/** Applies the configuration to the current session. Attempts to set all
* properties even if previous ones fail. Only the last error will be
* returned.
* `lockForConfiguration` must be called first.
*/
- (BOOL)setConfiguration : (RTC_OBJC_TYPE(RTCAudioSessionConfiguration) *)configuration error
: (NSError **)outError;
/** Convenience method that calls both setConfiguration and setActive.
* `lockForConfiguration` must be called first.
*/
- (BOOL)setConfiguration:(RTC_OBJC_TYPE(RTCAudioSessionConfiguration) *)configuration
active:(BOOL)active
error:(NSError **)outError
disableRecording:(BOOL)disableRecording;
@end
NS_ASSUME_NONNULL_END

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,47 @@
/*
* Copyright 2016 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 <AVFoundation/AVFoundation.h>
#import <Foundation/Foundation.h>
#import "RTCMacros.h"
NS_ASSUME_NONNULL_BEGIN
RTC_EXTERN const double kRTCAudioSessionHighPerformanceSampleRate;
RTC_EXTERN const double kRTCAudioSessionLowComplexitySampleRate;
RTC_EXTERN const double kRTCAudioSessionHighPerformanceIOBufferDuration;
RTC_EXTERN const double kRTCAudioSessionLowComplexityIOBufferDuration;
// Struct to hold configuration values.
RTC_OBJC_EXPORT
@interface RTC_OBJC_TYPE (RTCAudioSessionConfiguration) : NSObject
@property(nonatomic, strong) NSString *category;
@property(nonatomic, assign) AVAudioSessionCategoryOptions categoryOptions;
@property(nonatomic, strong) NSString *mode;
@property(nonatomic, assign) double sampleRate;
@property(nonatomic, assign) NSTimeInterval ioBufferDuration;
@property(nonatomic, assign) NSInteger inputNumberOfChannels;
@property(nonatomic, assign) NSInteger outputNumberOfChannels;
/** Initializes configuration to defaults. */
- (instancetype)init NS_DESIGNATED_INITIALIZER;
/** Returns the current configuration of the audio session. */
+ (instancetype)currentConfiguration;
/** Returns the configuration that WebRTC needs. */
+ (instancetype)webRTCConfiguration;
/** Provide a way to override the default configuration. */
+ (void)setWebRTCConfiguration:(RTC_OBJC_TYPE(RTCAudioSessionConfiguration) *)configuration;
@end
NS_ASSUME_NONNULL_END

View file

@ -0,0 +1,133 @@
/*
* Copyright 2016 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 "RTCAudioSessionConfiguration.h"
#import "RTCAudioSession.h"
#import "helpers/RTCDispatcher.h"
#import "helpers/UIDevice+RTCDevice.h"
// Try to use mono to save resources. Also avoids channel format conversion
// in the I/O audio unit. Initial tests have shown that it is possible to use
// mono natively for built-in microphones and for BT headsets but not for
// wired headsets. Wired headsets only support stereo as native channel format
// but it is a low cost operation to do a format conversion to mono in the
// audio unit. Hence, we will not hit a RTC_CHECK in
// VerifyAudioParametersForActiveAudioSession() for a mismatch between the
// preferred number of channels and the actual number of channels.
const int kRTCAudioSessionPreferredNumberOfChannels = 1;
// Preferred hardware sample rate (unit is in Hertz). The client sample rate
// will be set to this value as well to avoid resampling the the audio unit's
// format converter. Note that, some devices, e.g. BT headsets, only supports
// 8000Hz as native sample rate.
const double kRTCAudioSessionHighPerformanceSampleRate = 48000.0;
// A lower sample rate will be used for devices with only one core
// (e.g. iPhone 4). The goal is to reduce the CPU load of the application.
const double kRTCAudioSessionLowComplexitySampleRate = 16000.0;
// Use a hardware I/O buffer size (unit is in seconds) that matches the 10ms
// size used by WebRTC. The exact actual size will differ between devices.
// Example: using 48kHz on iPhone 6 results in a native buffer size of
// ~10.6667ms or 512 audio frames per buffer. The FineAudioBuffer instance will
// take care of any buffering required to convert between native buffers and
// buffers used by WebRTC. It is beneficial for the performance if the native
// size is as an even multiple of 10ms as possible since it results in "clean"
// callback sequence without bursts of callbacks back to back.
const double kRTCAudioSessionHighPerformanceIOBufferDuration = 0.02;
// Use a larger buffer size on devices with only one core (e.g. iPhone 4).
// It will result in a lower CPU consumption at the cost of a larger latency.
// The size of 60ms is based on instrumentation that shows a significant
// reduction in CPU load compared with 10ms on low-end devices.
// TODO(henrika): monitor this size and determine if it should be modified.
const double kRTCAudioSessionLowComplexityIOBufferDuration = 0.06;
static RTC_OBJC_TYPE(RTCAudioSessionConfiguration) *gWebRTCConfiguration = nil;
@implementation RTC_OBJC_TYPE (RTCAudioSessionConfiguration)
@synthesize category = _category;
@synthesize categoryOptions = _categoryOptions;
@synthesize mode = _mode;
@synthesize sampleRate = _sampleRate;
@synthesize ioBufferDuration = _ioBufferDuration;
@synthesize inputNumberOfChannels = _inputNumberOfChannels;
@synthesize outputNumberOfChannels = _outputNumberOfChannels;
- (instancetype)init {
if (self = [super init]) {
// Use a category which supports simultaneous recording and playback.
// By default, using this category implies that our apps audio is
// nonmixable, hence activating the session will interrupt any other
// audio sessions which are also nonmixable.
_category = AVAudioSessionCategoryPlayAndRecord;
_categoryOptions = AVAudioSessionCategoryOptionAllowBluetooth;
// Specify mode for two-way voice communication (e.g. VoIP).
_mode = AVAudioSessionModeVoiceChat;
// Set the session's sample rate or the hardware sample rate.
// It is essential that we use the same sample rate as stream format
// to ensure that the I/O unit does not have to do sample rate conversion.
// Set the preferred audio I/O buffer duration, in seconds.
NSUInteger processorCount = [NSProcessInfo processInfo].processorCount;
// Use best sample rate and buffer duration if the CPU has more than one
// core.
if (processorCount > 1) {
_sampleRate = kRTCAudioSessionHighPerformanceSampleRate;
_ioBufferDuration = kRTCAudioSessionHighPerformanceIOBufferDuration;
} else {
_sampleRate = kRTCAudioSessionLowComplexitySampleRate;
_ioBufferDuration = kRTCAudioSessionLowComplexityIOBufferDuration;
}
// We try to use mono in both directions to save resources and format
// conversions in the audio unit. Some devices does only support stereo;
// e.g. wired headset on iPhone 6.
// TODO(henrika): add support for stereo if needed.
_inputNumberOfChannels = kRTCAudioSessionPreferredNumberOfChannels;
_outputNumberOfChannels = kRTCAudioSessionPreferredNumberOfChannels;
}
return self;
}
+ (void)initialize {
gWebRTCConfiguration = [[self alloc] init];
}
+ (instancetype)currentConfiguration {
RTC_OBJC_TYPE(RTCAudioSession) *session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance];
RTC_OBJC_TYPE(RTCAudioSessionConfiguration) *config =
[[RTC_OBJC_TYPE(RTCAudioSessionConfiguration) alloc] init];
config.category = session.category;
config.categoryOptions = session.categoryOptions;
config.mode = session.mode;
config.sampleRate = session.sampleRate;
config.ioBufferDuration = session.IOBufferDuration;
config.inputNumberOfChannels = session.inputNumberOfChannels;
config.outputNumberOfChannels = session.outputNumberOfChannels;
return config;
}
+ (instancetype)webRTCConfiguration {
@synchronized(self) {
return (RTC_OBJC_TYPE(RTCAudioSessionConfiguration) *)gWebRTCConfiguration;
}
}
+ (void)setWebRTCConfiguration:(RTC_OBJC_TYPE(RTCAudioSessionConfiguration) *)configuration {
@synchronized(self) {
gWebRTCConfiguration = configuration;
}
}
@end

View file

@ -0,0 +1,33 @@
/*
* Copyright 2018 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 "RTCAudioSession.h"
NS_ASSUME_NONNULL_BEGIN
namespace webrtc {
class AudioSessionObserver;
}
/** Adapter that forwards RTCAudioSessionDelegate calls to the appropriate
* methods on the AudioSessionObserver.
*/
@interface RTCNativeAudioSessionDelegateAdapter : NSObject <RTC_OBJC_TYPE (RTCAudioSessionDelegate)>
- (instancetype)init NS_UNAVAILABLE;
/** `observer` is a raw pointer and should be kept alive
* for this object's lifetime.
*/
- (instancetype)initWithObserver:(webrtc::AudioSessionObserver *)observer NS_DESIGNATED_INITIALIZER;
@end
NS_ASSUME_NONNULL_END

View file

@ -0,0 +1,89 @@
/*
* Copyright 2018 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 "RTCNativeAudioSessionDelegateAdapter.h"
#include "sdk/objc/native/src/audio/audio_session_observer.h"
#import "base/RTCLogging.h"
@implementation RTCNativeAudioSessionDelegateAdapter {
webrtc::AudioSessionObserver *_observer;
}
- (instancetype)initWithObserver:(webrtc::AudioSessionObserver *)observer {
RTC_DCHECK(observer);
if (self = [super init]) {
_observer = observer;
}
return self;
}
#pragma mark - RTC_OBJC_TYPE(RTCAudioSessionDelegate)
- (void)audioSessionDidBeginInterruption:(RTC_OBJC_TYPE(RTCAudioSession) *)session {
_observer->OnInterruptionBegin();
}
- (void)audioSessionDidEndInterruption:(RTC_OBJC_TYPE(RTCAudioSession) *)session
shouldResumeSession:(BOOL)shouldResumeSession {
_observer->OnInterruptionEnd();
}
- (void)audioSessionDidChangeRoute:(RTC_OBJC_TYPE(RTCAudioSession) *)session
reason:(AVAudioSessionRouteChangeReason)reason
previousRoute:(AVAudioSessionRouteDescription *)previousRoute {
switch (reason) {
case AVAudioSessionRouteChangeReasonUnknown:
case AVAudioSessionRouteChangeReasonNewDeviceAvailable:
case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:
case AVAudioSessionRouteChangeReasonCategoryChange:
// It turns out that we see a category change (at least in iOS 9.2)
// when making a switch from a BT device to e.g. Speaker using the
// iOS Control Center and that we therefore must check if the sample
// rate has changed. And if so is the case, restart the audio unit.
case AVAudioSessionRouteChangeReasonOverride:
case AVAudioSessionRouteChangeReasonWakeFromSleep:
case AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory:
_observer->OnValidRouteChange();
break;
case AVAudioSessionRouteChangeReasonRouteConfigurationChange:
// The set of input and output ports has not changed, but their
// configuration has, e.g., a ports selected data source has
// changed. Ignore this type of route change since we are focusing
// on detecting headset changes.
RTCLog(@"Ignoring RouteConfigurationChange");
break;
}
}
- (void)audioSessionMediaServerTerminated:(RTC_OBJC_TYPE(RTCAudioSession) *)session {
}
- (void)audioSessionMediaServerReset:(RTC_OBJC_TYPE(RTCAudioSession) *)session {
}
- (void)audioSession:(RTC_OBJC_TYPE(RTCAudioSession) *)session
didChangeCanPlayOrRecord:(BOOL)canPlayOrRecord {
_observer->OnCanPlayOrRecordChange(canPlayOrRecord);
}
- (void)audioSessionDidStartPlayOrRecord:(RTC_OBJC_TYPE(RTCAudioSession) *)session {
}
- (void)audioSessionDidStopPlayOrRecord:(RTC_OBJC_TYPE(RTCAudioSession) *)session {
}
- (void)audioSession:(RTC_OBJC_TYPE(RTCAudioSession) *)audioSession
didChangeOutputVolume:(float)outputVolume {
_observer->OnChangedOutputVolume();
}
@end

View file

@ -0,0 +1,329 @@
/*
* Copyright (c) 2012 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.
*/
#ifndef TGCALLS_AUDIO_AUDIO_DEVICE_IOS_H_
#define TGCALLS_AUDIO_AUDIO_DEVICE_IOS_H_
#include <atomic>
#include <memory>
#include "api/scoped_refptr.h"
#include "api/sequence_checker.h"
#include "sdk/objc/native/src/audio/audio_session_observer.h"
#include "api/task_queue/pending_task_safety_flag.h"
#include "modules/audio_device/audio_device_generic.h"
#include "rtc_base/buffer.h"
#include "rtc_base/thread.h"
#include "rtc_base/thread_annotations.h"
#include "sdk/objc/base/RTCMacros.h"
#include "tgcalls_voice_processing_audio_unit.h"
#include "platform/darwin/iOS/CallAudioTone.h"
RTC_FWD_DECL_OBJC_CLASS(RTCNativeAudioSessionDelegateAdapter);
namespace webrtc {
class FineAudioBuffer;
namespace tgcalls_ios_adm {
// Implements full duplex 16-bit mono PCM audio support for iOS using a
// Voice-Processing (VP) I/O audio unit in Core Audio. The VP I/O audio unit
// supports audio echo cancellation. It also adds automatic gain control,
// adjustment of voice-processing quality and muting.
//
// An instance must be created and destroyed on one and the same thread.
// All supported public methods must also be called on the same thread.
// A thread checker will RTC_DCHECK if any supported method is called on an
// invalid thread.
//
// Recorded audio will be delivered on a real-time internal I/O thread in the
// audio unit. The audio unit will also ask for audio data to play out on this
// same thread.
class AudioDeviceIOS : public AudioDeviceGeneric,
public AudioSessionObserver,
public VoiceProcessingAudioUnitObserver {
public:
explicit AudioDeviceIOS(bool bypass_voice_processing, bool disable_recording, bool enableSystemMute, int numChannels);
~AudioDeviceIOS() override;
void AttachAudioBuffer(AudioDeviceBuffer* audioBuffer) override;
InitStatus Init() override;
int32_t Terminate() override;
bool Initialized() const override;
int32_t InitPlayout() override;
bool PlayoutIsInitialized() const override;
int32_t InitRecording() override;
bool RecordingIsInitialized() const override;
int32_t StartPlayout() override;
int32_t StopPlayout() override;
bool Playing() const override;
int32_t StartRecording() override;
int32_t StopRecording() override;
bool Recording() const override;
void setIsBufferPlaying(bool isBufferPlaying);
void setIsBufferRecording(bool isBufferRecording);
// These methods returns hard-coded delay values and not dynamic delay
// estimates. The reason is that iOS supports a built-in AEC and the WebRTC
// AEC will always be disabled in the Libjingle layer to avoid running two
// AEC implementations at the same time. And, it saves resources to avoid
// updating these delay values continuously.
// TODO(henrika): it would be possible to mark these two methods as not
// implemented since they are only called for A/V-sync purposes today and
// A/V-sync is not supported on iOS. However, we avoid adding error messages
// the log by using these dummy implementations instead.
int32_t PlayoutDelay(uint16_t& delayMS) const override;
// No implementation for playout underrun on iOS. We override it to avoid a
// periodic log that it isn't available from the base class.
int32_t GetPlayoutUnderrunCount() const override { return -1; }
// Native audio parameters stored during construction.
// These methods are unique for the iOS implementation.
int GetPlayoutAudioParameters(AudioParameters* params) const override;
int GetRecordAudioParameters(AudioParameters* params) const override;
// These methods are currently not fully implemented on iOS:
// See audio_device_not_implemented.cc for trivial implementations.
int32_t ActiveAudioLayer(
AudioDeviceModule::AudioLayer& audioLayer) const override;
int32_t PlayoutIsAvailable(bool& available) override;
int32_t RecordingIsAvailable(bool& available) override;
int16_t PlayoutDevices() override;
int16_t RecordingDevices() override;
int32_t PlayoutDeviceName(uint16_t index,
char name[kAdmMaxDeviceNameSize],
char guid[kAdmMaxGuidSize]) override;
int32_t RecordingDeviceName(uint16_t index,
char name[kAdmMaxDeviceNameSize],
char guid[kAdmMaxGuidSize]) override;
int32_t SetPlayoutDevice(uint16_t index) override;
int32_t SetPlayoutDevice(
AudioDeviceModule::WindowsDeviceType device) override;
int32_t SetRecordingDevice(uint16_t index) override;
int32_t SetRecordingDevice(
AudioDeviceModule::WindowsDeviceType device) override;
int32_t InitSpeaker() override;
bool SpeakerIsInitialized() const override;
int32_t InitMicrophone() override;
bool MicrophoneIsInitialized() const override;
int32_t SpeakerVolumeIsAvailable(bool& available) override;
int32_t SetSpeakerVolume(uint32_t volume) override;
int32_t SpeakerVolume(uint32_t& volume) const override;
int32_t MaxSpeakerVolume(uint32_t& maxVolume) const override;
int32_t MinSpeakerVolume(uint32_t& minVolume) const override;
int32_t MicrophoneVolumeIsAvailable(bool& available) override;
int32_t SetMicrophoneVolume(uint32_t volume) override;
int32_t MicrophoneVolume(uint32_t& volume) const override;
int32_t MaxMicrophoneVolume(uint32_t& maxVolume) const override;
int32_t MinMicrophoneVolume(uint32_t& minVolume) const override;
int32_t MicrophoneMuteIsAvailable(bool& available) override;
int32_t SetMicrophoneMute(bool enable) override;
int32_t MicrophoneMute(bool& enabled) const override;
int32_t SpeakerMuteIsAvailable(bool& available) override;
int32_t SetSpeakerMute(bool enable) override;
int32_t SpeakerMute(bool& enabled) const override;
int32_t StereoPlayoutIsAvailable(bool& available) override;
int32_t SetStereoPlayout(bool enable) override;
int32_t StereoPlayout(bool& enabled) const override;
int32_t StereoRecordingIsAvailable(bool& available) override;
int32_t SetStereoRecording(bool enable) override;
int32_t StereoRecording(bool& enabled) const override;
// AudioSessionObserver methods. May be called from any thread.
void OnInterruptionBegin() override;
void OnInterruptionEnd() override;
void OnValidRouteChange() override;
void OnCanPlayOrRecordChange(bool can_play_or_record) override;
void OnChangedOutputVolume() override;
void setTone(std::shared_ptr<tgcalls::CallAudioTone> tone);
// VoiceProcessingAudioUnitObserver methods.
OSStatus OnDeliverRecordedData(AudioUnitRenderActionFlags* flags,
const AudioTimeStamp* time_stamp,
UInt32 bus_number,
UInt32 num_frames,
AudioBufferList* io_data) override;
OSStatus OnGetPlayoutData(AudioUnitRenderActionFlags* flags,
const AudioTimeStamp* time_stamp,
UInt32 bus_number,
UInt32 num_frames,
AudioBufferList* io_data) override;
void OnMutedSpeechStatusChanged(bool isDetectingSpeech) override;
bool IsInterrupted();
public:
void (^mutedSpeechDetectionChanged)(bool);
private:
// Called by the relevant AudioSessionObserver methods on `thread_`.
void HandleInterruptionBegin();
void HandleInterruptionEnd();
void HandleValidRouteChange();
void HandleCanPlayOrRecordChange(bool can_play_or_record);
void HandleSampleRateChange();
void HandlePlayoutGlitchDetected();
void HandleOutputVolumeChange();
// Uses current `playout_parameters_` and `record_parameters_` to inform the
// audio device buffer (ADB) about our internal audio parameters.
void UpdateAudioDeviceBuffer();
// Since the preferred audio parameters are only hints to the OS, the actual
// values may be different once the AVAudioSession has been activated.
// This method asks for the current hardware parameters and takes actions
// if they should differ from what we have asked for initially. It also
// defines `playout_parameters_` and `record_parameters_`.
void SetupAudioBuffersForActiveAudioSession();
// Creates the audio unit.
bool CreateAudioUnit();
// Updates the audio unit state based on current state.
void UpdateAudioUnit(bool can_play_or_record);
// Configures the audio session for WebRTC.
bool ConfigureAudioSession();
// Like above, but requires caller to already hold session lock.
bool ConfigureAudioSessionLocked();
// Unconfigures the audio session.
void UnconfigureAudioSession();
// Activates our audio session, creates and initializes the voice-processing
// audio unit and verifies that we got the preferred native audio parameters.
bool InitPlayOrRecord();
// Closes and deletes the voice-processing I/O unit.
void ShutdownPlayOrRecord();
// Resets thread-checkers before a call is restarted.
void PrepareForNewStart();
// Determines whether voice processing should be enabled or disabled.
const bool bypass_voice_processing_;
const bool disable_recording_;
const bool enableSystemMute_ = false;
const int numChannels_;
// Native I/O audio thread checker.
SequenceChecker io_thread_checker_;
// Thread that this object is created on.
rtc::Thread* thread_;
// Raw pointer handle provided to us in AttachAudioBuffer(). Owned by the
// AudioDeviceModuleImpl class and called by AudioDeviceModule::Create().
// The AudioDeviceBuffer is a member of the AudioDeviceModuleImpl instance
// and therefore outlives this object.
AudioDeviceBuffer* audio_device_buffer_;
// Contains audio parameters (sample rate, #channels, buffer size etc.) for
// the playout and recording sides. These structure is set in two steps:
// first, native sample rate and #channels are defined in Init(). Next, the
// audio session is activated and we verify that the preferred parameters
// were granted by the OS. At this stage it is also possible to add a third
// component to the parameters; the native I/O buffer duration.
// A RTC_CHECK will be hit if we for some reason fail to open an audio session
// using the specified parameters.
AudioParameters playout_parameters_;
AudioParameters record_parameters_;
// The AudioUnit used to play and record audio.
std::unique_ptr<VoiceProcessingAudioUnit> audio_unit_;
// FineAudioBuffer takes an AudioDeviceBuffer which delivers audio data
// in chunks of 10ms. It then allows for this data to be pulled in
// a finer or coarser granularity. I.e. interacting with this class instead
// of directly with the AudioDeviceBuffer one can ask for any number of
// audio data samples. Is also supports a similar scheme for the recording
// side.
// Example: native buffer size can be 128 audio frames at 16kHz sample rate.
// WebRTC will provide 480 audio frames per 10ms but iOS asks for 128
// in each callback (one every 8ms). This class can then ask for 128 and the
// FineAudioBuffer will ask WebRTC for new data only when needed and also
// cache non-utilized audio between callbacks. On the recording side, iOS
// can provide audio data frames of size 128 and these are accumulated until
// enough data to supply one 10ms call exists. This 10ms chunk is then sent
// to WebRTC and the remaining part is stored.
std::unique_ptr<FineAudioBuffer> fine_audio_buffer_;
// Temporary storage for recorded data. AudioUnitRender() renders into this
// array as soon as a frame of the desired buffer size has been recorded.
// On real iOS devices, the size will be fixed and set once. For iOS
// simulators, the size can vary from callback to callback and the size
// will be changed dynamically to account for this behavior.
rtc::BufferT<int16_t> record_audio_buffer_;
// Set to 1 when recording is active and 0 otherwise.
std::atomic<int> recording_;
// Set to 1 when playout is active and 0 otherwise.
std::atomic<int> playing_;
// Set to true after successful call to Init(), false otherwise.
bool initialized_ RTC_GUARDED_BY(thread_);
// Set to true after successful call to InitRecording() or InitPlayout(),
// false otherwise.
bool audio_is_initialized_;
// Set to true if audio session is interrupted, false otherwise.
bool is_interrupted_;
// Audio interruption observer instance.
RTCNativeAudioSessionDelegateAdapter* audio_session_observer_
RTC_GUARDED_BY(thread_);
// Set to true if we've activated the audio session.
bool has_configured_session_ RTC_GUARDED_BY(thread_);
// Counts number of detected audio glitches on the playout side.
int64_t num_detected_playout_glitches_ RTC_GUARDED_BY(thread_);
int64_t last_playout_time_ RTC_GUARDED_BY(io_thread_checker_);
// Counts number of playout callbacks per call.
// The value is updated on the native I/O thread and later read on the
// creating `thread_` but at this stage no audio is active.
// Hence, it is a "thread safe" design and no lock is needed.
int64_t num_playout_callbacks_;
// Contains the time for when the last output volume change was detected.
int64_t last_output_volume_change_time_ RTC_GUARDED_BY(thread_);
// Avoids running pending task after `this` is Terminated.
webrtc::scoped_refptr<PendingTaskSafetyFlag> safety_ =
PendingTaskSafetyFlag::Create();
std::atomic<bool> _hasTone;
std::shared_ptr<tgcalls::CallAudioTone> _tone;
bool isBufferPlaying_ = false;
bool isBufferRecording_ = false;
bool isMicrophoneMuted_ = false;
};
} // namespace tgcalls_ios_adm
} // namespace webrtc
#endif // SDK_OBJC_NATIVE_SRC_AUDIO_AUDIO_DEVICE_IOS_H_

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,161 @@
/*
* Copyright (c) 2012 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.
*/
#ifndef TGCALLS_AUDIO_AUDIO_DEVICE_MODULE_IOS_H_
#define TGCALLS_AUDIO_AUDIO_DEVICE_MODULE_IOS_H_
#include <memory>
#include "tgcalls_audio_device_ios.h"
#include "api/task_queue/task_queue_factory.h"
#include "modules/audio_device/audio_device_buffer.h"
#include "modules/audio_device/include/audio_device.h"
#include "rtc_base/checks.h"
#include "platform/darwin/iOS/CallAudioTone.h"
namespace webrtc {
class AudioDeviceGeneric;
namespace tgcalls_ios_adm {
class AudioDeviceModuleIOS : public AudioDeviceModule {
public:
int32_t AttachAudioBuffer();
explicit AudioDeviceModuleIOS(bool bypass_voice_processing, bool disable_recording, bool enableSystemMute, int numChannels);
~AudioDeviceModuleIOS() override;
// Retrieve the currently utilized audio layer
int32_t ActiveAudioLayer(AudioLayer* audioLayer) const override;
// Full-duplex transportation of PCM audio
int32_t RegisterAudioCallback(AudioTransport* audioCallback) override;
// Main initializaton and termination
int32_t Init() override;
int32_t Terminate() override;
bool Initialized() const override;
// Device enumeration
int16_t PlayoutDevices() override;
int16_t RecordingDevices() override;
int32_t PlayoutDeviceName(uint16_t index,
char name[kAdmMaxDeviceNameSize],
char guid[kAdmMaxGuidSize]) override;
int32_t RecordingDeviceName(uint16_t index,
char name[kAdmMaxDeviceNameSize],
char guid[kAdmMaxGuidSize]) override;
// Device selection
int32_t SetPlayoutDevice(uint16_t index) override;
int32_t SetPlayoutDevice(WindowsDeviceType device) override;
int32_t SetRecordingDevice(uint16_t index) override;
int32_t SetRecordingDevice(WindowsDeviceType device) override;
// Audio transport initialization
int32_t PlayoutIsAvailable(bool* available) override;
int32_t InitPlayout() override;
bool PlayoutIsInitialized() const override;
int32_t RecordingIsAvailable(bool* available) override;
int32_t InitRecording() override;
bool RecordingIsInitialized() const override;
// Audio transport control
int32_t InternalStartPlayout();
int32_t StartPlayout() override;
int32_t StopPlayout() override;
bool Playing() const override;
int32_t InternalStartRecording();
int32_t StartRecording() override;
int32_t StopRecording() override;
bool Recording() const override;
// Audio mixer initialization
int32_t InitSpeaker() override;
bool SpeakerIsInitialized() const override;
int32_t InitMicrophone() override;
bool MicrophoneIsInitialized() const override;
// Speaker volume controls
int32_t SpeakerVolumeIsAvailable(bool* available) override;
int32_t SetSpeakerVolume(uint32_t volume) override;
int32_t SpeakerVolume(uint32_t* volume) const override;
int32_t MaxSpeakerVolume(uint32_t* maxVolume) const override;
int32_t MinSpeakerVolume(uint32_t* minVolume) const override;
// Microphone volume controls
int32_t MicrophoneVolumeIsAvailable(bool* available) override;
int32_t SetMicrophoneVolume(uint32_t volume) override;
int32_t MicrophoneVolume(uint32_t* volume) const override;
int32_t MaxMicrophoneVolume(uint32_t* maxVolume) const override;
int32_t MinMicrophoneVolume(uint32_t* minVolume) const override;
// Speaker mute control
int32_t SpeakerMuteIsAvailable(bool* available) override;
int32_t SetSpeakerMute(bool enable) override;
int32_t SpeakerMute(bool* enabled) const override;
// Microphone mute control
int32_t MicrophoneMuteIsAvailable(bool* available) override;
int32_t SetMicrophoneMute(bool enable) override;
int32_t MicrophoneMute(bool* enabled) const override;
// Stereo support
int32_t StereoPlayoutIsAvailable(bool* available) const override;
int32_t SetStereoPlayout(bool enable) override;
int32_t StereoPlayout(bool* enabled) const override;
int32_t StereoRecordingIsAvailable(bool* available) const override;
int32_t SetStereoRecording(bool enable) override;
int32_t StereoRecording(bool* enabled) const override;
// Delay information and control
int32_t PlayoutDelay(uint16_t* delayMS) const override;
bool BuiltInAECIsAvailable() const override;
int32_t EnableBuiltInAEC(bool enable) override;
bool BuiltInAGCIsAvailable() const override;
int32_t EnableBuiltInAGC(bool enable) override;
bool BuiltInNSIsAvailable() const override;
int32_t EnableBuiltInNS(bool enable) override;
int32_t GetPlayoutUnderrunCount() const override;
void setTone(std::shared_ptr<tgcalls::CallAudioTone> tone);
#if defined(WEBRTC_IOS)
int GetPlayoutAudioParameters(AudioParameters* params) const override;
int GetRecordAudioParameters(AudioParameters* params) const override;
#endif // WEBRTC_IOS
public:
void (^mutedSpeechDetectionChanged)(bool);
private:
const bool bypass_voice_processing_;
const bool disable_recording_;
const bool enableSystemMute_;
const int numChannels_;
bool initialized_ = false;
bool internalIsPlaying_ = false;
bool audioBufferPlayoutStarted_ = false;
bool audioBufferRecordingStarted_ = false;
const std::unique_ptr<TaskQueueFactory> task_queue_factory_;
std::unique_ptr<AudioDeviceIOS> audio_device_;
std::unique_ptr<AudioDeviceBuffer> audio_device_buffer_;
std::shared_ptr<tgcalls::CallAudioTone> pendingAudioTone_;
};
} // namespace tgcalls_ios_adm
} // namespace webrtc
#endif // SDK_OBJC_NATIVE_SRC_AUDIO_AUDIO_DEVICE_MODULE_IOS_H_

View file

@ -0,0 +1,756 @@
/*
* Copyright (c) 2012 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.
*/
#include "tgcalls_audio_device_module_ios.h"
#include "api/task_queue/default_task_queue_factory.h"
#include "modules/audio_device/audio_device_config.h"
#include "modules/audio_device/audio_device_generic.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include "rtc_base/ref_count.h"
#include "system_wrappers/include/metrics.h"
#include "api/make_ref_counted.h"
#if defined(WEBRTC_IOS)
#include "tgcalls_audio_device_ios.h"
#endif
#define CHECKinitialized_() \
{ \
if (!initialized_) { \
return -1; \
}; \
}
#define CHECKinitialized__BOOL() \
{ \
if (!initialized_) { \
return false; \
}; \
}
namespace webrtc {
webrtc::scoped_refptr<AudioDeviceModule> CreateAudioDeviceModule(bool bypass_voice_processing) {
return rtc::make_ref_counted<webrtc::tgcalls_ios_adm::AudioDeviceModuleIOS>(bypass_voice_processing, false, false, 1);
}
webrtc::scoped_refptr<AudioDeviceModule> AudioDeviceModule::Create(
AudioLayer audio_layer,
TaskQueueFactory* task_queue_factory) {
RTC_DLOG(LS_INFO) << __FUNCTION__;
return CreateAudioDeviceModule(false);
}
}
namespace webrtc {
namespace tgcalls_ios_adm {
AudioDeviceModuleIOS::AudioDeviceModuleIOS(bool bypass_voice_processing, bool disable_recording, bool enableSystemMute, int numChannels)
: bypass_voice_processing_(bypass_voice_processing),
disable_recording_(disable_recording),
enableSystemMute_(enableSystemMute),
numChannels_(numChannels),
task_queue_factory_(CreateDefaultTaskQueueFactory()) {
RTC_LOG(LS_INFO) << "current platform is IOS";
RTC_LOG(LS_INFO) << "iPhone Audio APIs will be utilized.";
}
int32_t AudioDeviceModuleIOS::AttachAudioBuffer() {
RTC_DLOG(LS_INFO) << __FUNCTION__;
audio_device_->AttachAudioBuffer(audio_device_buffer_.get());
return 0;
}
AudioDeviceModuleIOS::~AudioDeviceModuleIOS() {
RTC_DLOG(LS_INFO) << __FUNCTION__;
}
int32_t AudioDeviceModuleIOS::ActiveAudioLayer(AudioLayer* audioLayer) const {
RTC_DLOG(LS_INFO) << __FUNCTION__;
AudioLayer activeAudio;
if (audio_device_->ActiveAudioLayer(activeAudio) == -1) {
return -1;
}
*audioLayer = activeAudio;
return 0;
}
int32_t AudioDeviceModuleIOS::Init() {
RTC_DLOG(LS_INFO) << __FUNCTION__;
if (initialized_)
return 0;
audio_device_buffer_.reset(new webrtc::AudioDeviceBuffer(task_queue_factory_.get()));
audio_device_.reset(new tgcalls_ios_adm::AudioDeviceIOS(bypass_voice_processing_, disable_recording_, enableSystemMute_, numChannels_));
RTC_CHECK(audio_device_);
if (audio_device_) {
audio_device_->mutedSpeechDetectionChanged = [mutedSpeechDetectionChanged copy];
}
this->AttachAudioBuffer();
AudioDeviceGeneric::InitStatus status = audio_device_->Init();
RTC_HISTOGRAM_ENUMERATION(
"WebRTC.Audio.InitializationResult", static_cast<int>(status),
static_cast<int>(AudioDeviceGeneric::InitStatus::NUM_STATUSES));
if (status != AudioDeviceGeneric::InitStatus::OK) {
RTC_LOG(LS_ERROR) << "Audio device initialization failed.";
return -1;
}
initialized_ = true;
if (pendingAudioTone_) {
audio_device_->setTone(pendingAudioTone_);
pendingAudioTone_ = nullptr;
}
return 0;
}
int32_t AudioDeviceModuleIOS::Terminate() {
RTC_DLOG(LS_INFO) << __FUNCTION__;
if (!initialized_)
return 0;
if (audio_device_->Terminate() == -1) {
return -1;
}
initialized_ = false;
return 0;
}
bool AudioDeviceModuleIOS::Initialized() const {
RTC_DLOG(LS_INFO) << __FUNCTION__ << ": " << initialized_;
return initialized_;
}
int32_t AudioDeviceModuleIOS::InitSpeaker() {
RTC_DLOG(LS_INFO) << __FUNCTION__;
CHECKinitialized_();
return audio_device_->InitSpeaker();
}
int32_t AudioDeviceModuleIOS::InitMicrophone() {
RTC_DLOG(LS_INFO) << __FUNCTION__;
CHECKinitialized_();
return audio_device_->InitMicrophone();
}
int32_t AudioDeviceModuleIOS::SpeakerVolumeIsAvailable(bool* available) {
RTC_DLOG(LS_INFO) << __FUNCTION__;
CHECKinitialized_();
bool isAvailable = false;
if (audio_device_->SpeakerVolumeIsAvailable(isAvailable) == -1) {
return -1;
}
*available = isAvailable;
RTC_DLOG(LS_INFO) << "output: " << isAvailable;
return 0;
}
int32_t AudioDeviceModuleIOS::SetSpeakerVolume(uint32_t volume) {
RTC_DLOG(LS_INFO) << __FUNCTION__ << "(" << volume << ")";
CHECKinitialized_();
return audio_device_->SetSpeakerVolume(volume);
}
int32_t AudioDeviceModuleIOS::SpeakerVolume(uint32_t* volume) const {
RTC_DLOG(LS_INFO) << __FUNCTION__;
CHECKinitialized_();
uint32_t level = 0;
if (audio_device_->SpeakerVolume(level) == -1) {
return -1;
}
*volume = level;
RTC_DLOG(LS_INFO) << "output: " << *volume;
return 0;
}
bool AudioDeviceModuleIOS::SpeakerIsInitialized() const {
RTC_DLOG(LS_INFO) << __FUNCTION__;
CHECKinitialized__BOOL();
bool isInitialized = audio_device_->SpeakerIsInitialized();
RTC_DLOG(LS_INFO) << "output: " << isInitialized;
return isInitialized;
}
bool AudioDeviceModuleIOS::MicrophoneIsInitialized() const {
RTC_DLOG(LS_INFO) << __FUNCTION__;
CHECKinitialized__BOOL();
bool isInitialized = audio_device_->MicrophoneIsInitialized();
RTC_DLOG(LS_INFO) << "output: " << isInitialized;
return isInitialized;
}
int32_t AudioDeviceModuleIOS::MaxSpeakerVolume(uint32_t* maxVolume) const {
CHECKinitialized_();
uint32_t maxVol = 0;
if (audio_device_->MaxSpeakerVolume(maxVol) == -1) {
return -1;
}
*maxVolume = maxVol;
return 0;
}
int32_t AudioDeviceModuleIOS::MinSpeakerVolume(uint32_t* minVolume) const {
CHECKinitialized_();
uint32_t minVol = 0;
if (audio_device_->MinSpeakerVolume(minVol) == -1) {
return -1;
}
*minVolume = minVol;
return 0;
}
int32_t AudioDeviceModuleIOS::SpeakerMuteIsAvailable(bool* available) {
RTC_DLOG(LS_INFO) << __FUNCTION__;
CHECKinitialized_();
bool isAvailable = false;
if (audio_device_->SpeakerMuteIsAvailable(isAvailable) == -1) {
return -1;
}
*available = isAvailable;
RTC_DLOG(LS_INFO) << "output: " << isAvailable;
return 0;
}
int32_t AudioDeviceModuleIOS::SetSpeakerMute(bool enable) {
RTC_DLOG(LS_INFO) << __FUNCTION__ << "(" << enable << ")";
CHECKinitialized_();
return audio_device_->SetSpeakerMute(enable);
}
int32_t AudioDeviceModuleIOS::SpeakerMute(bool* enabled) const {
RTC_DLOG(LS_INFO) << __FUNCTION__;
CHECKinitialized_();
bool muted = false;
if (audio_device_->SpeakerMute(muted) == -1) {
return -1;
}
*enabled = muted;
RTC_DLOG(LS_INFO) << "output: " << muted;
return 0;
}
int32_t AudioDeviceModuleIOS::MicrophoneMuteIsAvailable(bool* available) {
RTC_DLOG(LS_INFO) << __FUNCTION__;
CHECKinitialized_();
bool isAvailable = false;
if (audio_device_->MicrophoneMuteIsAvailable(isAvailable) == -1) {
return -1;
}
*available = isAvailable;
RTC_DLOG(LS_INFO) << "output: " << isAvailable;
return 0;
}
int32_t AudioDeviceModuleIOS::SetMicrophoneMute(bool enable) {
RTC_DLOG(LS_INFO) << __FUNCTION__ << "(" << enable << ")";
CHECKinitialized_();
return (audio_device_->SetMicrophoneMute(enable));
}
int32_t AudioDeviceModuleIOS::MicrophoneMute(bool* enabled) const {
RTC_DLOG(LS_INFO) << __FUNCTION__;
CHECKinitialized_();
bool muted = false;
if (audio_device_->MicrophoneMute(muted) == -1) {
return -1;
}
*enabled = muted;
RTC_DLOG(LS_INFO) << "output: " << muted;
return 0;
}
int32_t AudioDeviceModuleIOS::MicrophoneVolumeIsAvailable(bool* available) {
RTC_DLOG(LS_INFO) << __FUNCTION__;
CHECKinitialized_();
bool isAvailable = false;
if (audio_device_->MicrophoneVolumeIsAvailable(isAvailable) == -1) {
return -1;
}
*available = isAvailable;
RTC_DLOG(LS_INFO) << "output: " << isAvailable;
return 0;
}
int32_t AudioDeviceModuleIOS::SetMicrophoneVolume(uint32_t volume) {
RTC_DLOG(LS_INFO) << __FUNCTION__ << "(" << volume << ")";
CHECKinitialized_();
return (audio_device_->SetMicrophoneVolume(volume));
}
int32_t AudioDeviceModuleIOS::MicrophoneVolume(uint32_t* volume) const {
RTC_DLOG(LS_INFO) << __FUNCTION__;
CHECKinitialized_();
uint32_t level = 0;
if (audio_device_->MicrophoneVolume(level) == -1) {
return -1;
}
*volume = level;
RTC_DLOG(LS_INFO) << "output: " << *volume;
return 0;
}
int32_t AudioDeviceModuleIOS::StereoRecordingIsAvailable(
bool* available) const {
RTC_DLOG(LS_INFO) << __FUNCTION__;
CHECKinitialized_();
bool isAvailable = false;
if (audio_device_->StereoRecordingIsAvailable(isAvailable) == -1) {
return -1;
}
*available = isAvailable;
RTC_DLOG(LS_INFO) << "output: " << isAvailable;
return 0;
}
int32_t AudioDeviceModuleIOS::SetStereoRecording(bool enable) {
RTC_DLOG(LS_INFO) << __FUNCTION__ << "(" << enable << ")";
CHECKinitialized_();
if (enable) {
RTC_LOG(LS_WARNING) << "recording in stereo is not supported";
}
return -1;
}
int32_t AudioDeviceModuleIOS::StereoRecording(bool* enabled) const {
RTC_DLOG(LS_INFO) << __FUNCTION__;
CHECKinitialized_();
bool stereo = false;
if (audio_device_->StereoRecording(stereo) == -1) {
return -1;
}
*enabled = stereo;
RTC_DLOG(LS_INFO) << "output: " << stereo;
return 0;
}
int32_t AudioDeviceModuleIOS::StereoPlayoutIsAvailable(bool* available) const {
RTC_DLOG(LS_INFO) << __FUNCTION__;
CHECKinitialized_();
bool isAvailable = false;
if (audio_device_->StereoPlayoutIsAvailable(isAvailable) == -1) {
return -1;
}
*available = isAvailable;
RTC_DLOG(LS_INFO) << "output: " << isAvailable;
return 0;
}
int32_t AudioDeviceModuleIOS::SetStereoPlayout(bool enable) {
RTC_DLOG(LS_INFO) << __FUNCTION__ << "(" << enable << ")";
CHECKinitialized_();
if (audio_device_->PlayoutIsInitialized()) {
RTC_LOG(LS_ERROR) << "unable to set stereo mode while playing side is initialized";
return -1;
}
if (audio_device_->SetStereoPlayout(enable)) {
RTC_LOG(LS_WARNING) << "stereo playout is not supported";
return -1;
}
int8_t nChannels(1);
if (enable) {
nChannels = 2;
}
audio_device_buffer_.get()->SetPlayoutChannels(nChannels);
return 0;
}
int32_t AudioDeviceModuleIOS::StereoPlayout(bool* enabled) const {
RTC_DLOG(LS_INFO) << __FUNCTION__;
CHECKinitialized_();
bool stereo = false;
if (audio_device_->StereoPlayout(stereo) == -1) {
return -1;
}
*enabled = stereo;
RTC_DLOG(LS_INFO) << "output: " << stereo;
return 0;
}
int32_t AudioDeviceModuleIOS::PlayoutIsAvailable(bool* available) {
RTC_DLOG(LS_INFO) << __FUNCTION__;
CHECKinitialized_();
bool isAvailable = false;
if (audio_device_->PlayoutIsAvailable(isAvailable) == -1) {
return -1;
}
*available = isAvailable;
RTC_DLOG(LS_INFO) << "output: " << isAvailable;
return 0;
}
int32_t AudioDeviceModuleIOS::RecordingIsAvailable(bool* available) {
RTC_DLOG(LS_INFO) << __FUNCTION__;
CHECKinitialized_();
bool isAvailable = false;
if (audio_device_->RecordingIsAvailable(isAvailable) == -1) {
return -1;
}
*available = isAvailable;
RTC_DLOG(LS_INFO) << "output: " << isAvailable;
return 0;
}
int32_t AudioDeviceModuleIOS::MaxMicrophoneVolume(uint32_t* maxVolume) const {
CHECKinitialized_();
uint32_t maxVol(0);
if (audio_device_->MaxMicrophoneVolume(maxVol) == -1) {
return -1;
}
*maxVolume = maxVol;
return 0;
}
int32_t AudioDeviceModuleIOS::MinMicrophoneVolume(uint32_t* minVolume) const {
CHECKinitialized_();
uint32_t minVol(0);
if (audio_device_->MinMicrophoneVolume(minVol) == -1) {
return -1;
}
*minVolume = minVol;
return 0;
}
int16_t AudioDeviceModuleIOS::PlayoutDevices() {
RTC_DLOG(LS_INFO) << __FUNCTION__;
CHECKinitialized_();
uint16_t nPlayoutDevices = audio_device_->PlayoutDevices();
RTC_DLOG(LS_INFO) << "output: " << nPlayoutDevices;
return (int16_t)(nPlayoutDevices);
}
int32_t AudioDeviceModuleIOS::SetPlayoutDevice(uint16_t index) {
RTC_DLOG(LS_INFO) << __FUNCTION__ << "(" << index << ")";
CHECKinitialized_();
return audio_device_->SetPlayoutDevice(index);
}
int32_t AudioDeviceModuleIOS::SetPlayoutDevice(WindowsDeviceType device) {
RTC_DLOG(LS_INFO) << __FUNCTION__;
CHECKinitialized_();
return audio_device_->SetPlayoutDevice(device);
}
int32_t AudioDeviceModuleIOS::PlayoutDeviceName(
uint16_t index,
char name[kAdmMaxDeviceNameSize],
char guid[kAdmMaxGuidSize]) {
RTC_DLOG(LS_INFO) << __FUNCTION__ << "(" << index << ", ...)";
CHECKinitialized_();
if (name == NULL) {
return -1;
}
if (audio_device_->PlayoutDeviceName(index, name, guid) == -1) {
return -1;
}
if (name != NULL) {
RTC_DLOG(LS_INFO) << "output: name = " << name;
}
if (guid != NULL) {
RTC_DLOG(LS_INFO) << "output: guid = " << guid;
}
return 0;
}
int32_t AudioDeviceModuleIOS::RecordingDeviceName(
uint16_t index,
char name[kAdmMaxDeviceNameSize],
char guid[kAdmMaxGuidSize]) {
RTC_DLOG(LS_INFO) << __FUNCTION__ << "(" << index << ", ...)";
CHECKinitialized_();
if (name == NULL) {
return -1;
}
if (audio_device_->RecordingDeviceName(index, name, guid) == -1) {
return -1;
}
if (name != NULL) {
RTC_DLOG(LS_INFO) << "output: name = " << name;
}
if (guid != NULL) {
RTC_DLOG(LS_INFO) << "output: guid = " << guid;
}
return 0;
}
int16_t AudioDeviceModuleIOS::RecordingDevices() {
RTC_DLOG(LS_INFO) << __FUNCTION__;
CHECKinitialized_();
uint16_t nRecordingDevices = audio_device_->RecordingDevices();
RTC_DLOG(LS_INFO) << "output: " << nRecordingDevices;
return (int16_t)nRecordingDevices;
}
int32_t AudioDeviceModuleIOS::SetRecordingDevice(uint16_t index) {
RTC_DLOG(LS_INFO) << __FUNCTION__ << "(" << index << ")";
CHECKinitialized_();
return audio_device_->SetRecordingDevice(index);
}
int32_t AudioDeviceModuleIOS::SetRecordingDevice(WindowsDeviceType device) {
RTC_DLOG(LS_INFO) << __FUNCTION__;
CHECKinitialized_();
return audio_device_->SetRecordingDevice(device);
}
int32_t AudioDeviceModuleIOS::InitPlayout() {
RTC_DLOG(LS_INFO) << __FUNCTION__;
CHECKinitialized_();
if (PlayoutIsInitialized()) {
return 0;
}
int32_t result = audio_device_->InitPlayout();
RTC_DLOG(LS_INFO) << "output: " << result;
RTC_HISTOGRAM_BOOLEAN("WebRTC.Audio.InitPlayoutSuccess",
static_cast<int>(result == 0));
return result;
}
int32_t AudioDeviceModuleIOS::InitRecording() {
RTC_DLOG(LS_INFO) << __FUNCTION__;
CHECKinitialized_();
if (RecordingIsInitialized()) {
return 0;
}
int32_t result = audio_device_->InitRecording();
RTC_DLOG(LS_INFO) << "output: " << result;
RTC_HISTOGRAM_BOOLEAN("WebRTC.Audio.InitRecordingSuccess",
static_cast<int>(result == 0));
return result;
}
bool AudioDeviceModuleIOS::PlayoutIsInitialized() const {
RTC_DLOG(LS_INFO) << __FUNCTION__;
CHECKinitialized__BOOL();
return audio_device_->PlayoutIsInitialized();
}
bool AudioDeviceModuleIOS::RecordingIsInitialized() const {
RTC_DLOG(LS_INFO) << __FUNCTION__;
CHECKinitialized__BOOL();
return audio_device_->RecordingIsInitialized();
}
int32_t AudioDeviceModuleIOS::InternalStartPlayout() {
RTC_DLOG(LS_INFO) << __FUNCTION__;
CHECKinitialized_();
if (audio_device_->Playing()) {
return 0;
}
int32_t result = audio_device_->StartPlayout();
RTC_DLOG(LS_INFO) << "output: " << result;
RTC_HISTOGRAM_BOOLEAN("WebRTC.Audio.StartPlayoutSuccess",
static_cast<int>(result == 0));
return result;
}
int32_t AudioDeviceModuleIOS::StartPlayout() {
RTC_DLOG(LS_INFO) << __FUNCTION__;
CHECKinitialized_();
if (audio_device_->Playing()) {
if (!audioBufferPlayoutStarted_) {
audioBufferPlayoutStarted_ = true;
audio_device_buffer_.get()->StartPlayout();
audio_device_->setIsBufferPlaying(true);
}
internalIsPlaying_ = true;
return 0;
}
audio_device_buffer_.get()->StartPlayout();
audio_device_->setIsBufferPlaying(true);
audioBufferPlayoutStarted_ = true;
int32_t result = audio_device_->StartPlayout();
RTC_DLOG(LS_INFO) << "output: " << result;
RTC_HISTOGRAM_BOOLEAN("WebRTC.Audio.StartPlayoutSuccess",
static_cast<int>(result == 0));
internalIsPlaying_ = true;
return result;
}
int32_t AudioDeviceModuleIOS::StopPlayout() {
RTC_DLOG(LS_INFO) << __FUNCTION__;
CHECKinitialized_();
int32_t result = audio_device_->StopPlayout();
if (audioBufferPlayoutStarted_) {
audio_device_buffer_.get()->StopPlayout();
audioBufferPlayoutStarted_ = false;
audio_device_->setIsBufferPlaying(false);
}
RTC_DLOG(LS_INFO) << "output: " << result;
RTC_HISTOGRAM_BOOLEAN("WebRTC.Audio.StopPlayoutSuccess",
static_cast<int>(result == 0));
return result;
}
bool AudioDeviceModuleIOS::Playing() const {
RTC_DLOG(LS_INFO) << __FUNCTION__;
CHECKinitialized__BOOL();
return internalIsPlaying_;
}
int32_t AudioDeviceModuleIOS::InternalStartRecording() {
RTC_DLOG(LS_INFO) << __FUNCTION__;
CHECKinitialized_();
if (Recording()) {
return 0;
}
int32_t result = audio_device_->StartRecording();
RTC_DLOG(LS_INFO) << "output: " << result;
RTC_HISTOGRAM_BOOLEAN("WebRTC.Audio.StartRecordingSuccess",
static_cast<int>(result == 0));
return result;
}
int32_t AudioDeviceModuleIOS::StartRecording() {
RTC_DLOG(LS_INFO) << __FUNCTION__;
CHECKinitialized_();
if (Recording()) {
if (!audioBufferRecordingStarted_) {
audioBufferRecordingStarted_ = true;
audio_device_buffer_.get()->StartRecording();
audio_device_->setIsBufferRecording(true);
}
return 0;
}
audio_device_buffer_.get()->StartRecording();
audioBufferRecordingStarted_ = true;
audio_device_->setIsBufferRecording(true);
int32_t result = audio_device_->StartRecording();
RTC_DLOG(LS_INFO) << "output: " << result;
RTC_HISTOGRAM_BOOLEAN("WebRTC.Audio.StartRecordingSuccess",
static_cast<int>(result == 0));
return result;
}
int32_t AudioDeviceModuleIOS::StopRecording() {
RTC_DLOG(LS_INFO) << __FUNCTION__;
CHECKinitialized_();
int32_t result = audio_device_->StopRecording();
if (audioBufferRecordingStarted_) {
audioBufferRecordingStarted_ = false;
audio_device_buffer_.get()->StopRecording();
audio_device_->setIsBufferRecording(false);
}
RTC_DLOG(LS_INFO) << "output: " << result;
RTC_HISTOGRAM_BOOLEAN("WebRTC.Audio.StopRecordingSuccess",
static_cast<int>(result == 0));
return result;
}
bool AudioDeviceModuleIOS::Recording() const {
RTC_DLOG(LS_INFO) << __FUNCTION__;
CHECKinitialized__BOOL();
return audio_device_->Recording();
}
int32_t AudioDeviceModuleIOS::RegisterAudioCallback(
AudioTransport* audioCallback) {
RTC_DLOG(LS_INFO) << __FUNCTION__;
return audio_device_buffer_.get()->RegisterAudioCallback(audioCallback);
}
int32_t AudioDeviceModuleIOS::PlayoutDelay(uint16_t* delayMS) const {
CHECKinitialized_();
uint16_t delay = 0;
if (audio_device_->PlayoutDelay(delay) == -1) {
RTC_LOG(LS_ERROR) << "failed to retrieve the playout delay";
return -1;
}
*delayMS = delay;
return 0;
}
bool AudioDeviceModuleIOS::BuiltInAECIsAvailable() const {
RTC_DLOG(LS_INFO) << __FUNCTION__;
CHECKinitialized__BOOL();
bool isAvailable = audio_device_->BuiltInAECIsAvailable();
RTC_DLOG(LS_INFO) << "output: " << isAvailable;
return isAvailable;
}
int32_t AudioDeviceModuleIOS::EnableBuiltInAEC(bool enable) {
RTC_DLOG(LS_INFO) << __FUNCTION__ << "(" << enable << ")";
CHECKinitialized_();
int32_t ok = audio_device_->EnableBuiltInAEC(enable);
RTC_DLOG(LS_INFO) << "output: " << ok;
return ok;
}
bool AudioDeviceModuleIOS::BuiltInAGCIsAvailable() const {
RTC_DLOG(LS_INFO) << __FUNCTION__;
CHECKinitialized__BOOL();
bool isAvailable = audio_device_->BuiltInAGCIsAvailable();
RTC_DLOG(LS_INFO) << "output: " << isAvailable;
return isAvailable;
}
int32_t AudioDeviceModuleIOS::EnableBuiltInAGC(bool enable) {
RTC_DLOG(LS_INFO) << __FUNCTION__ << "(" << enable << ")";
CHECKinitialized_();
int32_t ok = audio_device_->EnableBuiltInAGC(enable);
RTC_DLOG(LS_INFO) << "output: " << ok;
return ok;
}
bool AudioDeviceModuleIOS::BuiltInNSIsAvailable() const {
RTC_DLOG(LS_INFO) << __FUNCTION__;
CHECKinitialized__BOOL();
bool isAvailable = audio_device_->BuiltInNSIsAvailable();
RTC_DLOG(LS_INFO) << "output: " << isAvailable;
return isAvailable;
}
int32_t AudioDeviceModuleIOS::EnableBuiltInNS(bool enable) {
RTC_DLOG(LS_INFO) << __FUNCTION__ << "(" << enable << ")";
CHECKinitialized_();
int32_t ok = audio_device_->EnableBuiltInNS(enable);
RTC_DLOG(LS_INFO) << "output: " << ok;
return ok;
}
int32_t AudioDeviceModuleIOS::GetPlayoutUnderrunCount() const {
// Don't log here, as this method can be called very often.
CHECKinitialized_();
int32_t ok = audio_device_->GetPlayoutUnderrunCount();
return ok;
}
void AudioDeviceModuleIOS::setTone(std::shared_ptr<tgcalls::CallAudioTone> tone) {
if (audio_device_) {
audio_device_->setTone(tone);
} else {
pendingAudioTone_ = tone;
}
}
#if defined(WEBRTC_IOS)
int AudioDeviceModuleIOS::GetPlayoutAudioParameters(
AudioParameters* params) const {
RTC_DLOG(LS_INFO) << __FUNCTION__;
int r = audio_device_->GetPlayoutAudioParameters(params);
RTC_DLOG(LS_INFO) << "output: " << r;
return r;
}
int AudioDeviceModuleIOS::GetRecordAudioParameters(
AudioParameters* params) const {
RTC_DLOG(LS_INFO) << __FUNCTION__;
int r = audio_device_->GetRecordAudioParameters(params);
RTC_DLOG(LS_INFO) << "output: " << r;
return r;
}
#endif // WEBRTC_IOS
}
}

View file

@ -0,0 +1,149 @@
/*
* Copyright 2016 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.
*/
#ifndef TGCALLS_AUDIO_VOICE_PROCESSING_AUDIO_UNIT_H_
#define TGCALLS_AUDIO_VOICE_PROCESSING_AUDIO_UNIT_H_
#include <AudioUnit/AudioUnit.h>
namespace webrtc {
namespace tgcalls_ios_adm {
class VoiceProcessingAudioUnitObserver {
public:
// Callback function called on a real-time priority I/O thread from the audio
// unit. This method is used to signal that recorded audio is available.
virtual OSStatus OnDeliverRecordedData(AudioUnitRenderActionFlags* flags,
const AudioTimeStamp* time_stamp,
UInt32 bus_number,
UInt32 num_frames,
AudioBufferList* io_data) = 0;
// Callback function called on a real-time priority I/O thread from the audio
// unit. This method is used to provide audio samples to the audio unit.
virtual OSStatus OnGetPlayoutData(AudioUnitRenderActionFlags* io_action_flags,
const AudioTimeStamp* time_stamp,
UInt32 bus_number,
UInt32 num_frames,
AudioBufferList* io_data) = 0;
virtual void OnMutedSpeechStatusChanged(bool isDetectingSpeech) {}
protected:
~VoiceProcessingAudioUnitObserver() {}
};
// Convenience class to abstract away the management of a Voice Processing
// I/O Audio Unit. The Voice Processing I/O unit has the same characteristics
// as the Remote I/O unit (supports full duplex low-latency audio input and
// output) and adds AEC for for two-way duplex communication. It also adds AGC,
// adjustment of voice-processing quality, and muting. Hence, ideal for
// VoIP applications.
class VoiceProcessingAudioUnit {
public:
VoiceProcessingAudioUnit(bool bypass_voice_processing, bool disable_recording, bool enableSystemMute, int numChannels,
VoiceProcessingAudioUnitObserver* observer);
~VoiceProcessingAudioUnit();
// TODO(tkchin): enum for state and state checking.
enum State : int32_t {
// Init() should be called.
kInitRequired,
// Audio unit created but not initialized.
kUninitialized,
// Initialized but not started. Equivalent to stopped.
kInitialized,
// Initialized and started.
kStarted,
};
// Number of bytes per audio sample for 16-bit signed integer representation.
static const UInt32 kBytesPerSample;
// Initializes this class by creating the underlying audio unit instance.
// Creates a Voice-Processing I/O unit and configures it for full-duplex
// audio. The selected stream format is selected to avoid internal resampling
// and to match the 10ms callback rate for WebRTC as well as possible.
// Does not intialize the audio unit.
bool Init();
VoiceProcessingAudioUnit::State GetState() const;
// Initializes the underlying audio unit with the given sample rate.
bool Initialize(Float64 sample_rate);
// Starts the underlying audio unit.
OSStatus Start();
// Stops the underlying audio unit.
bool Stop();
// Uninitializes the underlying audio unit.
bool Uninitialize();
void setIsMicrophoneMuted(bool isMuted);
// Calls render on the underlying audio unit.
OSStatus Render(AudioUnitRenderActionFlags* flags,
const AudioTimeStamp* time_stamp,
UInt32 output_bus_number,
UInt32 num_frames,
AudioBufferList* io_data);
private:
// The C API used to set callbacks requires static functions. When these are
// called, they will invoke the relevant instance method by casting
// in_ref_con to VoiceProcessingAudioUnit*.
static OSStatus OnGetPlayoutData(void* in_ref_con,
AudioUnitRenderActionFlags* flags,
const AudioTimeStamp* time_stamp,
UInt32 bus_number,
UInt32 num_frames,
AudioBufferList* io_data);
static OSStatus OnDeliverRecordedData(void* in_ref_con,
AudioUnitRenderActionFlags* flags,
const AudioTimeStamp* time_stamp,
UInt32 bus_number,
UInt32 num_frames,
AudioBufferList* io_data);
// Notifies observer that samples are needed for playback.
OSStatus NotifyGetPlayoutData(AudioUnitRenderActionFlags* flags,
const AudioTimeStamp* time_stamp,
UInt32 bus_number,
UInt32 num_frames,
AudioBufferList* io_data);
// Notifies observer that recorded samples are available for render.
OSStatus NotifyDeliverRecordedData(AudioUnitRenderActionFlags* flags,
const AudioTimeStamp* time_stamp,
UInt32 bus_number,
UInt32 num_frames,
AudioBufferList* io_data);
// Returns the predetermined format with a specific sample rate. See
// implementation file for details on format.
AudioStreamBasicDescription GetFormat(Float64 sample_rate, int numChannels) const;
// Deletes the underlying audio unit.
void DisposeAudioUnit();
const bool bypass_voice_processing_;
const bool disable_recording_;
const int numChannels_;
bool enableSystemMute_ = false;
bool isMicrophoneMuted_ = true;
VoiceProcessingAudioUnitObserver* observer_;
AudioUnit vpio_unit_;
VoiceProcessingAudioUnit::State state_;
};
} // namespace tgcalls_ios_adm
} // namespace webrtc
#endif // SDK_OBJC_NATIVE_SRC_AUDIO_VOICE_PROCESSING_AUDIO_UNIT_H_

View file

@ -0,0 +1,544 @@
/*
* Copyright 2016 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 "tgcalls_voice_processing_audio_unit.h"
#include "rtc_base/checks.h"
#include "system_wrappers/include/metrics.h"
#import "base/RTCLogging.h"
#import "platform/darwin/iOS/RTCAudioSessionConfiguration.h"
#if !defined(NDEBUG)
static void LogStreamDescription(AudioStreamBasicDescription description) {
char formatIdString[5];
UInt32 formatId = CFSwapInt32HostToBig(description.mFormatID);
bcopy(&formatId, formatIdString, 4);
formatIdString[4] = '\0';
RTCLog(@"AudioStreamBasicDescription: {\n"
" mSampleRate: %.2f\n"
" formatIDString: %s\n"
" mFormatFlags: 0x%X\n"
" mBytesPerPacket: %u\n"
" mFramesPerPacket: %u\n"
" mBytesPerFrame: %u\n"
" mChannelsPerFrame: %u\n"
" mBitsPerChannel: %u\n"
" mReserved: %u\n}",
description.mSampleRate, formatIdString,
static_cast<unsigned int>(description.mFormatFlags),
static_cast<unsigned int>(description.mBytesPerPacket),
static_cast<unsigned int>(description.mFramesPerPacket),
static_cast<unsigned int>(description.mBytesPerFrame),
static_cast<unsigned int>(description.mChannelsPerFrame),
static_cast<unsigned int>(description.mBitsPerChannel),
static_cast<unsigned int>(description.mReserved));
}
#endif
namespace webrtc {
namespace tgcalls_ios_adm {
// Calls to AudioUnitInitialize() can fail if called back-to-back on different
// ADM instances. A fall-back solution is to allow multiple sequential calls
// with as small delay between each. This factor sets the max number of allowed
// initialization attempts.
static const int kMaxNumberOfAudioUnitInitializeAttempts = 5;
// A VP I/O unit's bus 1 connects to input hardware (microphone).
static const AudioUnitElement kInputBus = 1;
// A VP I/O unit's bus 0 connects to output hardware (speaker).
static const AudioUnitElement kOutputBus = 0;
// Returns the automatic gain control (AGC) state on the processed microphone
// signal. Should be on by default for Voice Processing audio units.
static OSStatus GetAGCState(AudioUnit audio_unit, UInt32* enabled) {
RTC_DCHECK(audio_unit);
UInt32 size = sizeof(*enabled);
OSStatus result = AudioUnitGetProperty(audio_unit,
kAUVoiceIOProperty_VoiceProcessingEnableAGC,
kAudioUnitScope_Global,
kInputBus,
enabled,
&size);
RTCLog(@"VPIO unit AGC: %u", static_cast<unsigned int>(*enabled));
return result;
}
VoiceProcessingAudioUnit::VoiceProcessingAudioUnit(bool bypass_voice_processing, bool disable_recording, bool enableSystemMute, int numChannels,
VoiceProcessingAudioUnitObserver* observer)
: bypass_voice_processing_(bypass_voice_processing),
disable_recording_(disable_recording),
numChannels_(numChannels),
enableSystemMute_(enableSystemMute),
observer_(observer),
vpio_unit_(nullptr),
state_(kInitRequired) {
RTC_DCHECK(observer);
}
VoiceProcessingAudioUnit::~VoiceProcessingAudioUnit() {
DisposeAudioUnit();
}
const UInt32 VoiceProcessingAudioUnit::kBytesPerSample = 2;
bool VoiceProcessingAudioUnit::Init() {
RTC_DCHECK_EQ(state_, kInitRequired);
// Create an audio component description to identify the Voice Processing
// I/O audio unit.
AudioComponentDescription vpio_unit_description;
vpio_unit_description.componentType = kAudioUnitType_Output;
if (disable_recording_) {
vpio_unit_description.componentSubType = kAudioUnitSubType_RemoteIO;
} else {
vpio_unit_description.componentSubType = kAudioUnitSubType_VoiceProcessingIO;
}
vpio_unit_description.componentManufacturer = kAudioUnitManufacturer_Apple;
vpio_unit_description.componentFlags = 0;
vpio_unit_description.componentFlagsMask = 0;
// Obtain an audio unit instance given the description.
AudioComponent found_vpio_unit_ref =
AudioComponentFindNext(nullptr, &vpio_unit_description);
// Create a Voice Processing IO audio unit.
OSStatus result = noErr;
result = AudioComponentInstanceNew(found_vpio_unit_ref, &vpio_unit_);
if (result != noErr) {
vpio_unit_ = nullptr;
RTCLogError(@"AudioComponentInstanceNew failed. Error=%ld.", (long)result);
return false;
}
if (!disable_recording_) {
// Enable input on the input scope of the input element.
UInt32 enable_input = 1;
result = AudioUnitSetProperty(vpio_unit_, kAudioOutputUnitProperty_EnableIO,
kAudioUnitScope_Input, kInputBus, &enable_input,
sizeof(enable_input));
if (result != noErr) {
DisposeAudioUnit();
RTCLogError(@"Failed to enable input on input scope of input element. "
"Error=%ld.",
(long)result);
return false;
}
}
// Enable output on the output scope of the output element.
UInt32 enable_output = 1;
result = AudioUnitSetProperty(vpio_unit_, kAudioOutputUnitProperty_EnableIO,
kAudioUnitScope_Output, kOutputBus,
&enable_output, sizeof(enable_output));
if (result != noErr) {
DisposeAudioUnit();
RTCLogError(@"Failed to enable output on output scope of output element. "
"Error=%ld.",
(long)result);
return false;
}
// Specify the callback function that provides audio samples to the audio
// unit.
AURenderCallbackStruct render_callback;
render_callback.inputProc = OnGetPlayoutData;
render_callback.inputProcRefCon = this;
result = AudioUnitSetProperty(
vpio_unit_, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input,
kOutputBus, &render_callback, sizeof(render_callback));
if (result != noErr) {
DisposeAudioUnit();
RTCLogError(@"Failed to specify the render callback on the output bus. "
"Error=%ld.",
(long)result);
return false;
}
if (!disable_recording_) {
// Disable AU buffer allocation for the recorder, we allocate our own.
// TODO(henrika): not sure that it actually saves resource to make this call.
UInt32 flag = 0;
result = AudioUnitSetProperty(
vpio_unit_, kAudioUnitProperty_ShouldAllocateBuffer,
kAudioUnitScope_Output, kInputBus, &flag, sizeof(flag));
if (result != noErr) {
DisposeAudioUnit();
RTCLogError(@"Failed to disable buffer allocation on the input bus. "
"Error=%ld.",
(long)result);
return false;
}
// Specify the callback to be called by the I/O thread to us when input audio
// is available. The recorded samples can then be obtained by calling the
// AudioUnitRender() method.
AURenderCallbackStruct input_callback;
input_callback.inputProc = OnDeliverRecordedData;
input_callback.inputProcRefCon = this;
result = AudioUnitSetProperty(vpio_unit_,
kAudioOutputUnitProperty_SetInputCallback,
kAudioUnitScope_Global, kInputBus,
&input_callback, sizeof(input_callback));
if (result != noErr) {
DisposeAudioUnit();
RTCLogError(@"Failed to specify the input callback on the input bus. "
"Error=%ld.",
(long)result);
return false;
}
if (@available(iOS 15.0, macOS 14.0, *)) {
VoiceProcessingAudioUnit *audio_unit = this;
AUVoiceIOMutedSpeechActivityEventListener listener = ^(AUVoiceIOSpeechActivityEvent event) {
if (event == kAUVoiceIOSpeechActivityHasStarted) {
if (audio_unit->observer_) {
audio_unit->observer_->OnMutedSpeechStatusChanged(true);
}
} else if (event == kAUVoiceIOSpeechActivityHasEnded) {
if (audio_unit->observer_) {
audio_unit->observer_->OnMutedSpeechStatusChanged(false);
}
}
};
result = AudioUnitSetProperty(vpio_unit_, kAUVoiceIOProperty_MutedSpeechActivityEventListener, kAudioUnitScope_Global, kInputBus, &listener, sizeof(AUVoiceIOMutedSpeechActivityEventListener));
}
}
if (enableSystemMute_) {
UInt32 muteUplinkOutput = isMicrophoneMuted_ ? 1 : 0;
OSStatus result = AudioUnitSetProperty(vpio_unit_, kAUVoiceIOProperty_MuteOutput, kAudioUnitScope_Global, kInputBus, &muteUplinkOutput, sizeof(muteUplinkOutput));
if (result != noErr) {
RTCLogError(@"Failed to set kAUVoiceIOProperty_MuteOutput. Error=%ld", (long)result);
}
}
state_ = kUninitialized;
return true;
}
VoiceProcessingAudioUnit::State VoiceProcessingAudioUnit::GetState() const {
return state_;
}
bool VoiceProcessingAudioUnit::Initialize(Float64 sample_rate) {
RTC_DCHECK_GE(state_, kUninitialized);
RTCLog(@"Initializing audio unit with sample rate: %f", sample_rate);
OSStatus result = noErr;
AudioStreamBasicDescription outputFormat = GetFormat(sample_rate, numChannels_);
AudioStreamBasicDescription inputFormat = GetFormat(sample_rate, 1);
UInt32 size = sizeof(outputFormat);
#if !defined(NDEBUG)
LogStreamDescription(outputFormat);
#endif
if (!disable_recording_) {
// Set the format on the output scope of the input element/bus.
result =
AudioUnitSetProperty(vpio_unit_, kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Output, kInputBus, &inputFormat, size);
if (result != noErr) {
RTCLogError(@"Failed to set format on output scope of input bus. "
"Error=%ld.",
(long)result);
return false;
}
}
// Set the format on the input scope of the output element/bus.
result =
AudioUnitSetProperty(vpio_unit_, kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input, kOutputBus, &outputFormat, size);
if (result != noErr) {
RTCLogError(@"Failed to set format on input scope of output bus. "
"Error=%ld.",
(long)result);
return false;
}
// Initialize the Voice Processing I/O unit instance.
// Calls to AudioUnitInitialize() can fail if called back-to-back on
// different ADM instances. The error message in this case is -66635 which is
// undocumented. Tests have shown that calling AudioUnitInitialize a second
// time, after a short sleep, avoids this issue.
// See webrtc:5166 for details.
int failed_initalize_attempts = 0;
result = AudioUnitInitialize(vpio_unit_);
while (result != noErr) {
RTCLogError(@"Failed to initialize the Voice Processing I/O unit. "
"Error=%ld.",
(long)result);
++failed_initalize_attempts;
if (failed_initalize_attempts == kMaxNumberOfAudioUnitInitializeAttempts) {
// Max number of initialization attempts exceeded, hence abort.
RTCLogError(@"Too many initialization attempts.");
return false;
}
RTCLog(@"Pause 100ms and try audio unit initialization again...");
[NSThread sleepForTimeInterval:0.1f];
result = AudioUnitInitialize(vpio_unit_);
}
if (result == noErr) {
RTCLog(@"Voice Processing I/O unit is now initialized.");
}
if (bypass_voice_processing_) {
// Attempt to disable builtin voice processing.
UInt32 toggle = 1;
result = AudioUnitSetProperty(vpio_unit_,
kAUVoiceIOProperty_BypassVoiceProcessing,
kAudioUnitScope_Global,
kInputBus,
&toggle,
sizeof(toggle));
if (result == noErr) {
RTCLog(@"Successfully bypassed voice processing.");
} else {
RTCLogError(@"Failed to bypass voice processing. Error=%ld.", (long)result);
}
state_ = kInitialized;
return true;
}
if (!disable_recording_) {
// AGC should be enabled by default for Voice Processing I/O units but it is
// checked below and enabled explicitly if needed. This scheme is used
// to be absolutely sure that the AGC is enabled since we have seen cases
// where only zeros are recorded and a disabled AGC could be one of the
// reasons why it happens.
int agc_was_enabled_by_default = 0;
UInt32 agc_is_enabled = 0;
result = GetAGCState(vpio_unit_, &agc_is_enabled);
if (result != noErr) {
RTCLogError(@"Failed to get AGC state (1st attempt). "
"Error=%ld.",
(long)result);
// Example of error code: kAudioUnitErr_NoConnection (-10876).
// All error codes related to audio units are negative and are therefore
// converted into a postive value to match the UMA APIs.
RTC_HISTOGRAM_COUNTS_SPARSE_100000(
"WebRTC.Audio.GetAGCStateErrorCode1", (-1) * result);
}
if (agc_is_enabled) {
// Remember that the AGC was enabled by default. Will be used in UMA.
agc_was_enabled_by_default = 1;
} else {
// AGC was initially disabled => try to enable it explicitly.
UInt32 enable_agc = 1;
result =
AudioUnitSetProperty(vpio_unit_,
kAUVoiceIOProperty_VoiceProcessingEnableAGC,
kAudioUnitScope_Global, kInputBus, &enable_agc,
sizeof(enable_agc));
if (result != noErr) {
RTCLogError(@"Failed to enable the built-in AGC. "
"Error=%ld.",
(long)result);
RTC_HISTOGRAM_COUNTS_SPARSE_100000(
"WebRTC.Audio.SetAGCStateErrorCode", (-1) * result);
}
result = GetAGCState(vpio_unit_, &agc_is_enabled);
if (result != noErr) {
RTCLogError(@"Failed to get AGC state (2nd attempt). "
"Error=%ld.",
(long)result);
RTC_HISTOGRAM_COUNTS_SPARSE_100000(
"WebRTC.Audio.GetAGCStateErrorCode2", (-1) * result);
}
}
// Track if the built-in AGC was enabled by default (as it should) or not.
RTC_HISTOGRAM_BOOLEAN("WebRTC.Audio.BuiltInAGCWasEnabledByDefault",
agc_was_enabled_by_default);
RTCLog(@"WebRTC.Audio.BuiltInAGCWasEnabledByDefault: %d",
agc_was_enabled_by_default);
// As a final step, add an UMA histogram for tracking the AGC state.
// At this stage, the AGC should be enabled, and if it is not, more work is
// needed to find out the root cause.
RTC_HISTOGRAM_BOOLEAN("WebRTC.Audio.BuiltInAGCIsEnabled", agc_is_enabled);
RTCLog(@"WebRTC.Audio.BuiltInAGCIsEnabled: %u",
static_cast<unsigned int>(agc_is_enabled));
}
state_ = kInitialized;
return true;
}
OSStatus VoiceProcessingAudioUnit::Start() {
RTC_DCHECK_GE(state_, kUninitialized);
RTCLog(@"Starting audio unit.");
OSStatus result = AudioOutputUnitStart(vpio_unit_);
if (result != noErr) {
RTCLogError(@"Failed to start audio unit. Error=%ld", (long)result);
return result;
} else {
RTCLog(@"Started audio unit");
}
state_ = kStarted;
return noErr;
}
bool VoiceProcessingAudioUnit::Stop() {
RTC_DCHECK_GE(state_, kUninitialized);
RTCLog(@"Stopping audio unit.");
OSStatus result = AudioOutputUnitStop(vpio_unit_);
if (result != noErr) {
RTCLogError(@"Failed to stop audio unit. Error=%ld", (long)result);
return false;
} else {
RTCLog(@"Stopped audio unit");
}
state_ = kInitialized;
return true;
}
bool VoiceProcessingAudioUnit::Uninitialize() {
RTC_DCHECK_GE(state_, kUninitialized);
RTCLog(@"Unintializing audio unit.");
OSStatus result = AudioUnitUninitialize(vpio_unit_);
if (result != noErr) {
RTCLogError(@"Failed to uninitialize audio unit. Error=%ld", (long)result);
return false;
} else {
RTCLog(@"Uninitialized audio unit.");
}
state_ = kUninitialized;
return true;
}
void VoiceProcessingAudioUnit::setIsMicrophoneMuted(bool isMuted) {
isMicrophoneMuted_ = isMuted;
if (enableSystemMute_) {
if (vpio_unit_ && state_ == kStarted) {
UInt32 muteUplinkOutput = isMuted ? 1 : 0;
OSStatus result = AudioUnitSetProperty(vpio_unit_, kAUVoiceIOProperty_MuteOutput, kAudioUnitScope_Global, kInputBus, &muteUplinkOutput, sizeof(muteUplinkOutput));
if (result != noErr) {
RTCLogError(@"Failed to set kAUVoiceIOProperty_MuteOutput. Error=%ld", (long)result);
}
}
}
}
OSStatus VoiceProcessingAudioUnit::Render(AudioUnitRenderActionFlags* flags,
const AudioTimeStamp* time_stamp,
UInt32 output_bus_number,
UInt32 num_frames,
AudioBufferList* io_data) {
RTC_DCHECK(vpio_unit_) << "Init() not called.";
OSStatus result = AudioUnitRender(vpio_unit_, flags, time_stamp,
output_bus_number, num_frames, io_data);
if (result != noErr) {
RTCLogError(@"Failed to render audio unit. Error=%ld", (long)result);
}
return result;
}
OSStatus VoiceProcessingAudioUnit::OnGetPlayoutData(
void* in_ref_con,
AudioUnitRenderActionFlags* flags,
const AudioTimeStamp* time_stamp,
UInt32 bus_number,
UInt32 num_frames,
AudioBufferList* io_data) {
VoiceProcessingAudioUnit* audio_unit =
static_cast<VoiceProcessingAudioUnit*>(in_ref_con);
return audio_unit->NotifyGetPlayoutData(flags, time_stamp, bus_number,
num_frames, io_data);
}
OSStatus VoiceProcessingAudioUnit::OnDeliverRecordedData(
void* in_ref_con,
AudioUnitRenderActionFlags* flags,
const AudioTimeStamp* time_stamp,
UInt32 bus_number,
UInt32 num_frames,
AudioBufferList* io_data) {
VoiceProcessingAudioUnit* audio_unit =
static_cast<VoiceProcessingAudioUnit*>(in_ref_con);
return audio_unit->NotifyDeliverRecordedData(flags, time_stamp, bus_number,
num_frames, io_data);
}
OSStatus VoiceProcessingAudioUnit::NotifyGetPlayoutData(
AudioUnitRenderActionFlags* flags,
const AudioTimeStamp* time_stamp,
UInt32 bus_number,
UInt32 num_frames,
AudioBufferList* io_data) {
return observer_->OnGetPlayoutData(flags, time_stamp, bus_number, num_frames,
io_data);
}
OSStatus VoiceProcessingAudioUnit::NotifyDeliverRecordedData(
AudioUnitRenderActionFlags* flags,
const AudioTimeStamp* time_stamp,
UInt32 bus_number,
UInt32 num_frames,
AudioBufferList* io_data) {
return observer_->OnDeliverRecordedData(flags, time_stamp, bus_number,
num_frames, io_data);
}
AudioStreamBasicDescription VoiceProcessingAudioUnit::GetFormat(
Float64 sample_rate, int numChannels) const {
// Set the application formats for input and output:
// - use same format in both directions
// - avoid resampling in the I/O unit by using the hardware sample rate
// - linear PCM => noncompressed audio data format with one frame per packet
// - no need to specify interleaving since only mono is supported
AudioStreamBasicDescription format;
format.mSampleRate = sample_rate;
format.mFormatID = kAudioFormatLinearPCM;
format.mFormatFlags =
kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked | kAudioFormatFlagsNativeEndian;
format.mBytesPerPacket = kBytesPerSample * numChannels;
format.mFramesPerPacket = 1; // uncompressed.
format.mBytesPerFrame = kBytesPerSample * numChannels;
format.mChannelsPerFrame = numChannels;
format.mBitsPerChannel = 8 * kBytesPerSample;
return format;
}
void VoiceProcessingAudioUnit::DisposeAudioUnit() {
if (vpio_unit_) {
switch (state_) {
case kStarted:
Stop();
[[fallthrough]];
case kInitialized:
Uninitialize();
break;
case kUninitialized:
case kInitRequired:
break;
}
RTCLog(@"Disposing audio unit.");
OSStatus result = AudioComponentInstanceDispose(vpio_unit_);
if (result != noErr) {
RTCLogError(@"AudioComponentInstanceDispose failed. Error=%ld.",
(long)result);
}
vpio_unit_ = nullptr;
}
}
} // namespace tgcalls_ios_adm
} // namespace webrtc