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

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

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

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

本文介绍了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转换成CGImageRef
dispatch_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. 如何利用腾讯云COS为静态博客添加动态相册

    前言 本文首发于个人网站Jianger's Blog,欢迎访问订阅.个人博客小站刚建站不久,想着除了主题里的功能外再添加上相册模块,于是半搜索半摸索把相册模块搞出来了,最后采用了利用腾讯云对象存储作图 ...

  2. 在腾讯云容器服务 TKE 中利用 HPA 实现业务的弹性伸缩

    在 TKE 上利用 HPA 实现业务的弹性伸缩 概述 Kubernetes Pod 水平自动扩缩(Horizontal Pod Autoscaler,以下简称 HPA)可以基于 CPU 利用率.内存利 ...

  3. Taro开发微信小程序之利用腾讯地图sdk标记

    首先要下载腾讯地图提供的sdk,放在项目的对应目录下,引用. import QQMapWX from '../../sdks/qqmap-wx-jssdk' 设置好后,就可以开始使用了. let qq ...

  4. 利用腾讯云COS云对象存储定时远程备份网站

    版权声明:本文由张戈 原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/942851001487125915 来源:腾云阁 h ...

  5. 利用腾讯云免费证书打造全https站

    什么是https? 超文本传输安全协议(Hypertext Transfer Protocol Secure,缩写为HTTPS)是一种网络安全传输协议http是HTTP协议运行在TCP之上,所有传输的 ...

  6. 利用腾讯云为你的域名申请并配置免费SSL一年

    我想,点进来的朋友,应该都知道SSL的重要性吧.这里就简单提一下,大型网站域名只有配置了SSL后,才会更加安全. 现在,微信小程序也开始要求后台必须是SSL配置后的域名了.说了这么多,估计有些人还是有 ...

  7. 利用腾讯云函数部署.Net 5米游社原神每日签到功能

    自从GitHub批量禁止滥用Action功能后,项目不得不考虑另外方案执行应用.其中腾讯云函数被大家作为不错的选择(虽然马上也要收费了). 但对于.Net的部署目前资源很少,而且我也没学过bash.在 ...

  8. QQ音乐PB级ClickHouse实时数据平台架构演进之路

    导语 | OLAP(On-Line Analytical Processing),是数据仓库系统的主要应用形式,帮助分析人员多角度分析数据,挖掘数据价值.本文基于QQ音乐海量大数据实时分析场景,通过Q ...

  9. QQ音乐Android客户端Web页面通用性能优化实践

    QQ音乐 Android 客户端的 Web 页面日均 PV 达到千万量级,然而页面的打开耗时与 Native 页面相距甚远,需要系统性优化.本文将介绍 QQ 音乐 Android 客户端在进行 Web ...

随机推荐

  1. Hdu2389 Rain on your Parade (HK二分图最大匹配)

    Rain on your Parade Problem Description You’re giving a party in the garden of your villa by the sea ...

  2. hdu 1576 A/B 【扩展欧几里德】

    题目 A/9973=n 那么:n= A - A / 9973 * 9973   --① 设:A/B=x  则A=B*x,代入①  得  n=B*x-A/9973*9973 然后这个方程中的A/9973 ...

  3. [php]PHP_函数收集

    //http://php.net/manual/en/control-structures.break.php //break ends execution of the current for, f ...

  4. 安装使用Entity Framework Power Tool Bate4 (Code First)从已建好的数据自动生成项目中的对应Model(新手贴,望各位大侠给予指点)

    从开始学习使用MVC以后,同时也开始接触EF,很多原理都不是太懂,只知道安装了EF以后,点击哪里可以生成数据库对应的Model,不用再自己手写Model.这里记录的就是如何从已建立好的数据库生成项目代 ...

  5. ASP.NET Core ef启用数据迁移

    创建一个项目 通过Nuget获取EF Core相关的扩展包 appsettings.json 建立数据库连接串 创建数据库上下文EntityDbContext类,用于实体类映射数据库表 使用包管理器控 ...

  6. C# 不添加WEB引用调用WSDL接口

    在项目中添加WEB引用耦合度较高,更新时要更新引用,所以我建议不添加WEB引用调用WSDL接口,废话不多说,直接上代码 例如WSDL地址为:http://XXX.XX.XXX.XXX:9115/WsP ...

  7. 【CJOJ2616】 【HZOI 2016】偏序 I(cdq分治,树状数组)

    传送门 CJOJ Solution 考虑这是一个四维偏序对吧. 直接cdq套在一起,然后这题有两种实现方法(树状数组的更快!) 代码实现1(cdq+cdq+cdq) /* mail: mleautom ...

  8. 通过Yum快速部署LAMP与LNMP

    LAMP动态网站部署架构是由一套 Linux+Apache+MySQL+PHP 组成的动态网站系统解决方案. LNMP动态网站部署架构是由一套 Linux+Nginx+MySQL+PHP 组成的动态网 ...

  9. <c:if test=""></c:if>如何判断空(使用例子)

    <c:if test=""></c:if>如何判断空(使用例子) 标签: session / 前端 / c-if userName是登录的时候放到sessi ...

  10. Git文件状态

    在Git中,文件状态是一个非常重要的概念,不同的状态对应不同的操作.因此,要想熟练掌握Git的用法,需要了解Git的几种文件状态. Git库所在的文件夹中的文件大致有4种状态: Untracked:未 ...