Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions sdk/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ if (is_ios || is_mac) {
"objc/base/RTCLogging.h",
"objc/base/RTCLogging.mm",
"objc/base/RTCMacros.h",
"objc/base/RTCAudioRenderer.h",
"objc/base/RTCMutableI420Buffer.h",
"objc/base/RTCMutableYUVPlanarBuffer.h",
"objc/base/RTCSSLCertificateVerifier.h",
Expand Down Expand Up @@ -864,6 +865,24 @@ if (is_ios || is_mac) {
]
}

rtc_library("audiorendereradapter_objc") {
visibility = [ "*" ]
allow_poison = [ "audio_codecs" ] # TODO(bugs.webrtc.org/8396): Remove.
sources = [
"objc/api/RTCAudioRendererAdapter+Private.h",
"objc/api/RTCAudioRendererAdapter.h",
"objc/api/RTCAudioRendererAdapter.mm",
]

configs += [ "..:common_objc" ]
public_configs = [ ":common_config_objc" ]

deps = [
":base_objc",
"../api:media_stream_interface",
]
}

rtc_library("mediasource_objc") {
sources = [
"objc/api/peerconnection/RTCMediaSource+Private.h",
Expand Down Expand Up @@ -1041,6 +1060,7 @@ if (is_ios || is_mac) {
public_configs = [ ":common_config_objc" ]

deps = [
":audiorendereradapter_objc",
":audio_device_api_objc",
":base_native_additions_objc",
":base_objc",
Expand Down Expand Up @@ -1117,6 +1137,7 @@ if (is_ios || is_mac) {
"objc/unittests/RTCAudioDeviceModule_xctest.mm",
"objc/unittests/RTCAudioDevice_xctest.mm",
"objc/unittests/RTCAudioSessionTest.mm",
"objc/unittests/RTCAudioTrack_xctest.mm",
"objc/unittests/RTCCVPixelBuffer_xctest.mm",
"objc/unittests/RTCCallbackLogger_xctest.m",
"objc/unittests/RTCCameraVideoCapturerTests.mm",
Expand Down Expand Up @@ -1261,6 +1282,7 @@ if (is_ios || is_mac) {
"objc/base/RTCI420Buffer.h",
"objc/base/RTCLogging.h",
"objc/base/RTCMacros.h",
"objc/base/RTCAudioRenderer.h",
"objc/base/RTCMutableI420Buffer.h",
"objc/base/RTCMutableYUVPlanarBuffer.h",
"objc/base/RTCSSLCertificateVerifier.h",
Expand Down Expand Up @@ -1465,6 +1487,7 @@ if (is_ios || is_mac) {
"objc/base/RTCI420Buffer.h",
"objc/base/RTCLogging.h",
"objc/base/RTCMacros.h",
"objc/base/RTCAudioRenderer.h",
"objc/base/RTCMutableI420Buffer.h",
"objc/base/RTCMutableYUVPlanarBuffer.h",
"objc/base/RTCSSLCertificateVerifier.h",
Expand Down
40 changes: 40 additions & 0 deletions sdk/objc/api/RTCAudioRendererAdapter+Private.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright 2026 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 "RTCAudioRendererAdapter.h"

#import "base/RTCAudioRenderer.h"

#include "api/media_stream_interface.h"

NS_ASSUME_NONNULL_BEGIN

@interface RTCAudioRendererAdapter ()

/**
* The Objective-C audio renderer passed to this adapter during construction.
* Calls made to the native AudioTrackSinkInterface will be adapted and passed
* to this renderer.
*/
@property(nonatomic, readonly) id<RTC_OBJC_TYPE(RTCAudioRenderer)> audioRenderer;

/**
* The native AudioTrackSinkInterface surface exposed by this adapter. This
* pointer is unsafe and owned by this class.
*/
@property(nonatomic, readonly) webrtc::AudioTrackSinkInterface *nativeAudioRenderer;

/** Initialize an RTCAudioRendererAdapter with an RTCAudioRenderer. */
- (instancetype)initWithNativeRenderer:(id<RTC_OBJC_TYPE(RTCAudioRenderer)>)audioRenderer
NS_DESIGNATED_INITIALIZER;

@end

NS_ASSUME_NONNULL_END
26 changes: 26 additions & 0 deletions sdk/objc/api/RTCAudioRendererAdapter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright 2026 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 <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

/*
* Creates a webrtc::AudioTrackSinkInterface surface for an RTCAudioRenderer.
* The sink is used by WebRTC audio rendering code - this adapter forwards
* callbacks to the RTCAudioRenderer supplied during construction.
*/
@interface RTCAudioRendererAdapter : NSObject

- (instancetype)init NS_UNAVAILABLE;

@end

NS_ASSUME_NONNULL_END
76 changes: 76 additions & 0 deletions sdk/objc/api/RTCAudioRendererAdapter.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Copyright 2026 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 "RTCAudioRendererAdapter+Private.h"

#include <memory>

namespace webrtc {

class AudioRendererAdapter : public webrtc::AudioTrackSinkInterface {
public:
explicit AudioRendererAdapter(::RTCAudioRendererAdapter* adapter)
: adapter_(adapter) {}

void OnData(const void* audio_data,
int bits_per_sample,
int sample_rate,
size_t number_of_channels,
size_t number_of_frames) override {
[adapter_.audioRenderer renderPCMData:audio_data
bitsPerSample:bits_per_sample
sampleRate:sample_rate
numberOfChannels:number_of_channels
numberOfFrames:number_of_frames
absoluteCaptureTimestampMs:0
timestampIsValid:NO];
}

void OnData(const void* audio_data,
int bits_per_sample,
int sample_rate,
size_t number_of_channels,
size_t number_of_frames,
absl::optional<int64_t> absolute_capture_timestamp_ms) override {
[adapter_.audioRenderer renderPCMData:audio_data
bitsPerSample:bits_per_sample
sampleRate:sample_rate
numberOfChannels:number_of_channels
numberOfFrames:number_of_frames
absoluteCaptureTimestampMs:absolute_capture_timestamp_ms.value_or(0)
timestampIsValid:absolute_capture_timestamp_ms.has_value()];
}

private:
__weak ::RTCAudioRendererAdapter* adapter_;
};

} // namespace webrtc

@implementation RTCAudioRendererAdapter {
std::unique_ptr<webrtc::AudioRendererAdapter> _adapter;
}

@synthesize audioRenderer = _audioRenderer;

- (instancetype)initWithNativeRenderer:(id<RTC_OBJC_TYPE(RTCAudioRenderer)>)audioRenderer {
NSParameterAssert(audioRenderer);
if (self = [super init]) {
_audioRenderer = audioRenderer;
_adapter = std::make_unique<webrtc::AudioRendererAdapter>(self);
}
return self;
}

- (webrtc::AudioTrackSinkInterface*)nativeAudioRenderer {
return _adapter.get();
}

@end
11 changes: 10 additions & 1 deletion sdk/objc/api/peerconnection/RTCAudioTrack.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
* be found in the AUTHORS file in the root of the source tree.
*/

#import <Foundation/Foundation.h>

#import "RTCAudioRenderer.h"
#import "RTCMacros.h"
#import "RTCMediaStreamTrack.h"

Expand All @@ -21,7 +24,13 @@ RTC_OBJC_EXPORT
- (instancetype)init NS_UNAVAILABLE;

/** The audio source for this audio track. */
@property(nonatomic, readonly) RTC_OBJC_TYPE(RTCAudioSource) * source;
@property(nonatomic, readonly) RTC_OBJC_TYPE(RTCAudioSource) *source;

/** Register a renderer that will receive decoded PCM frames on this track. */
- (void)addRenderer:(id<RTC_OBJC_TYPE(RTCAudioRenderer)>)renderer;

/** Deregister a previously registered renderer. */
- (void)removeRenderer:(id<RTC_OBJC_TYPE(RTCAudioRenderer)>)renderer;

@end

Expand Down
63 changes: 61 additions & 2 deletions sdk/objc/api/peerconnection/RTCAudioTrack.mm
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,19 @@

#import "RTCAudioTrack+Private.h"

#import "api/RTCAudioRendererAdapter+Private.h"
#import "RTCAudioSource+Private.h"
#import "RTCMediaStreamTrack+Private.h"
#import "RTCPeerConnectionFactory+Private.h"
#import "helpers/NSString+StdString.h"

#include "rtc_base/checks.h"
#include "rtc_base/logging.h"

@implementation RTC_OBJC_TYPE (RTCAudioTrack)
@implementation RTC_OBJC_TYPE (RTCAudioTrack) {
rtc::Thread *_workerThread;
NSMutableArray *_adapters /* accessed on _workerThread */;
}

@synthesize source = _source;

Expand All @@ -43,7 +48,17 @@ - (instancetype)initWithFactory:(RTC_OBJC_TYPE(RTCPeerConnectionFactory) *)facto
NSParameterAssert(factory);
NSParameterAssert(nativeTrack);
NSParameterAssert(type == RTCMediaStreamTrackTypeAudio);
return [super initWithFactory:factory nativeTrack:nativeTrack type:type];
if (self = [super initWithFactory:factory nativeTrack:nativeTrack type:type]) {
_adapters = [NSMutableArray array];
_workerThread = factory.workerThread;
}
return self;
}

- (void)dealloc {
for (RTCAudioRendererAdapter *adapter in _adapters) {
self.nativeAudioTrack->RemoveSink(adapter.nativeAudioRenderer);
}
}

- (RTC_OBJC_TYPE(RTCAudioSource) *)source {
Expand All @@ -57,6 +72,50 @@ - (instancetype)initWithFactory:(RTC_OBJC_TYPE(RTCPeerConnectionFactory) *)facto
return _source;
}

- (void)addRenderer:(id<RTC_OBJC_TYPE(RTCAudioRenderer)>)renderer {
if (!_workerThread->IsCurrent()) {
_workerThread->BlockingCall([renderer, self] { [self addRenderer:renderer]; });
return;
}

for (RTCAudioRendererAdapter *adapter in _adapters) {
if (adapter.audioRenderer == renderer) {
RTC_LOG(LS_INFO) << "|renderer| is already attached to this track";
return;
}
}

RTCAudioRendererAdapter *adapter =
[[RTCAudioRendererAdapter alloc] initWithNativeRenderer:renderer];
[_adapters addObject:adapter];
self.nativeAudioTrack->AddSink(adapter.nativeAudioRenderer);
}

- (void)removeRenderer:(id<RTC_OBJC_TYPE(RTCAudioRenderer)>)renderer {
if (!_workerThread->IsCurrent()) {
_workerThread->BlockingCall([renderer, self] { [self removeRenderer:renderer]; });
return;
}

__block NSUInteger indexToRemove = NSNotFound;
[_adapters enumerateObjectsUsingBlock:^(RTCAudioRendererAdapter *adapter,
NSUInteger idx,
BOOL *stop) {
if (adapter.audioRenderer == renderer) {
indexToRemove = idx;
*stop = YES;
}
}];
if (indexToRemove == NSNotFound) {
RTC_LOG(LS_INFO) << "removeRenderer called with a renderer that has not been previously added";
return;
}

RTCAudioRendererAdapter *adapterToRemove = [_adapters objectAtIndex:indexToRemove];
self.nativeAudioTrack->RemoveSink(adapterToRemove.nativeAudioRenderer);
[_adapters removeObjectAtIndex:indexToRemove];
}

#pragma mark - Private

- (rtc::scoped_refptr<webrtc::AudioTrackInterface>)nativeAudioTrack {
Expand Down
38 changes: 38 additions & 0 deletions sdk/objc/base/RTCAudioRenderer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright 2026 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 <Foundation/Foundation.h>
#include <stddef.h>
#include <stdint.h>

#import "RTCMacros.h"

NS_ASSUME_NONNULL_BEGIN

/**
* Receives decoded PCM samples from an RTCAudioTrack.
*
* The audio_data pointer is only valid for the lifetime of the callback.
* Callbacks may be delivered on a WebRTC audio thread.
*/
RTC_OBJC_EXPORT
@protocol RTC_OBJC_TYPE(RTCAudioRenderer) <NSObject>

- (void)renderPCMData:(const void *)audio_data
bitsPerSample:(int)bits_per_sample
sampleRate:(int)sample_rate
numberOfChannels:(size_t)number_of_channels
numberOfFrames:(size_t)number_of_frames
absoluteCaptureTimestampMs:(int64_t)absolute_capture_timestamp_ms
timestampIsValid:(BOOL)timestamp_is_valid;

@end

NS_ASSUME_NONNULL_END
Loading