在直播场景里,我们经常需要将多个视频画面混合成一个视频画面(或是多路音频合成一路声音),常见的场景如:
视频混流的过程,是指定一块画面区域,在此区域内,按画面的位置布局,将区域中的每个视频画面的像素混合计算成一个像素。这里面主要涉及到的是图层与颜色计算。
图层是视频混流的一个概念,通常分为背景图层和叠加图层,图层可以有效把控画面布局。
背景图层一般是用来限制混流的范围区域,在背景图层分辨率范围之外的视频画面是不允许被混流的,通常我们会使用画布(纯色的画面区域)来充当背景图层。
叠加图层是在背景图层上进行叠加的画面,每一个叠加图层都会在背景图层之上对应一块位置区域。混流区域内的像素颜色值,是根据其位置上所有图层(包括背景图层和叠加图层)对应的颜色值,按规则计算出来。
在对视频进行混流之前,需要先将图层的布局安排好,以避免出现图层遮挡、超出背景范围等问题。
颜色计算是混流的基本步骤,通常是将像素的颜色表示为RGBA值,然后逐像素进行处理,叠加时颜色计算是线性的,公式一般利用Porter-Duff模型,核心公式如下:
C_s:原色 F_s:原因子 C_d:目标色 F_d:目标因子
A_s:原透明度 F_s:原因子 A_d:目标透明度 A_d:目标因子
其中,F_s和F_d的值根据不同的混合方式来确定,有兴趣的可以查看一下这篇文章了解颜色混合的相关说明。
音频混流的基本原理是将多个音频源的波形按一定的算法进行叠加计算,混合成一路音频波形。需要注意的是音频叠加的算法是非线性的,不能简单地依靠波形数据的值进行加减。
通常在混音时,需要先对输入音频统一采样率、位宽、声道等参数,然后再对PCM波进行混合,混合采用的方法一般有以下几种:
混合方式 | 说明 |
---|---|
线性叠加后求平均 | 不会产生溢出,噪音较小,但衰减过大,影响音频质量,音量小的会拉低均值 |
自适应加权求平均 | 根据输入流的特点分配权重,加权后再求平均,优点是多音频时较好,但可能会引入噪音 |
多通道混音 | 软件模拟通道,然后混合多个通道的声音,效果较好,但通道越多,处理复杂度越高 |
这里有一篇论文可以了解一下混音的实现原理。
客户端的音视频混流通常可以使用系统自带的音视频库或第三方音视频库实现,诸如常见的OpenGL、DirectX等都可以实现基本的混合,在常见的推流器如OBS中,画面和声音的混合只需要操作鼠标选择即可,甚至于很多虚拟摄像头也带有视频混合功能。客户端混流的挑战在于对客户端的性能要求较高,尤其是存在多个输入源时,对于性能要求较高。
服务端混流出现是为了减少客户端的性能压力,以及更方便的混流参数配置。
腾讯云云直播服务提供了云端混流功能,支持最多16路音频、视频、图片、画布的数据流混合,开发者可以方便的使用云端混流接口实现连麦PK、多画面混合等功能。
说明文档:https://cloud.tencent.com/document/product/267/8832
接口调用方式为
http://fcgi.video.qcloud.com/common_access?appid=1200000000&interface=Mix_StreamV2&t=t&sign=sign
相关的鉴权参数说明见上面的文档,下面重点看一下传入的混流参数部分。
启动混流示例(勿直接复制使用,注释仅为了方便理解而加):
{
//UNIX 时间戳,用于标记请求时间
"timestamp": int(time.time()),
//网络请求的标识,通常取随机数即可
"eventId": int(time.time()),
//以下为启动混流的参数
"interface": {
//接口方法名,固定值,指定为 Mix_StreamV2
"interfaceName": "Mix_StreamV2",
"para": {
//填写云直播控制台上 12xxxx 开头的 APPID
"app_id": appid,
//启动混流方法,固定为mix_streamv2.start_mix_stream_advanced
"interface": "mix_streamv2.start_mix_stream_advanced",
//混流会话 ID,用于标识一次混流操作(取消混流时需要使用启动混流对应的会话ID)
"mix_stream_session_id": "lewis_room",
//指定混流后输出到哪一个流 ID,在 URL 允许字符范围内可以自由指定,同 APPID 下建议保证唯一(可以是正在推流的流 ID,也可以是没有推流的流 ID)
"output_stream_id": "stream_lewis01",
//输入流列表
"input_stream_list": [
//背景画面
{
//背景图层输入流 ID
"input_stream_id": "stream_lewis01",
//布局参数
"layout_params": {
//图层编号,背景图层填1
"image_layer": 1
}
},
//叠加图层
{
//叠加图层输入流 ID
"input_stream_id": "stream_lewis02",
//布局参数
"layout_params": {
//叠加图层编号
"image_layer": 2,
//画面宽度
"image_width": 160,
//画面高度
"image_height": 240,
//X偏移:相对于背景图层左上角的横向偏移
"location_x": 380,
//Y偏移:相对于背景画面左上角的纵向偏移
"location_y": 630
}
}]
}
}
}
取消混流示例(勿直接复制使用,注释仅为了方便理解而加):
使用 mix_streamv2.cancel_mix_stream
接口取消混流。
{
//UNIX 时间戳,用于标记请求时间
"timestamp": int(time.time()),
//网络请求的标识,通常取随机数即可
"eventId": int(time.time()),
//以下为取消混流参数
"interface": {
//接口方法名,固定值,指定为 Mix_StreamV2
"interfaceName": "Mix_StreamV2",
"para": {
//填写云直播控制台上 12xxxx 开头的 APPID
"app_id": appid,
//取消混流方法,固定为mix_streamv2.cancel_mix_stream
"interface": "mix_streamv2.cancel_mix_stream",
//混流会话 ID,用于标识一次混流操作(取消混流时需要使用启动混流对应的会话ID)
"mix_stream_session_id": "lewis_room",
//指定需要取消混流的输出流 ID
"output_stream_id": "stream_lewis01"
}
}
}
注意:取消混流30秒后才可使用相同的 session id 申请启动混流。
参数名称 | 参数含义 | 输入类型 | 备注 | 是否必填 |
---|---|---|---|---|
timestamp | 当前时间 | int64 | 取当前时间(秒)即可。 | Y |
eventId | 标识一次网络请求 | int32 | 取随机值即可。 | Y |
interfaceName | 接口标识 | string | Mix_StreamV2,固定值,表明使用混流接口 。 | Y |
参数名称 | 参数含义 | 输入类型 | 范围 | 备注 | 是否必填 |
---|---|---|---|---|---|
app_id | 直播 APPID | int32 | 直播 APPID。 | Y | |
interface | 混流接口名称 | string | mix_streamv2.start_mix_stream_advanced mix_streamv2.cancel_mix_stream | 申请混流:mix_streamv2.start_mix_stream_advanced。 取消混流:mix_streamv2.cancel_mix_stream。 | Y |
mix_stream_session_id | 混流会话(申请混流开始到取消混流结束)标识 ID | string | 80字节以内,仅含字母、数字以及下划线的字符串 | 申请混流成功后,业务需要记录该值,在取消混流时,将申请混流时的 mix_stream_session_id 填入。 | Y |
mix_stream_template_id | 输入模板 ID,若设置该参数,将按默认模板布局输出,无需填入自定义位置参数 | int32 | 0,10,20,30,40,50 310,390,391,410,510,590,610 | 不填默认为0。 两输入源支持10,20,30,40,50。 三输入源支持310,390,391。 四输入源支持410。 五输入源支持510,590。 六输入源支持610。 | N |
参数名称 | 参数含义 | 输入类型 | 范围 | 备注 | 是否必填 |
---|---|---|---|---|---|
output_stream_id | 输出流 ID | string | 80字节以内,仅含字母、数字以及下划线的字符串 | 指定输出流 ID。 | Y |
output_stream_type | 输出流类型 | int32 | [0,1] | 不填默认为0。 当输出流为输入流 list 中的一条时,填写0。 当期望生成的混流结果成为一条新流时,该值填为1。 该值为1时,output_stream_id 不能出现在 input_stram_list 中,且直播后台中,不能存在相同 ID 的流。 | N |
output_stream_bitrate | 输出码率 | int32 | [1,50000] | 不填系统会自动判断。 | N |
参数名称 | 参数含义 | 输入类型 | 范围 | 备注 | 是否必填 |
---|---|---|---|---|---|
input_stream_id | 输入源 ID | string | 80字节以内,仅含字母、数字以及下划线的字符串 | 指定输入源 ID。 | Y |
image_layer | 图层标识号 | int32 | [1,16] | 1)背景流(即大主播画面或画布)的 image_layer 填1。 2)纯音频混流,该参数也需填。 | Y |
input_type | 输入源类型 | int32 | [0,5] | 目前支持: 不填默认为0。 0表示输入源为音视频。 2表示输入源为图片。 3表示输入源为画布。 4表示输入源为音频。 5表示输入源为纯视频。 | N |
image_width | 输入画面在输出时的宽度 | double | 像素:[0,3000] 百分比:[0.01,0.99] | 不填默认为输入流的宽度。 使用百分比时,期望输出为(百分比 * 背景宽)。 | N |
image_height | 输入画面在输出时的高度 | double | 像素:[0,3000] 百分比:[0.01,0.99] | 不填默认为输入流的高度。 使用百分比时,期望输出为(百分比 * 背景高)。 | N |
location_x | x 偏移 | double | 像素:[0,3000] 百分比:[0.01,0.99] | 不填默认为0。 相对于大主播背景画面左上角的横向偏移。 使用百分比时,期望输出为(百分比 * 背景宽)。 | N |
location_y | y 偏移 | double | 像素:[0,3000] 百分比:[0.01,0.99] | 不填默认为0。 相对于大主播背景画面左上角的纵向偏移。 使用百分比时,期望输出为(百分比 * 背景高)。 | N |
color | 颜色 | string | - | 使用画布(input_type = 3)时填写,常用的颜色有: 红色:0xcc0033。 黄色:0xcc9900。 绿色:0xcccc33。 蓝色:0x99CCFF。 黑色:0x000000。 白色:0xFFFFFF。 灰色:0x999999。 | N |
picture_id | 水印 ID | int32 | - | 使用图片(input_type = 2)时填写,填入将图片上传为水印后生成的 ID。 | N |
调用混流接口后,返回的响应如下:
{
"code":0,
"message":"Success!",
"event_id": "1527240138",
"timestamp":1490079362
}
参数名 | 参数含义 | 类型 | 备注 |
---|---|---|---|
code | 返回错误码 | int32 | 0表示成功,其他表示失败。 |
message | 错误信息 | string | 返回错误信息。 |
timestamp | 时间戳 | int64 | 返回时间。 |
event_id | 请求 ID | int32 | 网络请求标识。 |
错误码 | 原因 | 排查建议 |
---|---|---|
-1 | 解析输入参数错误 | 检查请求体 body json 格式是否正确。 检查 interface name 是否为 mix_streamv2.start_mix_stream_advanced。 检查 input_stream_list 是否为空。 |
-2 | 输入参数错误 | 检查 appid 和 eventid 是否为0。 检查画面参数是否溢出。 |
-3 | 流数目错误 | 检查输入流数目是否在[1, 16]范围内。 |
-4 | 流参数错误 | 检查输入输出长宽在(0,3000)范围内。 检查输入流数目是否在 [1,16]范围内。 检查输入流是否携带 layout_params 。 检查 input_type 是否支持(合法数值:0,2,3,4,5)。 检查流 ID 长度是否满足(1,80)。 |
-11 | 图层错误 | 检查图层个数与输入流个数是否一致。 检查图层 ID 是否重复。 检查图层 ID 是否在[1,16]之间。 |
-20 | 输入参数与接口不匹配 | 检查输入流条数是否匹配模板 ID。 检查颜色参数是否正确。 |
-21 | 混流输入流条数错误 | 检查输入流的条数是否至少为两条。 |
-28 | 获取背景长宽失败 | 如果设置画布,检查画布的长宽是否设置。 检查背景流是否存在(推流后需等待5s再混流)。 |
-29 | 裁剪参数错误 | 检查裁剪位置是否超出流的长宽。 |
-33 | 水印图片 ID 错误 | 检查输入图片 ID 是否设置。 |
-34 | 获取水印图片 URL 失败 | 检查图片是否上传成功,是否已经生成 URL。 |
-111 | output_stream_id 参数与 output_stream_type 不匹配 | output_stream_type 为0,output_stram_id 必须出现在 input_stream_list 中。 output_stream_type 为1,output_stram_id 必须不在 input_stream_list 中。 |
-300 | 输出流 ID 已经被使用 | 检查当前输出流是否已经是另一个混流的输出流。 |
-505 | 输入流无法在 upload 查到 | 是否推流成功5s后发起混流。 检查能否播放。 检查混流参数中 appid 是否填写正确。 |
-507 | 流长宽参数查询失败 | 检查画布宽、高是否设置。 检查推流是否已经成功,建议推流后5s再开始混流。 |
-508 | 输出流 ID 错误 | 检查是否存在同样 sessionid 使用不同输出流 ID 的情况。 |
-10031 | 触发混流失败 | 建议推流后等待5s再混流。 |
-30300 -31001 -31002 | 取消混流时 sessionid 不存在 | 检查 sessionid 是否存在。 |
-31003 | 输出流 ID 与 session 中输出流 ID 不匹配 | 检查取消混流时填入的输出流 ID。 |
-31004 | 输出流码率不合法 | 检查输出流码率是否在[1,50000]之间。 |
混流示例 Demo 请单击 此处 下载。
混流是一个转码过程,若想了解混流的操作流程,可以使用 ffmpeg 进行实验,利用复合过滤器 filter_complex 来实现两个视频文件的混合,参考如下命令:
ffmpeg -i input_file1.mp4 -i input_file2.mp4 -filter_complex "[1:v]scale=240:320[input_file1];[0:v][input_file1]overlay=0:0" output_file.mp4
参数说明:
-i:输入文件
-filter_complex:复合过滤器,用于处理多个输入输出。
[1:v]和[0:v]表示第2个视频和第1个视频;
[input_file1]表示引用input_file1.mp4文件;
scale表示缩放到w:h的分辨率;
overlay表示布局位置。
本例中使用的文件 input_file1.mp4 和 input_file2.mp4 及 output_file.mp4 可到这里下载查看效果。
附上ffmpeg转码过程示意图:
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。