當(dāng) SDK 自帶的美顏無法滿足需求,例如需要做掛件、貼紙,或者美顏效果無法達到預(yù)期時,建議開發(fā)者使用外部濾鏡功能。
對于比較復(fù)雜的場景,例如想要用攝像頭畫面做圖層混合,建議開發(fā)者使用外部采集方案,這樣性能優(yōu)化的空間會更大。
考慮到濾鏡的性能問題和美顏廠商的多樣性,SDK 的視頻外部濾鏡采用面向?qū)ο笤O(shè)計,結(jié)合線程模型,幫助用戶把外部代碼封裝成可替換的濾鏡組件。
主要結(jié)構(gòu)如下:
ZegoVideoFilterFactory 是外部濾鏡的入口,定義了創(chuàng)建、銷毀 ZegoVideoFilter 接口,向 SDK 提供管理 ZegoVideoFilter 生命周期的能力。需要調(diào)用 setVideoFilterFactory
的地方必須實現(xiàn)該接口。
ZegoVideoFilter 定義最基本的組件功能,包括 zego_allocateAndStart
、zego_stopAndDeAllocate
,方便 SDK 在直播流程中進行交互。
請注意,SDK會在適當(dāng)?shù)臅r機創(chuàng)建和銷毀 ZegoVideoFilter,開發(fā)者無需擔(dān)心生命周期不一致的問題。
為了實現(xiàn)傳輸不同數(shù)據(jù)模型,適配不同線程模型,同時避免實現(xiàn)多余接口,SDK 采用偽 COM 的設(shè)計方式。
開發(fā)者需要在子類中顯式指定一種數(shù)據(jù)傳遞類型,SDK 目前支持的類型有:
濾鏡類型 | 說明 |
---|---|
ZegoVideoBufferTypeAsyncPixelBuffer | 異步濾鏡(異步傳遞 BGRA32 的 CVPixelBufferRef) |
ZegoVideoBufferTypeAsyncI420PixelBuffer | 異步 I420 濾鏡(異步傳遞 I420 的 CVPixelBufferRef) |
ZegoVideoBufferTypeSyncPixelBuffer | 同步濾鏡(同步傳遞 BGRA32 的 CVPixelBufferRef) |
SDK 會根據(jù)數(shù)據(jù)類型,實例化不同類型的 client,在調(diào)用 zego_allocateAndStart
時傳給外部濾鏡。
下面會按照不同的濾鏡類型,分別介紹用法。
請注意:
如果外部濾鏡沒有明顯的性能問題,使用同步濾鏡可以減少數(shù)據(jù)拷貝的次數(shù)。但如果比較耗時,請使用異步的方式傳遞數(shù)據(jù),以保證低端機型可以流暢運行。
請開發(fā)者結(jié)合業(yè)務(wù)特點,選擇合適的濾鏡類型。
下述代碼演示了如何創(chuàng)建外部濾鏡工廠。工廠保存了 ZegoVideoFilter 的實例,不會反復(fù)創(chuàng)建。
@interface ZegoVideoFilterFactoryDemo : NSObject<ZegoVideoFilterFactory>@end@implementation ZegoVideoFilterFactoryDemo { id<ZegoVideoFilter> g_filter_;}- (id<ZegoVideoFilter>)zego_create { if (g_filter_ == nil) { g_filter_ = [[ZegoVideoFilterDemo alloc]init]; // 此處的 ZegoVideoFilterDemo 是異步濾鏡 } return g_filter_;}- (void)zego_destroy:(id<ZegoVideoFilter>)filter { if (g_filter_ == filter) { g_filter_ = nil; }}
請注意:
- 大部分情況下,ZegoVideoFilterFactory 會緩存原有 ZegoVideoFilter 實例,開發(fā)者需避免創(chuàng)建新的實例。
- 開發(fā)者必須保證 ZegoVideoFilter 在 create 和 destroy 之間是可用的,請勿直接銷毀對象。
開發(fā)者需要使用外部濾鏡功能時,請在使用前調(diào)用 setVideoFilterFactory:
設(shè)置外部濾鏡工廠對象(此例中的對象為步驟 1 中所創(chuàng)建的 ZegoVideoFilterFactoryDemo)。該工廠對象不可為空。
請注意,如果用戶釋放了工廠對象,不再需要它時,請調(diào)用本接口將其設(shè)置為空。
if (bUse){ { if (g_filterFactory == nullptr) g_filterFactory = [[ZegoVideoFilterFactoryDemo alloc] init]; [ZegoLiveRoomApi setVideoFilterFactory:g_filterFactory]; } else { [ZegoLiveRoomApi setVideoFilterFactory:nil]; }}
下述代碼,以創(chuàng)建異步拷貝圖像的濾鏡為例,開發(fā)者可根據(jù)需求,參考如下實現(xiàn)步驟。
ZegoVideoFilterDemo 的類定義如下:
// 注意異步濾鏡設(shè)備需要實現(xiàn) ZegoVideoFilter、ZegoVideoBufferPool 協(xié)議ZegoVideoCaptureFromImage.h@interface ZegoVideoFilterDemo : NSObject<ZegoVideoFilter, ZegoVideoBufferPool>@endZegoVideoCaptureFromImage.m@interface ZegoVideoFilterDemo()@property (atomic) int pendingCount; // 未處理幀數(shù)@end@implementation ZegoVideoFilterDemo { id<ZegoVideoFilterClient> client_; // id<ZegoVideoBufferPool> buffer_pool_; dispatch_queue_t queue_; int width_; int height_; int stride_; CVPixelBufferPoolRef pool_; int buffer_count_;}
外部濾鏡需要根據(jù) supportBufferType 的類型,對 client 進行轉(zhuǎn)型:
- ZegoVideoBufferTypeAsyncPixelBuffer: SDK 按照 ZegoVideoBufferPool 調(diào)用外部濾鏡。
- ZegoVideoBufferTypeAsyncI420PixelBuffer:SDK 按照 ZegoVideoBufferPool 調(diào)用外部濾鏡,與 Async 型濾鏡只是圖像顏色空間有所區(qū)別。
- ZegoVideoBufferTypeSyncPixelBuffer: SDK 按照 ZegoVideoFilterDelegate 調(diào)用外部濾鏡。
正常來說,如果 SDK 是異步調(diào)用外部濾鏡,外部濾鏡完成前處理后,也按照同樣的步驟回調(diào) SDK。
在本例中,開發(fā)者需要顯式在 supportBufferType
中指定當(dāng)前使用的濾鏡類型為異步濾鏡:
- (ZegoVideoBufferType)supportBufferType { // 返回濾鏡的類型,此濾鏡為:異步濾鏡 return ZegoVideoBufferTypeAsyncPixelBuffer;}
開發(fā)者初始化資源在 zego_allocateAndStart
中進行。
開發(fā)者在 zego_allocateAndStart
中獲取到 client(SDK 內(nèi)部實現(xiàn)的、同樣實現(xiàn) ZegoVideoFilterClient 協(xié)議的對象),用于通知 SDK 處理結(jié)果。
SDK 會在 App 第一次預(yù)覽/推流/拉流時調(diào)用 zego_allocateAndStart
。除非 App 中途調(diào)用過 zego_stopAndDeAllocate
,否則 SDK 不會再調(diào)用 zego_allocateAndStart
。
- (void)zego_allocateAndStart:(id<ZegoVideoFilterClient>) client { client_ = client; if ([client_ conformsToProtocol:@protocol(ZegoVideoBufferPool)]) { buffer_pool_ = (id<ZegoVideoBufferPool>)client; } width_ = 0; height_ = 0; stride_ = 0; pool_ = nil; buffer_count_ = 4; self.pendingCount = 0; queue_ = dispatch_queue_create("video.filter", nil);}
請注意,client 必須保存為強引用對象,在
zego_stopAndDeAllocate
被調(diào)用前必須一直被保存,SDK 不負責(zé)管理 client 的生命周期
開發(fā)者釋放資源在 zego_stopAndDeAllocate
中進行。
建議同步停止濾鏡任務(wù)后再清理 client 對象,保證 SDK 調(diào)用 zego_stopAndDeAllocate
后,沒有殘留的異步任務(wù)導(dǎo)致野指針 crash。
- (void)zego_stopAndDeAllocate { dispatch_sync(queue_, ^ { }); if (pool_) { [ZegoLiveRoomApi destroyPixelBufferPool:&pool_]; } queue_ = nil; [client_ destroy]; // 必須 destroy client client_ = nil; buffer_pool_ = nil;}
請注意,開發(fā)者必須在 zego_stopAndDeAllocate 方法中調(diào)用 client 的 destroy 方法,否則會造成內(nèi)存泄漏。
SDK 先調(diào)用 ZegoVideoBufferPool 的 dequeueInputBuffer:height:stride:
方法,通知外部濾鏡當(dāng)前采集圖像的寬高,并請求外部濾鏡返回一個 CVPixelBufferRef 用于拷貝內(nèi)存數(shù)據(jù)。
// SDK 回調(diào)。從 App 獲取 CVPixelBufferRef 對象,用于保存原始視頻幀數(shù)據(jù)- (CVPixelBufferRef)dequeueInputBuffer:(int)width height:(int)height stride:(int)stride { // * 按需創(chuàng)建 CVPixelBufferPool if (width_ != width || height_ != height || stride_ != stride) { if (pool_) { [ZegoLiveRoomApi destroyPixelBufferPool:&pool_]; } if ([ZegoLiveRoomApi createPixelBufferPool:&pool_ width:width height:height]) { width_ = width; height_ = height; stride_ = stride; } else { return nil; } } // * 如果處理不及時,未處理幀超過了 pool 的大小,則丟棄該幀 if (self.pendingCount >= buffer_count_) { return nil; } CVPixelBufferRef pixel_buffer = nil; CVReturn ret = CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, pool_, &pixel_buffer); if (ret != kCVReturnSuccess) { return nil; } else { self.pendingCount = self.pendingCount + 1; // * 返回一個可以用于存儲采集到的圖像的 CVPixelBuffer 實例 return pixel_buffer; }}
當(dāng) SDK 拷貝完數(shù)據(jù)給外部濾鏡后,會調(diào)用 queueInputBuffer:timestamp:
方法通知外部濾鏡。
外部濾鏡應(yīng)當(dāng)按照約定的數(shù)據(jù)傳遞類型,切換線程,異步處理。
此處的演示代碼沒有做任何操作,只是在另一個線程進行數(shù)據(jù)拷貝。開發(fā)者應(yīng)該按照各自需求,實現(xiàn)該方法。
// SDK 回調(diào)。App 在此接口中獲取 SDK 采集到的視頻幀數(shù)據(jù),并進行處理- (void)queueInputBuffer:(CVPixelBufferRef)pixel_buffer timestamp:(unsigned long long)timestamp_100n { // * 采集到的圖像數(shù)據(jù)通過這個傳進來,這個點需要異步處理 dispatch_async(queue_, ^ { [self copyPixelBufferToPool:pixel_buffer timestamp:timestamp_100n]; CVPixelBufferRelease(pixel_buffer); });}
外部濾鏡處理完數(shù)據(jù)后,拷貝給 SDK 的流程和 SDK 調(diào)用外部濾鏡的步驟類似:
dequeueInputBuffer:height:stride:
向 SDK 請求 CVPixelBufferRef 作為拷貝目標(biāo)queueInputBuffer:timestamp:
通知 SDK 拷貝完畢// App 將處理好的數(shù)據(jù),傳遞回 SDK- (void)copyPixelBufferToPool:(CVPixelBufferRef)output timestamp:(unsigned long long)timestamp_100n{ int imageWidth = 0; int imageHeight = 0; int imageStride = 0; if (output) { imageWidth = (int)CVPixelBufferGetWidth(output); imageHeight = (int)CVPixelBufferGetHeight(output); imageStride = (int)CVPixelBufferGetBytesPerRowOfPlane(output, 0); } // * 處理完圖像,需要從 buffer pool 中取出一個 CVPixelBuffer 實例,把處理過的圖像數(shù)據(jù)拷貝到該實例中 CVPixelBufferRef dst = [buffer_pool_ dequeueInputBuffer:imageWidth height:imageHeight stride:imageStride]; if (!dst) { return ; } if ([ZegoLiveRoomApi copyPixelBufferFrom:output to:dst]) { // * 把從 buffer pool 中得到的 CVPixelBuffer 實例傳進來 [buffer_pool_ queueInputBuffer:dst timestamp:timestamp_100n]; } self.pendingCount = self.pendingCount - 1;}
上述步驟的示例代碼可以在 ZegoVideoFilterDemo.h 和 ZegoVideoFilterDemo.m 找到,具體細節(jié)不再贅述。
下述代碼演示了如何創(chuàng)建外部濾鏡工廠。工廠保存了 ZegoVideoFilter 的實例,不會反復(fù)創(chuàng)建。
@interface ZegoVideoFilterFactoryDemo : NSObject<ZegoVideoFilterFactory>@end@implementation ZegoVideoFilterFactoryDemo { id<ZegoVideoFilter> g_filter_;}- (id<ZegoVideoFilter>)zego_create { if (g_filter_ == nil) { g_filter_ = [[ZegoVideoFilterI420Demo alloc]init]; // 此處的 ZegoVideoFilterI420Demo 是異步 I420 濾鏡 } return g_filter_;}- (void)zego_destroy:(id<ZegoVideoFilter>)filter { if (g_filter_ == filter) { g_filter_ = nil; }}
請注意:
大部分情況下,ZegoVideoFilterFactory 會緩存原有 ZegoVideoFilter 實例,開發(fā)者需避免創(chuàng)建新的實例。
開發(fā)者必須保證 ZegoVideoFilter 在 create 和 destroy 之間是可用的,請勿直接銷毀對象。
開發(fā)者需要使用外部濾鏡功能時,請在使用前調(diào)用 setVideoFilterFactory:
設(shè)置外部濾鏡工廠對象(此例中的對象為步驟 1 中所創(chuàng)建的 ZegoVideoFilterFactoryDemo)。該工廠對象不可為空。
請注意,如果用戶釋放了工廠對象,不再需要它時,請調(diào)用本接口將其設(shè)置為空。
if (bUse){ { if (g_filterFactory == nullptr) g_filterFactory = [[ZegoVideoFilterFactoryDemo alloc] init]; [ZegoLiveRoomApi setVideoFilterFactory:g_filterFactory]; } else { [ZegoLiveRoomApi setVideoFilterFactory:nil]; }}
下述代碼,以創(chuàng)建異步拷貝圖像的濾鏡為例,開發(fā)者可根據(jù)需求,參看實現(xiàn)步驟。
ZegoVideoFilterI420Demo 的類定義如下:
// 注意異步濾鏡設(shè)備需要實現(xiàn) ZegoVideoFilter、ZegoVideoBufferPool 協(xié)議ZegoVideoCaptureFromImage.h@interface ZegoVideoFilterI420Demo : NSObject<ZegoVideoFilter, ZegoVideoBufferPool>@endZegoVideoCaptureFromImage.m@interface ZegoVideoFilterI420Demo()@property (atomic) int pendingCount;@end@implementation ZegoVideoFilterI420Demo { id<ZegoVideoFilterClient> client_; id<ZegoVideoBufferPool> buffer_pool_; dispatch_queue_t queue_; int width_; int height_; int stride_; CVPixelBufferPoolRef pool_; int buffer_count_;}
外部濾鏡需要根據(jù) supportBufferType 的類型,對 client 進行轉(zhuǎn)型:
- ZegoVideoBufferTypeAsyncPixelBuffer: SDK 按照 ZegoVideoBufferPool 調(diào)用外部濾鏡。
- ZegoVideoBufferTypeAsyncI420PixelBuffer:SDK 按照 ZegoVideoBufferPool 調(diào)用外部濾鏡,與 Async 型濾鏡只是圖像顏色空間有所區(qū)別。
- ZegoVideoBufferTypeSyncPixelBuffer: SDK 按照 ZegoVideoFilterDelegate 調(diào)用外部濾鏡。
正常來說,如果 SDK 是異步調(diào)用外部濾鏡,外部濾鏡完成前處理后,也按照同樣的步驟回調(diào)SDK。
在本例中,開發(fā)者需要顯式在 supportBufferType
中指定當(dāng)前使用的濾鏡類型為異步濾鏡:
- (ZegoVideoBufferType)supportBufferType { // 返回濾鏡的類型,此濾鏡為:異步 I420 濾鏡 return ZegoVideoBufferTypeAsyncI420PixelBuffer;}
開發(fā)者初始化資源在 zego_allocateAndStart
中進行。
開發(fā)者在 zego_allocateAndStart
中獲取到 client(SDK 內(nèi)部實現(xiàn)的、同樣實現(xiàn) ZegoVideoFilterClient 協(xié)議的對象),用于通知 SDK 處理結(jié)果。
SDK 會在 App 第一次預(yù)覽/推流/拉流時調(diào)用 zego_allocateAndStart
。除非 App 中途調(diào)用過 zego_stopAndDeAllocate
,否則 SDK 不會再調(diào)用 zego_allocateAndStart
。
- (void)zego_allocateAndStart:(id<ZegoVideoFilterClient>) client { client_ = client; if ([client_ conformsToProtocol:@protocol(ZegoVideoBufferPool)]) { buffer_pool_ = (id<ZegoVideoBufferPool>)client; } width_ = 0; height_ = 0; stride_ = 0; pool_ = nil; buffer_count_ = 4; self.pendingCount = 0; queue_ = dispatch_queue_create("video.filter", nil);}
開發(fā)者釋放資源在 zego_stopAndDeAllocate
中進行。
建議同步停止濾鏡任務(wù)后再清理 client 對象,保證 SDK 調(diào)用 zego_stopAndDeAllocate
后,沒有殘留的異步任務(wù)導(dǎo)致野指針 crash。
- (void)zego_stopAndDeAllocate { if (queue_) { dispatch_sync(queue_, ^ { }); queue_ = nil; } if (pool_) { [self destroyPool:&pool_]; pool_ = nil; } if (client_) { [client_ destroy]; client_ = nil; buffer_pool_ = nil; }}
SDK 先調(diào)用 ZegoVideoBufferPool 的 dequeueInputBuffer:height:stride:
方法,通知外部濾鏡當(dāng)前采集圖像的寬高,并請求外部濾鏡返回一個 CVPixelBufferRef 用于拷貝內(nèi)存數(shù)據(jù)。
請注意:
ZegoVideoBufferTypeAsyncI420PixelBuffer 和 ZegoVideoBufferTypeAsyncPixelBuffer 非常相似,僅僅只是傳遞的 CVPixelBufferRef 的顏色空間為 I420,在 iOS API里面對應(yīng)的是 kCVPixelFormatType_420YpCbCr8Planar。
// SDK 回調(diào)。從 App 獲取 CVPixelBufferRef 對象,用于保存視頻幀數(shù)據(jù)- (CVPixelBufferRef)dequeueInputBuffer:(int)width height:(int)height stride:(int)stride { if (width_ != width || height_ != height || stride_ != stride) { if (pool_) { [self destroyPool:&pool_]; } // 注意此處的 createPool:height:pool 中針對 I420 顏色空間做了特殊處理,類型為 kCVPixelFormatType_420YpCbCr8Planar if ([self createPool:width height:height pool:&pool_]) { width_ = width; height_ = height; stride_ = stride; } else { return nil; } } // * 如果處理不及時,未處理幀超過了 pool 的大小,則丟棄該幀 if (self.pendingCount >= buffer_count_) { return nil; } CVPixelBufferRef pixel_buffer = nil; CVReturn ret = CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, pool_, &pixel_buffer); if (ret != kCVReturnSuccess) { return nil; } else { self.pendingCount = self.pendingCount + 1; // * 返回一個可以用于存儲采集到的圖像的 CVPixelBuffer 實例 return pixel_buffer; }}
queueInputBuffer:timestamp:
方法通知外部濾鏡。dequeueInputBuffer:height:stride:
向 SDK 請求 CVPixelBufferRef 作為拷貝目標(biāo)queueInputBuffer:timestamp:
通知 SDK 拷貝完畢外部濾鏡應(yīng)當(dāng)按照約定的數(shù)據(jù)傳遞類型,切換線程,異步處理。
請注意,此處的演示代碼沒有做任何操作,只是在另一個線程進行數(shù)據(jù)拷貝。開發(fā)者應(yīng)該按照各自業(yè)務(wù)需求,差異化實現(xiàn)該方法。
// SDK 回調(diào)。App 在此接口中獲取 SDK 采集到的視頻幀數(shù)據(jù),并進行處理- (void)queueInputBuffer:(CVPixelBufferRef)pixel_buffer timestamp:(unsigned long long)timestamp_100n { dispatch_async(queue_, ^ { int imageWidth = (int)CVPixelBufferGetWidth(pixel_buffer); int imageHeight = (int)CVPixelBufferGetHeight(pixel_buffer); int imageStride = (int)CVPixelBufferGetBytesPerRowOfPlane(pixel_buffer, 0); // * App 需要從 buffer pool 中取出一個 CVPixelBuffer 實例,把處理過的圖像數(shù)據(jù)拷貝到該實例中 CVPixelBufferRef dst = [buffer_pool_ dequeueInputBuffer:imageWidth height:imageHeight stride:imageStride]; if (!dst) { return ; } // add your own code here if ([self copyVideoFrame:pixel_buffer toPixelBuffer:dst]) { // * 把從 buffer pool 中得到的 CVPixelBuffer 實例傳進來 [buffer_pool_ queueInputBuffer:dst timestamp:timestamp_100n]; } self.pendingCount = self.pendingCount - 1; CVPixelBufferRelease(pixel_buffer); });}
上述步驟的示例代碼可以在 ZegoVideoFilterDemo.h 和 ZegoVideoFilterDemo.m 找到,具體細節(jié)不再贅述。
下述代碼演示了如何創(chuàng)建外部濾鏡工廠。工廠保存了 ZegoVideoFilter 的實例,不會反復(fù)創(chuàng)建。
@interface ZegoVideoFilterFactoryDemo : NSObject<ZegoVideoFilterFactory>@end@implementation ZegoVideoFilterFactoryDemo { id<ZegoVideoFilter> g_filter_;}- (id<ZegoVideoFilter>)zego_create { if (g_filter_ == nil) { g_filter_ = [[ZegoVideoFilterDemo2 alloc]init]; // 此處的 ZegoVideoFilterDemo2 是同步濾鏡 } return g_filter_;}- (void)zego_destroy:(id<ZegoVideoFilter>)filter { if (g_filter_ == filter) { g_filter_ = nil; }}
請注意:
大部分情況下,ZegoVideoFilterFactory 會緩存原有 ZegoVideoFilter 實例,開發(fā)者需避免創(chuàng)建新的實例。
開發(fā)者必須保證 ZegoVideoFilter 在 create 和 destroy 之間是可用的,請勿直接銷毀對象。
開發(fā)者需要使用外部濾鏡功能時,請在使用前調(diào)用 setVideoFilterFactory:
設(shè)置外部濾鏡工廠對象(此例中的對象為步驟 1 中所創(chuàng)建的 ZegoVideoFilterFactoryDemo)。該工廠對象不可為空。
請注意,如果用戶釋放了工廠對象,不再需要它時,請調(diào)用本接口將其設(shè)置為空。
if (bUse){ { if (g_filterFactory == nullptr) g_filterFactory = [[ZegoVideoFilterFactoryDemo alloc] init]; [ZegoLiveRoomApi setVideoFilterFactory:g_filterFactory]; } else { [ZegoLiveRoomApi setVideoFilterFactory:nil]; }}
下述代碼,以創(chuàng)建同步拷貝圖像的濾鏡為例,開發(fā)者可根據(jù)需求,參看實現(xiàn)步驟。
ZegoVideoFilterDemo2 的類定義如下:
// 注意同步濾鏡設(shè)備需要實現(xiàn) ZegoVideoFilter、ZegoVideoFilterDelegate 協(xié)議ZegoVideoCaptureFromImage.h@interface ZegoVideoFilterDemo2 : NSObject<ZegoVideoFilter, ZegoVideoFilterDelegate>@endZegoVideoCaptureFromImage.m@implementation ZegoVideoFilterDemo2 { id<ZegoVideoFilterClient> client_; id<ZegoVideoFilterDelegate> delegate_; ZegoImageFilter* filter_;}
外部濾鏡需要根據(jù) supportBufferType 的類型,對 client 進行轉(zhuǎn)型:
- ZegoVideoBufferTypeAsyncPixelBuffer: SDK 按照 ZegoVideoBufferPool 調(diào)用外部濾鏡。
- ZegoVideoBufferTypeAsyncI420PixelBuffer:SDK 按照 ZegoVideoBufferPool 調(diào)用外部濾鏡,與 Async 型濾鏡只是圖像顏色空間有所區(qū)別。
- ZegoVideoBufferTypeSyncPixelBuffer: SDK 按照 ZegoVideoFilterDelegate 調(diào)用外部濾鏡。
正常來說,如果 SDK 是異步調(diào)用外部濾鏡,外部濾鏡完成前處理后,也按照同樣的步驟回調(diào)SDK。
在本例中,開發(fā)者需要顯式在 supportBufferType
中指定當(dāng)前使用的濾鏡類型為同步濾鏡:
- (ZegoVideoBufferType)supportBufferType { // 返回濾鏡的類型,此濾鏡為:同步濾鏡 return ZegoVideoBufferTypeSyncPixelBuffer;}
開發(fā)者初始化資源在 zego_allocateAndStart
中進行。
開發(fā)者在 zego_allocateAndStart
中獲取到 client(SDK 內(nèi)部實現(xiàn)的、同樣實現(xiàn) ZegoVideoFilterClient 協(xié)議的對象),用于通知 SDK 處理結(jié)果。
SDK 會在 App 第一次預(yù)覽/推流/拉流時調(diào)用 zego_allocateAndStart
。除非 App 中途調(diào)用過 zego_stopAndDeAllocate
,否則 SDK 不會再調(diào)用 zego_allocateAndStart
。
- (void)zego_allocateAndStart:(id<ZegoVideoFilterClient>) client { client_ = client; if ([client_ conformsToProtocol:@protocol(ZegoVideoFilterDelegate)]) { delegate_ = (id<ZegoVideoFilterDelegate>)client; } filter_ = [[ZegoImageFilter alloc] init]; [filter_ create]; [filter_ setCustomizedFilter:ZEGO_FILTER_CUSTOM_LOMO];}
開發(fā)者釋放資源在 zego_stopAndDeAllocate
中進行。
建議停止濾鏡任務(wù)后再清理 client 對象,保證 SDK 調(diào)用 zego_stopAndDeAllocate
后,沒有殘留的異步任務(wù)導(dǎo)致野指針 crash。
- (void)zego_stopAndDeAllocate { [filter_ destroy]; filter_ = nil; [client_ destroy]; // 必須 destroy client client_ = nil; delegate_ = nil;}
同步外部濾鏡數(shù)據(jù)傳遞的流程比較簡單:
ZegoVideoFilterDelegate的onProcess:withTimeStatmp:
方法同步傳遞圖像數(shù)據(jù)給外部濾鏡。onProcess:withTimeStatmp:
方法把處理后的數(shù)據(jù)傳給 SDK。// SDK 回調(diào)。同步傳遞圖像數(shù)據(jù)給外部濾鏡- (void)onProcess:(CVPixelBufferRef)pixel_buffer withTimeStatmp:(unsigned long long)timestamp_100 { // * 采集到的圖像數(shù)據(jù)通過這個傳進來,同步處理完返回處理結(jié)果 CVPixelBufferRef output = [filter_ render:pixel_buffer]; // filter_ 為 Demo 實現(xiàn)的外部濾鏡示例對象 [delegate_ onProcess:output withTimeStatmp:timestamp_100]; // 外部濾鏡將處理后的數(shù)據(jù)傳遞回 SDK}
上述步驟的示例代碼可以在 ZegoVideoFilterDemo.h 和 ZegoVideoFilterDemo.m 找到,具體細節(jié)不再贅述。
如何訪問 CVPixelBufferRef 持有的圖像數(shù)據(jù)?
請參考 ZegoLiveRoomApi 的 copyPixelBufferFrom:to:
方法,然后對照蘋果官方頭文件。
ZegoVideoFilterFactory 的子類什么時候釋放?
我們推薦把工廠的實例保存為單例,僅作為 SDK 管理外部濾鏡生命周期的通道,開發(fā)者可以為工廠子類添加 setter 和 getter,一起管理濾鏡的生命周期。
聯(lián)系客服