视频外部采集
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 前调用。
请注意
如果用户释放了工厂对象,不再需要它时,请调用本接口将其设置为空。
若客户端使用开关控制是否使用外部采集,在开启外部采集后,需要先释放 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 找到,具体细节不再赘述。