Repo created
This commit is contained in:
parent
81b91f4139
commit
f8c34fa5ee
22732 changed files with 4815320 additions and 2 deletions
|
|
@ -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_
|
||||
|
|
@ -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 session’s 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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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 app’s 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
|
||||
|
|
@ -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
|
||||
|
|
@ -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 port’s 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
|
||||
|
|
@ -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
|
|
@ -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_
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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_
|
||||
|
|
@ -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
|
||||
Loading…
Add table
Add a link
Reference in a new issue