音视频混流基本原理

前言

在直播场景里,我们经常需要将多个视频画面混合成一个视频画面(或是多路音频合成一路声音),常见的场景如:

  • 本地既要接入摄像头,又要展示桌面或者视频文件,在本地需要做一下画面混合
  • 主播连麦PK时,将多个主播的画面混合成一个,普通观众就不需要拉多路流
  • 在直播过程中通过麦克风采集人声,然后配上背景音乐,比如秀场的唱歌 这些场景里都会涉及到一个相同的部分,就是音视频的混流,其大致的过程如下图。
    音视频混流的基本过程

视频混流的基本原理

视频混流的过程,是指定一块画面区域,在此区域内,按画面的位置布局,将区域中的每个视频画面的像素混合计算成一个像素。这里面主要涉及到的是图层与颜色计算。

图层

图层是视频混流的一个概念,通常分为背景图层和叠加图层,图层可以有效把控画面布局。

背景图层一般是用来限制混流的范围区域,在背景图层分辨率范围之外的视频画面是不允许被混流的,通常我们会使用画布(纯色的画面区域)来充当背景图层。

叠加图层是在背景图层上进行叠加的画面,每一个叠加图层都会在背景图层之上对应一块位置区域。混流区域内的像素颜色值,是根据其位置上所有图层(包括背景图层和叠加图层)对应的颜色值,按规则计算出来。

在对视频进行混流之前,需要先将图层的布局安排好,以避免出现图层遮挡、超出背景范围等问题。

图层的示意

颜色计算

颜色计算是混流的基本步骤,通常是将像素的颜色表示为RGBA值,然后逐像素进行处理,叠加时颜色计算是线性的,公式一般利用Porter-Duff模型,核心公式如下:

  • 颜色值转换: 𝐶_r=𝐶_s 𝐹_𝑠+𝐶_𝑑 𝐹_𝑑

C_s:原色 F_s:原因子 C_d:目标色 F_d:目标因子

  • 透明度转换 𝐴_r=𝐴_s 𝐹_𝑠+𝐴_𝑑 𝐹_𝑑

A_s:原透明度 F_s:原因子 A_d:目标透明度 A_d:目标因子

其中,F_sF_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 申请启动混流。

完整的请求参数

CGI 参数

参数名称

参数含义

输入类型

备注

是否必填

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]之间。

混流常见问题Q&A

  1. Q:混流接口返回 code 为 -505 A:混流接口报-505,代表该流 ID 在云直播后台没有查询到记录。 - 可以使用播放器测试是否可以成功播放。 - 如果可以成功播放,但接口仍报 -505,请检查混流参数中 appid 填写是否正确。
  2. Q:纯音频混流后听不到声音 A:检查纯音频流的 input_type 是否填为4。
  3. Q:连麦场景,输出流 ID 不变,推流主播流 ID 变化,是否要取消混流后再重新混流 A:不需要。混流操作是无状态的,直接用新的主播 ID 发起启动混流请求即可。
  4. Q:申请混流后,如果一直未取消混流,会出现什么情况 A:混流会一直进行,直到收到取消混流命令。
  5. Q:混流过程中,输入流突然断开会出现什么情况 A:非背景流断开,断掉的流画面会停在最后一帧。 背景流断开,则整个画面都会卡住,在15分钟内该流以同一流 ID 重新推流成功,则自动恢复混流。
  6. Q:为什么混流后的视频有黑边 A:混流后有黑边有两种情况:1、原始流就有黑边;2、混流参数中的输出流的宽高比例与原始流的宽高比例不匹配。例如混流期望的比例为16:9,而原始视频比例为4:3,混流后台会在原始视频长宽比基础上补黑边,满足期望的16:9输出。 如果不希望产生黑边,也有两种方案:1、输出的比例与输入画面比例保持一致。2、使用裁剪参数,请参考云端裁剪功能的使用方法。
  7. Q:为什么混流的小主播画面有的时候与期望的位置不同 A:这种情况一般是参与混流的输入源分辨率发生了变化引起的,例如,申请混流时,长宽为1280 * 720,过了一段时间后,流分辨率变为了2560 * 1440,这个时候,混流输出的画面就会发生改变,与期望的输出有区别。不建议在混流过程中,变更输入流的分辨率,如确有需要,需计算位置参数后重新申请混流
  8. Q:混流输出是否支持 H.265 编码 A:混流目前只支持输出 H.264 编码。即使输入流均为 H.265 编码,输出流也以 H.264 编码输出。
  9. Q:取消混流后,再次取消,返回 -30300 错误 A:取消混流接口只需要调用一次,成功后无需重复调用。
  10. Q:混流过程中,输入流断开后何时自动取消混流 A:以两路输入流混流为例,如果其中一路流断开,混流不会自动取消,如果开了录制,录制也将继续进行。如果两路流均断开,15分钟后混流自动取消。
  11. Q:为何调用混流时,画面会出现回退。 A: 混流转码实现机制中,会尽量保证双方画面的一致,因此在处理过程中会有轻微的回退现象。为避免这种情况对业务使用造成影响,如无特殊使用场景,请勿频繁调用混流接口。
  12. Q:混流过程中,如果有主播下播,混流会自动更改混流布局吗? A: 不会,混流调度不会修改客户的布局参数,如果有主播下播的业务场景,需要业务方自行重新计算布局参数,重新发起混流。

DEMO 下载

混流示例 Demo 请单击 此处 下载。

附:使用 ffmpeg 进行混流实验

混流是一个转码过程,若想了解混流的操作流程,可以使用 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实验混流操作结果

附上ffmpeg转码过程示意图:

使用ffmpeg进行转码的流程示意

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

扫码关注云+社区

领取腾讯云代金券