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

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

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

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

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

本文介绍了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 送你一份技术课程大礼包!

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

原文地址:http://blog.51cto.com/13957478/2300774

时间: 2024-10-21 12:39:56

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

腾讯云如何建站,利用腾讯云服务器建站流程介绍

腾讯云服务器买好之后,下一步我们就是建立自己的网站,对于一些小白用户来说,还不知道购买好腾讯云服务器之后,如何建立自己的网站,今天就介绍下利用腾讯云服务器建站流程: [腾讯云]云产品采购季,助力行业复工.1核2G云服务器,首年99元 https://cloud.tencent.com/act/cps/redirect?redirect=1053&cps_key=bc2a905407a3a1aaa9ff26fe9b78522f&from=console 首先我们登录腾讯云服务器控制台,可以通

利用腾讯云服务器进行微校开放平台开发

版权声明:本文由追梦者原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/225 来源:腾云阁 https://www.qcloud.com/community 微校开放平台概述 腾讯微校是专注高校领域的公众号第三方平台,目前已接入公众号超过3万个,精准覆盖大学生超过2000万.通过微校开放平台,开发者可以轻松的直接向上千万大学生提供服务,公众号运营者也可以通过本文档来帮助拓展开发. 微校开放平台主要用于指导开发者如何借助微

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

什么是https? 超文本传输安全协议(Hypertext Transfer Protocol Secure,缩写为HTTPS)是一种网络安全传输协议http是HTTP协议运行在TCP之上,所有传输的内容都是明文,客户端和服务器端都无法验证对方的身份https是HTTP运行在SSL/TLS之上,SSL/TLS运行在TCP之上,所有传输的内容都经过加密,加密采用对称加密,但对称加密的密钥用服务器方的证书进行了非对称加密.HTTPS通过TLS层和证书机制提供了内容加密.身份认证和数据完整性三大功能,

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

版权声明:本文由张戈 原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/942851001487125915 来源:腾云阁 https://www.qcloud.com/community 一.优点分析 内网传输:和阿里云OSS一样,腾讯云COS同样支持内网和外网文件传输,对于腾讯云服务器,使用内网传输绝对是最快.最稳定的备份方案! 免费方案:看了下腾讯云COS的定价说明,发现对于备份网站来说简直是绝佳搭档,甚至可以说是钻

QQ音乐产品经理黄楚雄:产品与用户的情感联系

QQ 音乐产品经理关于产品的一些感悟. 2014 年是 QQ 音乐的第十个产品年度,这十年我们跟用户一起见证了整个互联网音乐的发展.2011 年的 3 月 QQ 音乐公布了第一个 iPhone 平台的版本号,在这三年多的时间里,QQ 音乐一共公布了 44 个版本号,这之中有非常多值得讲的故事和产品的情怀. 简单与复杂 为什么要把程序弄得这么复杂? 这样对用户来说,是否简单了! 这是 QQ 音乐开发的同学与产品经理间非经常见的一段对话.最初 QQ 音乐的版本号的时候,我们经常接到用户这种反馈:睡觉

Python Scrapy的QQ音乐爬虫 音乐下载、爬取歌曲信息、歌词、精彩评论

QQ音乐爬虫(with scrapy)/QQ Music Spider 磁力搜索网站2020/01/07更新 https://www.cnblogs.com/cilisousuo/p/12099547.html UPDATE 2019.12.23 已实现对QQ音乐文件的下载,出于版权考虑,不对此部分代码进行公开.此项目仅作为学习交流使用,支持正版,人人有责 项目介绍 在写一个项目的时候需要用到一些音乐的信息,但是在网上找了许久也没找到满意的音乐语料,于是便用scrapy写了一个QQ音乐的爬虫 由

如何使用腾讯云开发一款 AR 应用介绍

版权声明:本文由张亚舒原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/117 来源:腾云阁 https://www.qcloud.com/community 我们是一个深圳的创业团队,最近做了一款图书的 AR 应用,利用了腾讯云的相关平台和 Native 功能.总体来说做一款手机或 pad 运行的 AR 应用主要有以下几个关键技术: 图像识别(利用摄像头采集的实时图像信息,一桢一桢传给识别模块去处理,识别出来后返回图片标

将 QQ 音乐、网易云音乐和虾米音乐资源「整合」一起的Chrome 扩展Listen 1

原文地址:http://whosmall.com/?post=418 本文标签: Chrome扩展 Chrome浏览器 Chrome扩展Listen1 音乐资源整合 Listen1安装方法 在 Chrome 上安装了这款名为 Listen 1 的插件,妈妈可是再也不用担心你找不到想听的歌了.它将 QQ 音乐.网易云音乐以及虾米音乐的音乐资源「整合」在了一起,你只需要输入音乐关键词,就可以方便地三大曲库中跳转搜索. 安装方法 Listen 1 的安装方法与一般的 Chrome Extension

QQ音乐API

今天分享的是QQ音乐API 搜索歌曲API:http://s.music.qq.com/fcgi-bin/music_search_new_platform?t=0& amp;n={2}&aggr=1&cr=1&loginUin={3}&format=json&inCharset=GB2312&outCharset=utf-8&notice=0&platform=jqminiframe.json&needNewCode=0&a