1. 程式人生 > >iOS 視訊外部採集

iOS 視訊外部採集

視訊外部採集

1、使用場景

當開發者業務中出現以下情況時,我們推薦使用 SDK 的外部採集功能:

  • 普通攝像頭的採集無法滿足需求。例如,包含了大量的原有業務。

  • 直播過程中,開發者需要使用攝像頭完成的額外功能和 SDK 的預設邏輯有衝突,導致攝像頭無法正常使用。例如,直播到一半,需要錄製短視訊。

  • 直播非攝像頭資料。例如視訊播放、螢幕分享、遊戲直播等。

這是即構科技音視訊SDK(ZegoLiveRoom SDK )高階功能系列第十篇。

2、功能簡介

考慮到裝置的獨佔問題,SDK 的視訊外部採集採用的是面向物件的設計,幫助使用者把原有采集程式碼封裝成外部採集裝置。

開發者通過實現 ZegoVideoCaptureFactory 和 ZegoVideoCaptureDevice 協議,可以把採集的資料傳給 SDK 進行編碼推流:

ZegoVideoCaptureFactory 是外部採集的入口,定義了建立、銷燬 ZegoVideoCaptureDevice 的介面,向 SDK 提供管理 ZegoVideoCaptureDevice 生命週期的能力。需要呼叫 setVideoCaptureFactory 的地方必須實現該介面。

ZegoVideoCaptureDevice 定義基本的元件能力,包括 zego_allocateAndStart、zego_stopAndDeAllocate、zego_startCapture、zego_stopCapture,方便 SDK 在直播流程中進行互動。

請注意,SDK 會在適當的時機建立和銷燬 ZegoVideoCaptureDevice,開發者無需擔心生命週期不一致的問題。

3、步驟

外部採集的介面呼叫流程如下圖所示:
在這裡插入圖片描述
3.1 建立外部採集工廠

下述程式碼展示瞭如何建立外部採集工廠。工廠儲存了 ZegoVideoCaptureDevice 的例項,不會反覆建立。

@interface ZegoVideoCaptureFactory : NSObject<ZegoVideoCaptureFactory>
@end

@implementation ZegoVideoCaptureFactory {
    ZegoVideoCaptureFromImage * g_device_;
}

- (id<ZegoVideoCaptureDevice>)zego_create:(NSString*)deviceId {
    if (g_device_ == nil) {
        g_device_ = [[ZegoVideoCaptureFromImage alloc]init];
    }
    return g_device_;
}

- (void)zego_destroy:(id<ZegoVideoCaptureDevice>)device {

}

@end

請注意:

大部分情況下,ZegoVideoCaptureFactory 會快取 ZegoVideoCaptureDevice例項,開發者需避免建立新的例項,造成爭搶獨佔裝置(例如攝像頭)。

開發者必須保證 ZegoVideoCaptureDevice 在 create 和 destroy 之間是可用的,請勿直接銷燬物件。

3.2 設定外部採集工廠

開發者需要使用外部採集功能時,請在使用前呼叫 setVideoCaptureFactory: 設定外部採集工廠物件(此例中的物件為步驟 1 中所建立的 ZegoVideoCaptureFactory),該工廠物件不可為空。

setVideoCaptureFactory: 必須在 InitSDK 前呼叫。

請注意

  1. 如果使用者釋放了工廠物件,不再需要它時,請呼叫本介面將其設定為空。

  2. 若客戶端使用開關控制是否使用外部採集,在開啟外部採集後,需要先釋放 SDK 物件(直接將物件置為 nil),再重新執行外部採集的設定。

if (bUse)
{ 
    if (g_factory == nil)
        g_factory = [[VideoCaptureFactoryDemo alloc] init];

    [ZegoLiveRoomApi setVideoCaptureFactory:g_factory];
}
else
{
    [ZegoLiveRoomApi setVideoCaptureFactory:nil];
}

3.3 建立外部採集裝置

下述程式碼,以建立在記憶體中繪製圖片的採集裝置( ZegoVideoCaptureFromImage )為例,開發者可按各自的需求,參看實現步驟。

類定義

ZegoVideoCaptureFromImage 的類定義如下:

ZegoVideoCaptureFromImage.h

// 注意採集裝置需要遵循 ZegoVideoCaptureDevice 協議
@interface ZegoVideoCaptureFromImage : NSObject<ZegoVideoCaptureDevice>

@end

ZegoVideoCaptureFromImage.m

@implementation ZegoVideoCaptureFromImage

@end

告知 SDK 當前採集資料的型別

由於採集的多樣性,SDK 支援多種外部採集資料傳遞格式,所以開發者必須顯示告知 SDK 當前採集裝置使用何種資料傳遞型別。目前 SDK 支援的型別有:
在這裡插入圖片描述
後續示例程式碼都將以官方推薦的 CVPixelBuffer 型別來傳輸採集資料,用於演示 ZegoVideoCaptureFromImage 的使用:

- (ZegoVideoCaptureDeviceOutputBufferType)zego_supportBufferType {
    return ZegoVideoCaptureDeviceOutputBufferTypeCVPixelBuffer;
}

請注意 zego_supportBufferType 是optional的,但是在使用 ZegoVideoCaptureDeviceOutputBufferTypeGlTexture2D
ZegoVideoCaptureDeviceOutputBufferTypeEncodedFrame 時必須實現。

初始化資源

開發者初始化資源在 zego_allocateAndStart 中進行。

開發者在 zego_allocateAndStart 中獲取到 client (SDK 內部實現的、同樣實現 ZegoVideoCaptureClientDelegate 協議的客戶端),用於通知 SDK 採集結果。

SDK 會在 App 第一次預覽 / 推流 / 拉流時呼叫 zego_allocateAndStart。除非 App 中途呼叫過 zego_stopAndDeAllocate,否則 SDK 不會再呼叫 zego_allocateAndStart。

#pragma mark - ZegoVideoCaptureDevice

- (void)zego_allocateAndStart:(id<ZegoVideoCaptureClientDelegate>)client {
    client_ = client;
    is_take_photo_ = false;
}

釋放資源

開發者釋放資源在 zego_stopAndDeAllocate 中進行。

建議同步停止採集任務後清理 client 物件,保證 SDK 呼叫 zego_stopAndDeAllocate 後,沒有殘留的非同步任務導致野指標 crash。

- (void)zego_stopAndDeAllocate {
    [client_ destroy];   // 必須 destroy client
    client_ = nil;
}

請注意,開發者必須在 zego_stopAndDeAllocate 方法中呼叫 client 的 destroy 方法,否則會造成記憶體洩漏。

啟動採集

推流成功後,開發者需要在 zego_startCapture 中,採集資料並傳遞給 SDK 的 client 物件。

這裡演示的是啟動一個定時器,按照幀率定時觸發傳送資料。

// SDK 推流成功後,呼叫 zego_startCapture 通知外部採集裝置把採集到的影象傳給 client 物件。
- (int)zego_startCapture {
    if(m_oState.capture) {
        // * already started
        return 0;
    }

    m_oState.capture = true;

    dispatch_async(dispatch_get_main_queue(), ^{
        [g_fps_timer invalidate];
        int fps = m_oSettings.fps > 0 ?: 15;
        fps = 15;
        NSLog(@"%s, fps: %d", __func__, fps);

        // 啟動一個定時器,按照幀率定時觸發傳送資料
        g_fps_timer = [NSTimer scheduledTimerWithTimeInterval:1.0/fps target:self selector:@selector(handleTick) userInfo:nil repeats:YES];

        if (pb) {
            CVPixelBufferRelease(pb);
            pb = NULL;
        }
    });
    return 0;
}

定時器觸發後,外部採集裝置在記憶體中繪製圖像,並通過 client 的 onIncomingCapturedData:withPresentationTimeStamp: 方法,把資料傳給 SDK 進行編碼推流。

- (void)handleTick {
    if (pb) {
        CVPixelBufferRelease(pb);
        pb = NULL;
    }

    if (!pb) {
        UIImage *img = [UIImage imageNamed:@"[email protected]"];
        pb = [self pixelBufferFromCGImage:img.CGImage];
    }

    struct timeval tv_now;
    gettimeofday(&tv_now, NULL);
    unsigned long long t = (unsigned long long)(tv_now.tv_sec) * 1000 + tv_now.tv_usec / 1000;

    CMTime pts = CMTimeMakeWithSeconds(t, 1000);

    // SDK 接受外部採集的視訊幀資料
    [client_ onIncomingCapturedData:pb withPresentationTimeStamp:pts];
}

採集的影象資料存放在 CVPixelBufferRef 結構體中。

  • 顏色格式推薦使用:kCVPixelFormatType_32BGRA 和
    kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange。

  • 時間戳推薦使用:原始採集 API 返回的時間戳(如果是攝像頭,可直接透傳;如果是其他方式,可以使用系統當前時間,精度至少為毫秒)。

停止採集

開發者需要在 zego_startCapture 中停止外部採集裝置的採集工作。

這裡演示的是停止定時器。

// SDK停止推流時,呼叫 zego_stopCapture 通知外部採集裝置停止工作
- (int)zego_stopCapture {
    if(!m_oState.capture) {
        // * capture is not started
        return 0;
    }

    m_oState.capture = false;

    dispatch_async(dispatch_get_main_queue(), ^{
        [g_fps_timer invalidate];
        g_fps_timer = nil;
    });
    return 0;
}

上述的示例程式碼均可以在 Demo 中的 ZegoVideoCaptureFromImage.h 和 ZegoVideoCaptureFromImage.m 找到,具體細節不再贅述。