diff --git a/android/build.gradle b/android/build.gradle index 9ba91977f..6abb0d675 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -57,7 +57,7 @@ dependencies { if (isDev(project)) { api fileTree(dir: "libs", include: ["*.jar"]) } else { - api 'io.agora.rtc:iris-rtc:4.3.2.234-build.1' + api 'io.agora.rtc:iris-rtc:4.3.2.234-build.3' api 'io.agora.rtc:agora-special-full:4.3.2.234' api 'io.agora.rtc:full-screen-sharing:4.3.2.234' } diff --git a/internal/deps_summary.txt b/internal/deps_summary.txt index 8b8152d37..97afec15e 100644 --- a/internal/deps_summary.txt +++ b/internal/deps_summary.txt @@ -1,10 +1,10 @@ Iris: -https://download.agora.io/sdk/release/iris_4.3.2.234-build.1_DCG_Android_Video_20241218_1023_711.zip -https://download.agora.io/sdk/release/iris_4.3.2.234-build.1_DCG_iOS_Video_20241218_1025_587.zip +https://download.agora.io/sdk/release/iris_4.3.2.234-build.3_DCG_Android_Video_20250115_1019_721.zip +https://download.agora.io/sdk/release/iris_4.3.2.234-build.3_DCG_iOS_Video_20250115_1019_600.zip https://download.agora.io/sdk/release/iris_4.3.2.11-build.1_DCG_Mac_Video_20241206_1145_537.zip https://download.agora.io/sdk/release/iris_4.3.2.11-build.1_DCG_Windows_Video_20241206_1146_579.zip -implementation 'io.agora.rtc:iris-rtc:4.3.2.234-build.1' -pod 'AgoraIrisRTC_iOS', '4.3.2.234-build.1' +implementation 'io.agora.rtc:iris-rtc:4.3.2.234-build.3' +pod 'AgoraIrisRTC_iOS', '4.3.2.234-build.3' pod 'AgoraIrisRTC_macOS', '4.3.2.11-build.1' Native: diff --git a/ios/Classes/AgoraRtcNgPlugin.m b/ios/Classes/AgoraRtcNgPlugin.m index 0a670bd0a..cb4158996 100644 --- a/ios/Classes/AgoraRtcNgPlugin.m +++ b/ios/Classes/AgoraRtcNgPlugin.m @@ -8,8 +8,6 @@ @interface AgoraRtcNgPlugin () @property(nonatomic) NSObject *registrar; -- (void) dispose; - @end @implementation AgoraRtcNgPlugin @@ -59,20 +57,4 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { } } -- (void)detachFromEngineForRegistrar:(NSObject *)registrar { - [self dispose]; -} - -- (void) dispose { - if (self.videoViewController) { - [self.videoViewController dispose]; - self.videoViewController = NULL; - } -} - -- (void)dealloc -{ - [self dispose]; -} - @end diff --git a/ios/agora_rtc_engine.podspec b/ios/agora_rtc_engine.podspec index 7bdde97cf..0e573feac 100644 --- a/ios/agora_rtc_engine.podspec +++ b/ios/agora_rtc_engine.podspec @@ -23,7 +23,7 @@ Pod::Spec.new do |s| puts '[plugin_dev] Found .plugin_dev file, use vendored_frameworks instead.' s.vendored_frameworks = 'libs/*.xcframework' else - s.dependency 'AgoraIrisRTC_iOS', '4.3.2.234-build.1' + s.dependency 'AgoraIrisRTC_iOS', '4.3.2.234-build.3' s.dependency 'AgoraRtcEngine_Special_iOS', '4.3.2.234' end diff --git a/pubspec.yaml b/pubspec.yaml index 53bd701be..23bc9ec7a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: agora_rtc_engine description: >- Flutter plugin of Agora RTC SDK, allow you to simply integrate Agora Video Calling or Live Video Streaming to your app with just a few lines of code. -version: 6.3.2-sp.432234 +version: 6.3.2-sp.432234.b.3 homepage: https://www.agora.io repository: https://github.com/AgoraIO-Extensions/Agora-Flutter-SDK/tree/main environment: diff --git a/scripts/artifacts_version.sh b/scripts/artifacts_version.sh index e45260486..b5bc4529e 100644 --- a/scripts/artifacts_version.sh +++ b/scripts/artifacts_version.sh @@ -1,6 +1,6 @@ set -e -export IRIS_CDN_URL_ANDROID="https://download.agora.io/sdk/release/iris_4.3.2.234-build.1_DCG_Android_Video_20241218_1023_711.zip" -export IRIS_CDN_URL_IOS="https://download.agora.io/sdk/release/iris_4.3.2.234-build.1_DCG_iOS_Video_20241218_1025_587.zip" +export IRIS_CDN_URL_ANDROID="https://download.agora.io/sdk/release/iris_4.3.2.234-build.3_DCG_Android_Video_20250115_1019_721.zip" +export IRIS_CDN_URL_IOS="https://download.agora.io/sdk/release/iris_4.3.2.234-build.3_DCG_iOS_Video_20250115_1019_600.zip" export IRIS_CDN_URL_MACOS="https://download.agora.io/sdk/release/iris_4.3.2.11-build.1_DCG_Mac_Video_20241206_1145_537.zip" export IRIS_CDN_URL_WINDOWS="https://download.agora.io/sdk/release/iris_4.3.2.11-build.1_DCG_Windows_Video_20241206_1146_579.zip" diff --git a/shared/darwin/TextureRenderer.mm b/shared/darwin/TextureRenderer.mm index 00e3a4bc8..2838cbe8f 100644 --- a/shared/darwin/TextureRenderer.mm +++ b/shared/darwin/TextureRenderer.mm @@ -1,8 +1,9 @@ -#import #import "TextureRenderer.h" -#import -#import + #import +#import +#import +#import #import @@ -12,62 +13,72 @@ @interface TextureRender () @property(nonatomic, weak) NSObject *textureRegistry; @property(nonatomic, strong) FlutterMethodChannel *channel; -@property(nonatomic) CVPixelBufferRef buffer_cache; -@property(nonatomic, strong) dispatch_semaphore_t lock; @property(nonatomic) agora::iris::IrisRtcRendering *irisRtcRendering; @property(nonatomic, assign) int delegateId; -@property(nonatomic, assign) BOOL isDirtyBuffer; + +/// Tracks the latest pixel buffer sent from AVFoundation's sample buffer +/// delegate callback. Used to deliver the latest pixel buffer to the flutter +/// engine via the `copyPixelBuffer` API. +@property(readwrite, nonatomic) CVPixelBufferRef latestPixelBuffer; +/// The queue on which `latestPixelBuffer` property is accessed. +@property(strong, nonatomic) dispatch_queue_t pixelBufferSynchronizationQueue; @end namespace { class RendererDelegate : public agora::iris::VideoFrameObserverDelegate { public: - RendererDelegate(void *renderer) : renderer_(renderer), pre_width_(0), pre_height_(0) { } - + RendererDelegate(void *renderer) + : renderer_(renderer), pre_width_(0), pre_height_(0) {} + void OnVideoFrameReceived(const void *videoFrame, - const IrisRtcVideoFrameConfig &config, bool resize) override { + const IrisRtcVideoFrameConfig &config, + bool resize) override { @autoreleasepool { - TextureRender *renderer = (__bridge TextureRender *)renderer_; - - agora::media::base::VideoFrame *vf = (agora::media::base::VideoFrame *)videoFrame; - - if (vf->width == 0 || vf->height == 0) { - return; - } - - int tmpWidth = vf->width; - int tmpHeight = vf->height; - bool is_resize = pre_width_ != tmpWidth || pre_height_ != tmpHeight; - pre_width_ = tmpWidth; - pre_height_ = tmpHeight; - - CVPixelBufferRef _Nullable pixelBuffer = reinterpret_cast(vf->pixelBuffer); - if (pixelBuffer) { - if (is_resize) { - dispatch_async(dispatch_get_main_queue(), ^{ - [renderer.channel invokeMethod:@"onSizeChanged" - arguments:@{@"width": @(tmpWidth), - @"height": @(tmpHeight)}]; - }); - } - - dispatch_semaphore_wait(renderer.lock, DISPATCH_TIME_FOREVER); - if (!renderer.isDirtyBuffer) { - // Ensure the previous retained `CVPixelBufferRef` be released. - if (renderer.buffer_cache) { - CVBufferRelease(renderer.buffer_cache); - } - - renderer.buffer_cache = CVPixelBufferRetain(pixelBuffer); - renderer.isDirtyBuffer = YES; - } - dispatch_semaphore_signal(renderer.lock); - - if (renderer.textureRegistry && renderer.isDirtyBuffer) { - [renderer.textureRegistry textureFrameAvailable:renderer.textureId]; - } - } + TextureRender *renderer = (__bridge TextureRender *)renderer_; + + agora::media::base::VideoFrame *vf = + (agora::media::base::VideoFrame *)videoFrame; + + if (vf->width == 0 || vf->height == 0) { + return; + } + + CVPixelBufferRef _Nullable pixelBuffer = + reinterpret_cast(vf->pixelBuffer); + if (!pixelBuffer) { + return; + } + + if (pre_width_ != vf->width || pre_height_ != vf->height) { + pre_width_ = vf->width; + pre_height_ = vf->height; + dispatch_async(dispatch_get_main_queue(), ^{ + [renderer.channel invokeMethod:@"onSizeChanged" + arguments:@{ + @"width" : @(vf->width), + @"height" : @(vf->height) + }]; + }); + } + + __block CVPixelBufferRef previousPixelBuffer = nil; + // Use `dispatch_sync` to avoid unnecessary context switch under common + // non-contest scenarios; + // Under rare contest scenarios, it will not block for too long since + // the critical section is quite lightweight. + dispatch_sync(renderer.pixelBufferSynchronizationQueue, ^{ + previousPixelBuffer = renderer.latestPixelBuffer; + renderer.latestPixelBuffer = CVPixelBufferRetain(pixelBuffer); + }); + if (previousPixelBuffer) { + CFRelease(previousPixelBuffer); + } + + // notify new frame available on main thread + dispatch_async(dispatch_get_main_queue(), ^{ + [renderer.textureRegistry textureFrameAvailable:renderer.textureId]; + }); } } @@ -76,7 +87,7 @@ void OnVideoFrameReceived(const void *videoFrame, int pre_width_; int pre_height_; }; -} +} // namespace @interface TextureRender () @@ -86,75 +97,87 @@ @interface TextureRender () @implementation TextureRender -- (instancetype) initWithTextureRegistry:(NSObject *)textureRegistry - messenger:(NSObject *)messenger - irisRtcRenderingHandle:(void *)irisRtcRenderingHandle { - self = [super init]; - if (self) { - self.textureRegistry = textureRegistry; - self.irisRtcRendering = (agora::iris::IrisRtcRendering *)irisRtcRenderingHandle; - self.textureId = [self.textureRegistry registerTexture:self]; - self.channel = [FlutterMethodChannel - methodChannelWithName: - [NSString stringWithFormat:@"agora_rtc_engine/texture_render_%lld", - self.textureId] - binaryMessenger:messenger]; - - self.lock = dispatch_semaphore_create(1); - - self.delegate = new ::RendererDelegate((__bridge void *)self); - self.buffer_cache = NULL; - self.isDirtyBuffer = YES; - } - return self; +- (instancetype) + initWithTextureRegistry:(NSObject *)textureRegistry + messenger:(NSObject *)messenger + irisRtcRenderingHandle:(void *)irisRtcRenderingHandle { + self = [super init]; + if (self) { + self.textureRegistry = textureRegistry; + self.irisRtcRendering = + (agora::iris::IrisRtcRendering *)irisRtcRenderingHandle; + self.textureId = [self.textureRegistry registerTexture:self]; + self.channel = [FlutterMethodChannel + methodChannelWithName: + [NSString stringWithFormat:@"agora_rtc_engine/texture_render_%lld", + self.textureId] + binaryMessenger:messenger]; + + self.delegate = new ::RendererDelegate((__bridge void *)self); + self.latestPixelBuffer = nil; + self.pixelBufferSynchronizationQueue = dispatch_queue_create( + [[NSString stringWithFormat:@"io.agora.flutter.render_%lld", _textureId] + UTF8String], + nil); + } + return self; } -- (void)updateData:(NSNumber *)uid channelId:(NSString *)channelId videoSourceType:(NSNumber *)videoSourceType videoViewSetupMode:(NSNumber *)videoViewSetupMode { - IrisRtcVideoFrameConfig config; - config.video_frame_format = agora::media::base::VIDEO_PIXEL_FORMAT::VIDEO_CVPIXEL_NV12; - config.uid = [uid unsignedIntValue]; - config.video_source_type = [videoSourceType intValue]; - if (channelId && (NSNull *)channelId != [NSNull null]) { - strcpy(config.channelId, [channelId UTF8String]); - } else { - strcpy(config.channelId, ""); - } - config.video_view_setup_mode = [videoViewSetupMode intValue]; - config.observed_frame_position = agora::media::base::VIDEO_MODULE_POSITION::POSITION_POST_CAPTURER | agora::media::base::VIDEO_MODULE_POSITION::POSITION_PRE_RENDERER; - - self.delegateId = self.irisRtcRendering->AddVideoFrameObserverDelegate(config, self.delegate); +- (void)updateData:(NSNumber *)uid + channelId:(NSString *)channelId + videoSourceType:(NSNumber *)videoSourceType + videoViewSetupMode:(NSNumber *)videoViewSetupMode { + IrisRtcVideoFrameConfig config; + config.video_frame_format = + agora::media::base::VIDEO_PIXEL_FORMAT::VIDEO_CVPIXEL_NV12; + config.uid = [uid unsignedIntValue]; + config.video_source_type = [videoSourceType intValue]; + if (channelId && (NSNull *)channelId != [NSNull null]) { + strcpy(config.channelId, [channelId UTF8String]); + } else { + strcpy(config.channelId, ""); + } + config.video_view_setup_mode = [videoViewSetupMode intValue]; + config.observed_frame_position = + agora::media::base::VIDEO_MODULE_POSITION::POSITION_POST_CAPTURER | + agora::media::base::VIDEO_MODULE_POSITION::POSITION_PRE_RENDERER; + + self.delegateId = self.irisRtcRendering->AddVideoFrameObserverDelegate( + config, self.delegate); +} + +- (CVPixelBufferRef _Nullable)copyPixelBuffer { + __block CVPixelBufferRef pixelBuffer = nil; + // Use `dispatch_sync` because `copyPixelBuffer` API requires synchronous + // return. + dispatch_sync(self.pixelBufferSynchronizationQueue, ^{ + // No need weak self because it's dispatch_sync. + pixelBuffer = self.latestPixelBuffer; + self.latestPixelBuffer = nil; + }); + return pixelBuffer; } - (void)dispose { if (self.irisRtcRendering) { - self.irisRtcRendering->RemoveVideoFrameObserverDelegate(self.delegateId); - self.irisRtcRendering = NULL; + self.irisRtcRendering->RemoveVideoFrameObserverDelegate(self.delegateId); + self.irisRtcRendering = nil; } if (self.delegate) { - delete self.delegate; - self.delegate = NULL; + delete self.delegate; + self.delegate = nil; } if (self.textureRegistry) { - [self.textureRegistry unregisterTexture:self.textureId]; - self.textureRegistry = NULL; - } - if (self.buffer_cache && self.isDirtyBuffer) { - CVPixelBufferRelease(self.buffer_cache); - self.buffer_cache = NULL; + [self.textureRegistry unregisterTexture:self.textureId]; + self.textureRegistry = nil; } } -- (CVPixelBufferRef _Nullable)copyPixelBuffer { - dispatch_semaphore_wait(self.lock, DISPATCH_TIME_FOREVER); - CVPixelBufferRef buffer_temp = CVPixelBufferRetain(self.buffer_cache); - self.isDirtyBuffer = NO; - dispatch_semaphore_signal(self.lock); - - return buffer_temp; -} - -- (void)onTextureUnregistered:(NSObject *)texture { +- (void)dealloc { + if (self.latestPixelBuffer) { + CVPixelBufferRelease(self.latestPixelBuffer); + self.latestPixelBuffer = nil; + } } @end -