欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~

本文由 腾讯游戏云发表于 云+社区专栏

看着精彩的德甲赛事,突然裁判一声口哨,球赛断掉了,屏幕开始自动播放“吃麦趣鸡盒,看德甲比赛”的视频广告

那么问题来了,如何在直播流中,无缝的插入点播视频文件呢?

本文介绍了QQ音乐基于腾讯云AVSDK,实现互动直播插播动画的方案以及踩过的坑。

01

从产品经理给的需求说起

“开场动画?插播广告?”

不久之前,产品同学说我们要在音视频直播中,加一个开场动画。

要播放插播动画,怎么做呢?对于视频直播来说,当前直播画面流怎么处理?对于音频来说,又怎么输入一路流呢?

02

梳理技术方案

互动直播的方式,是把主播的画面推送到观众面前,而主播端的画面,既可以来自摄像头采集的数据,也可以来自其它的输入流。那么如果腾讯云的AVSDK能支持到播放输入流,就能通过在主播端本地解码一个视频文件,然后把这路流的数据推到观众端的方式,让所有的角色都能播放插播动画了。幸运的是,腾讯云AVSDK可以支持到这个特性,具体的方法有下面两种:

第一种:替换视频画面

/*!@abstract      对本地采集视频进行预处理的回调。@discussion    主线程回调,方面直接在回调中实现视频渲染。@param         frameData       本地采集的视频帧,对其中data数据的美颜、滤镜、特效等图像处理,会回传给SDK编码、发送,在远端收到的视频中生效。@see           QAVVideoFrame*/
- (void)OnLocalVideoPreProcess:(QAVVideoFrame *)frameData;

主播侧本地在采集到摄像头的数据后,在编码上行到服务器之前,会提供一个接口给予业务侧做预处理的回调,所以,对于视频直播,我们可以利用这个接口,把上行输入的视频画面修改为要插播进来动画的视频帧,这样,从观众角度看,被插播了视频动画。

第二种:使用外部输入流

/*!@abstract      开启外部视频采集功能时,向SDK传入外部采集的视频帧。@return        QAV_OK 成功。QAV_ERR_ROOM_NOT_EXIST 房间不存在,进房后调用才生效。QAV_ERR_DEVICE_NOT_EXIST 视频设备不存在。QAV_ERR_FAIL 失败。@see           QAVVideoFrame*/
- (int)fillExternalCaptureFrame:(QAVVideoFrame *)frame;

最开始时,我错误的认为,仅仅使用第二种方式就能够满足同时在音视频两种直播中插播动画的需求,但是实际实践的时候发现,如果要播放外部输入流,必须要先关闭摄像头画面。这个操作会引起腾讯云后台的视频位切换,并通过下面这个函数通知到观众端:

/*!@abstract      房间成员状态变化通知的函数。@discussion    当房间成员发生状态变化(如是否发音频、是否发视频等)时,会通过该函数通知业务侧。@param         eventID         状态变化id,详见QAVUpdateEvent的定义。@param         endpoints       发生状态变化的成员id列表。*/
- (void)OnEndpointsUpdateInfo:(QAVUpdateEvent)eventID endpointlist:(NSArray *)endpoints;

视频位短时间内的切换,会导致一些时序上的问题,跟SDK侧讨论也认为不建议这样做。最终,QQ音乐采用了两个方案共存的方式。

03

视频格式选型

对于插播动画的视频文件,如果考虑到如果需要支持流式播放,码率低,高画质,可以使用H264裸流+VideoToolBox硬解的方式。如果说只播放本地文件,可以采用H264编码的mp4+AVURLAsset解码的方式。因为目前还没有流式播放的需求,而设计同学直接给到的是一个mp4文件,所以后者则看起来更合理。笔者出于个人兴趣,对两种方案的实现都做了尝试,但是也遇到了下面的一些坑,总结一下,希望能让其它同学少走点弯路:

1.分辨率与帧率的配置

视频的分辨率需要与腾讯云后台的SPEAR引擎配置中的上行分辨率一致,QQ音乐选择的视频上行配置是960x540,帧率是15帧。但是实际的播放中,发现效果并不理想,所以需要播放更高分辨率的数据,这一步可以通过更换AVSDK的角色RoleName来实现,这里不做延伸。

另外一个问题是从摄像头采集上来的数据,是下图的角度为1的图像,在渲染的时候,会默认被旋转90度,在更改视频画面时,需要保持两者的一致性。摄像头采集的数据格式是NV12,而本地填充画面的格式可以是I420。在绘制时,可以根据数据格式来判断是否需要旋转图像展示。

2.ffmpeg 转h264裸流解码问题

从iOS8开始,苹果开放了VideoToolBox,使得应用程序拥有了硬解码h264格式的能力。具体的实现与分析,可以参考《iOS-H264 硬解码》这篇文章。因为设计同学给到的是一个mp4文件,所以首先需要先把mp4转为H264的裸码流,再做解码。这里我使用ffmpeg来做转换:

ffmpeg -i test.mp4 -codec copy -bsf: h264_mp4toannexb -s 960*540 -f h264 output.264

其中,annexb就是h264裸码流Elementary Stream的格式。对于Elementary Stream,sps跟pps并没有单独的包,而是附加在I帧前面,一般长这样:

00 00 00 01 sps 00 00 00 01 pps 00 00 00 01 I 帧

VideoToolBox的硬解码一般通过以下几个步骤:

1. 读取视频流
2. 找出sps,pps的信息,创建CMVideoFormatDescriptionRef,传入下一步作为参数
3. VTDecompressionSessionCreate:创建解码会话
4. VTDecompressionSessionDecodeFrame:解码一个视频帧
5. VTDecompressionSessionInvalidate:释放解码会话

但是对上面转换后的裸码流解码,发现总是会遇到解不出来数据的问题。分析转换后的文件发现,转换后的格式并不是纯码流,而被ffmpeg加入了一些无关的信息:

但是也不是没有办法,可以使用这个工具H264Naked来找出二进制文件中的这一段数据一并删掉。再尝试,发现依然播放不了,原因是在上面的第3步解码会话创建失败了,错误码OSStatus = -5。很坑的是,这个错误码在OSStatus.com中无法查到对应的错误信息,通过对比好坏两个文件的差异发现,解码失败的文件中,pps 前面的 startcode并不是3个0开头的,而是这样子

00 00 00 01 sps 00 00 01 pps 00 00 00 01 I 帧

但是实际上,通过查看h264的官方文档,发现两种形式都是正确的

而我只考虑了第一种情况,却忽略了第二种,导致解出来的pps数据错了。通过手动插入一个00,或者解码器兼容这种情况,都可以解决这个问题。但是同时也看出,这种方式很不直观。所以也就引入了下面的第二种方法。

3. AVAssetReader 解码视频

使用AVAssetReader解码出yuv比较简单,下面直接贴出代码:

    AVURLAsset *asset = [AVURLAsset URLAssetWithURL:[[NSURL alloc] initFileURLWithPath:path] options:nil];NSError *error;AVAssetReader* reader = [[AVAssetReader alloc] initWithAsset:asset error:&error];NSArray* videoTracks = [asset tracksWithMediaType:AVMediaTypeVideo];AVAssetTrack* videoTrack = [videoTracks objectAtIndex:0];int m_pixelFormatType = kCVPixelFormatType_420YpCbCr8Planar;NSDictionary* options = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt: (int)m_pixelFormatType] forKey:(id)kCVPixelBufferPixelFormatTypeKey];AVAssetReaderTrackOutput* videoReaderOutput = [[AVAssetReaderTrackOutput alloc] initWithTrack:videoTrack outputSettings:options];[reader addOutput:videoReaderOutput];[reader startReading];// 读取视频每一个buffer转换成CGImageRefdispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ while ([reader status] == AVAssetReaderStatusReading && videoTrack.nominalFrameRate > 0) {CMSampleBufferRef sampleBuff = [videoReaderOutput copyNextSampleBuffer];// 对sampleBuff 做点什么});

这里只说遇到的坑,有的mp4视频解码后绘制时会有一个迷之绿条,就像下面这个图

这是为什么,代码实现如下所示,我们先取出y分量的数据,再取出uv分量的数据,看起来没有问题,但是这实际上却不是我们的视频格式对应的数据存储方式。

// 首先把Samplebuff转成cvBufferRef, cvBufferRef中存储了像素缓冲区的数据
CVImageBufferRef cvBufferRef = CMSampleBufferGetImageBuffer(sampleBuff);
// 锁定地址,这样才能之后从主存访问到数据
CVPixelBufferLockBaseAddress(cvBufferRef, kCVPixelBufferLock_ReadOnly);
// 获取y分量的数据
unsigned char *y_frame = (unsigned char*)CVPixelBufferGetBaseAddressOfPlane(cvBufferRef, 0);
// 获取uv分量的数据
unsigned char *uv_frame = (unsigned char*)CVPixelBufferGetBaseAddressOfPlane(cvBufferRef, 1);

这份代码cvBufferRef中存储数据格式应该是:

typedef struct CVPlanarPixelBufferInfo_YCbCrPlanar   CVPlanarPixelBufferInfo_YCbCrPlanar;
struct CVPlanarPixelBufferInfo_YCbCrBiPlanar {CVPlanarComponentInfo  componentInfoY;CVPlanarComponentInfo  componentInfoCbCr;
};

然而第一份代码中,使用的pixelFormatType是kCVPixelFormatType_420YpCbCr8Planar,存储的数据格式却是:

typedef struct CVPlanarPixelBufferInfo         CVPlanarPixelBufferInfo;
struct CVPlanarPixelBufferInfo_YCbCrPlanar {CVPlanarComponentInfo  componentInfoY;CVPlanarComponentInfo  componentInfoCb;CVPlanarComponentInfo  componentInfoCr;
};

也就是说,这里应该把yuv按照三个分量来解码,而不是两个分量。 实现正确的解码方式,成功消除了绿条。

至此,遇到的坑就都踩完了,效果也不错。

最后,希望这篇文章能够对你有所帮助,在直播开发上,少走点弯路

相关阅读
欲练JS,必先攻CSS
交互微动效设计指南
【每日课程推荐】机器学习实战!快速入门在线广告业务及CTR相应知识

此文已由作者授权腾讯云+社区发布,更多原文请点击

搜索关注公众号「云加社区」,第一时间获取技术干货,关注后回复1024 送你一份技术课程大礼包!

海量技术实践经验,尽在云加社区!

从QQ音乐开发,探讨如何利用腾讯云SDK在直播中加入视频动画相关推荐

  1. 利用腾讯云 SDK 动态更新dnspod域名IP(原创)

    之前一直用f3322.net的域名解析,但是因为觉得它不够稳定,所以近期改用dnspo免费域名解析,为了更稳定地使用,我利用腾讯云SDK实现了ip地址动态更新         由于本人的opwrt不知 ...

  2. 3 天开发物联网应用!腾讯云 IoT 超级小程序来了

    腾讯在物联网的故事要从 6 年前开始说起. 作者 | 唐小引 出品 | CSDN(ID:CSDNnews) 在腾讯复盘起过去的 20 年里有哪些标志性的事件时,马化腾提出腾讯要「连接一切」和「腾讯云正 ...

  3. web利用腾讯云点播上传视频

    web利用腾讯云点播上传视频到云服务器 第一步导入 <script src="//imgcache.qq.com/open/qcloud/js/vod/sdk/ugcUploader. ...

  4. python 腾讯视频签到_利用腾讯云函数设置定时任务实现腾讯视频自动签到获得V力值...

    腾讯视频vip花了钱,还不能自动升级,要每天签到才涨V力值.升级VIP等级..实在蛋疼.这次又是云服务器函数,搞了个脚本COOKIES方式登录.测试了40多天.还没到期,感觉自用够了.分享出来吧..c ...

  5. 利用腾讯云Python函数实现机场每日自动签到领流量

    利用腾讯云Python函数实现机场每日自动签到领流量 基于项目 https://github.com/zhjc1124/ssr_autocheckin 修改 sspanel自动签到脚本(腾讯云函数) ...

  6. 利用腾讯云函数做蓝奏云解析API

    利用腾讯云函数做蓝奏云解析API 前言 忽然在网上看到网友说tenapi的蓝奏云解析不能用了,恰巧又在B站上看到某up主的相关文章,一时起意就用up主的代码稍加更改,改造了个能在腾讯云函数跑起来的版本 ...

  7. 微信小程序利用腾讯云IM发送语音 + 图片

    微信小程序利用腾讯云IM发送语音 + 图片 能做到这里 说明你已经可以发送普通文本了 如果没有的话可以看一下我的上一篇文章 有完整的讲解 效果图 语音聊天 发送图片 传送 → 发送图片 发送语音 ** ...

  8. 【腾讯云服务器+PicGo搭建自己的图床】利用腾讯云服务器搭建自己的私人图床

    起因 一直以来用的gitee图床挂了,本来想折腾折腾再换个图床的,但仔细想想任何图床都有再次挂掉的风险,千好万好不如自己有,之前一直图方便懒就没搭建自己的图床,今天趁这个机会自己利用腾讯云COS搭建一 ...

  9. 利用腾讯云轻量搭建私人云盘-Cloudreve

    利用腾讯云轻量搭建私人云盘-Cloudreve 购买链接:https://cloud.tencent.com/act/pro/lighthouse 大陆地区1核1G3Mbps的机器128一年,非常适合 ...

最新文章

  1. Android开发者指南(18) —— Web Apps Overview
  2. 谷歌跟随微软加入云安全联盟 亚马逊依然游离
  3. 简明高效的 Java 并发编程学习指南
  4. zabbix mysql路径_ZABBIX数据库迁移目录
  5. XCTF-高手进阶区:NewsCenter
  6. 2020牛客NOIP赛前集训营-提高组(第三场)C-牛半仙的妹子Tree【虚树,最短路】
  7. Weblogic 连接池的建立程序应用
  8. 6.828 - lab3
  9. python写梦幻西游脚本精灵_奔三新人学习按键精灵脚本做冷门项目
  10. 金融行业实战项目:如何理解业务?
  11. ListView 优化之 ViewHolder 复用机制
  12. 荣耀9igoogle模式_初学者:如何从iGoogle切换回纯Google主页
  13. 曹鹏CSS视频教程 编程之邦
  14. 【量化交易基础】金融相关基础知识
  15. stm32 F40x CCM数据区的使用
  16. STK12已出,STK 12 新特性介绍
  17. 【HTML5】调查问卷制作简约版
  18. 解决 Costmap2DROS transform timeout.问题
  19. 基于MATLAB 2021b的机器学习、深度学习实践应用
  20. 我经历过的那些奇葩用户体验(持续更新中。。。)

热门文章

  1. 智能(语音)对话系统架构研究
  2. web3开发课程精选
  3. 小米4 第三方re奇兔_小米推送测试
  4. 大彩串口屏之LUA使用1
  5. HDU 3236 Gift Hunting (程序猿的哄女朋友方式)
  6. 【评测】肠道微生物核酸提取试剂盒
  7. 【假期学习计划】深度强化学习算法与应用培训班
  8. epub转换mobi
  9. 掌阅科技让数字化阅读更便捷
  10. web网页设计实例作业 网页Dreamweaver设计