直播-流程

目录

  • 直播过程重要环节
  • 音视频项目大致技术点
  • 采集预览、SurfaceView
  • 美颜如何起作用
  • 视频是什么,大小怎么算
  • 为什么要编码

一、直播过程重要环节

直播过程对非技术人员来说简直太简单了,也就三步走:

  1. 主播点击开播
  2. 调整美颜效果
  3. 观众看

但是,在程序员并不是这么简单,下面给张图:

主播端流程.png

这里把主播端流程分成三块:

  1. 采集预览:主播按下开播按钮,就意味着采集的开始,程序需要初始化相机、绑定SurfaceView最终才能把采集到的画面呈现在界面上。
  2. 美颜处理:这里的美颜是一个笼统的概念,有大眼瘦脸美白磨皮等等,主播端调整美颜参数,SurfaceView检测到美颜参数改变时,自动将当前采集的图像进行相关处理,最后又通过SurfaceView显示到界面上。
  3. 编码、推流:编码是一个耗时过程,不同性能设备编码能力也大不相同,这一块需另开线程处理,编码完成后分别将音视频推到CDN服务器。

二、音视频项目大致技术点

直播大致流程大家都有点印象了,那大家觉得技术点有哪些呢?以下大概列举几个。

  1. 采集预览是这些里面最简单的,网上到处都是,只要弄清楚相关事件回调方法就行,等下我们会稍微带过一下。

  2. 美颜是一个音视频公司的核心技术、核心竞争力,一个没有美颜的直播时没有灵魂的,专门的音视频处理公司会提供这种SDK,但是大部分都是付费的。我们前端只要会调用就行~

  3. 编解码,编码分软硬编,纯CPU编码或者GPU编码。编码前端要做的其实也不多,主要是要搞清楚几个概念,下面会详细讲

  4. 推拉流,到这一步才算是真正把直播连上网,在这之前都是自嗨。拉流我们现在不讲,主流播放器都支持直播拉流,像Ijkplayer,Exoplayer等。以后有机会的话会结合源码分享一下Exoplayer的音视频同步逻辑。推流主要在于推流协议的选择,因为不同的协议延迟率也不一样,对于直播购物而言实时性要求不高,所以根据技术成本去考量选择就行,对于互动性的比如一对一教育等等选择什么协议推流就得好好比较了。RTMP、HLS算是用比较多也比较成熟的协议,这两种都是基于TCP的推流协议。这里简单说下HLS协议,这是苹果公司推出的协议,HLS推拉流时会包含m3u8和ts文件,单个ts无法播放,HLS有个分段策略,ts凑成一个分片才能播放,所以延迟相对较高。

  5. 音视频同步,音频和视频是分开推流的,如果不加以处理,就会出现声音和图像不衔接的问题,拉流端是一定要处理的,推流前最好也是要处理一下。处理音视频同步,其实就是给音视频加个参照时钟,处理DTS、PTS的对应关系。视频的处理较为复杂,因为DTS与PTS顺序不一定一致。编码会生成I P B帧,B帧会导致DTS、PTS顺序不一致。有机会的话这一块生成逻辑音视频同步再讲。

  6. 首帧秒开,这个点有具体的优化方式,详细优化点见 短视频秒开优化

  7. 两端延迟,对直播项目来讲延迟是一个重要的影响因素,单主播开播,主播端与观众端对延迟无感知,但连麦过程,会把这个延迟扩大化。延迟会极大影响连麦的体验。具体见 PK连麦

三、采集预览、SurfaceView

采集和预览,核心类是Camera和SurfaceView,这两个相辅相成,你挑水来我浇园,你采集来我预览。当然谷歌也新出了Camera2 + textureView组合,不过对于我们来说使用这个组合合适一些,Camera2是5.0之后才有的,我们最低支持到19。虽然说Camera到5.0之后就不维护了,但是他的功能还是挺完善的,用老罗的话来说,又不是不能用,下面从这两个类分别说下:

  • Camera注意两个地方:第一个是权限,安卓系统6.0以下,部分机型可手动更改权限设置,导致应用内的检测结果与设备的实际结果不一致,所以开播时,需二次检测相机权限是否可用,一般try catch去检测。第二个是以下这个新方法的使用,相机采集是不停的创建和销毁buffer会引起内存抖动,第二个方法会buffer复用内存地址
setPreviewCallback(new PreviewCallback {
    @Override void onPreviewFrame(byte[] data, Camera camera) {
    }
})

预先分配一个缓冲区,内存复用,必须调用addCallbackBuffer方法
setPreviewCallbackWithBuffer(new PreviewCallback {
    @Override void onPreviewFrame(byte[] data, Camera camera) {
        addCallbackBuffer(data)
    }
})
  • SurfaceView:
    做这部分功能的时候我们顺其自然的就想到用SurfaceView或者它的子类,那为什么用这个呢?因为它强啊~,那具体强在哪呢?
    对于纯粹的View来说,必须在16ms之内绘制完一帧才能保证流畅显示,但是对于相机采集来说明显不符合,相机不停的采集过程中,如果使用View去绘制画面肯定是卡的不行。也就是为了解决这个问题,所以就出来了SurfaceView系列。这里我列举了他三个特点:
    1. 另开子线程渲染。View是在主线程绘制UI,SurfaceView在子线程绘制,操作在主线程,这样相互不影响,不会导致界面的卡顿。
    2. 双缓冲机制。画面一帧一帧的渲染显示,一帧在显示的时候,下一帧已经在渲染了,渲染完成立马显示到画面上,双缓冲循环往复,保证了画面的流畅性。
    3. 独立于宿主窗口,画面改变不会引起应用程序窗口重绘。普通View画面改变会引起整个界面上的View去绘制,对于画面频繁变动的场景,原先的方式太消耗性能了。

四、美颜如何起作用

从刚开始讲直播,其实大家最感兴趣的地方就该就是这个美颜了吧,想知道这个美颜到底是怎么实现的。这个大妈怎么就突然成了可爱的乔碧萝殿下呢?
这里要让大家失望了,作为一个前端,我们的工作主要是调用。具体人脸怎么识别,怎么大眼,怎么瘦脸,怎么美白,怎么祛痘,如何实现我们都不讲,详细的算法过程我们也不需要关心太多。这些都是在Native层进行实现的,充其量我们只需要会点JNI就行
这里只解释两个问题。第一,我们需要做些什么,第二,它具体是如何生效的
不管是大眼瘦脸或者哪种美颜,其实都是对图片进行处理,各种美颜效果暴露给前端的其实是一个个不同的参数值,比如大眼,假设设置大眼参数为10。调用Native层方法,传入当前帧和大眼参数,Native层会处理当前帧并显示到界面上。关键方法就是下面这个方法

void onDrawFrame(GL10 gl)

SurfaceView在初始化时,会设置Render监听,调用方法如下:
surfaceView.setRender(interface Render)

而onDrawFrame方法便是Render接口中定义的一个方法,每一帧的渲染都会执行这个方法。
从而在这个方法中加入美颜处理时机是最准确的

五、视频是什么,大小怎么算

什么是视频,众所周知。一张张图片连续播放就成了视频,这意味着视频的实质其实就是图片。那视频的大小也就是图片的大小也就是一张张图片大小的总和了。看两个问题:
问题一:假设一段视频10秒,每秒10张图片,每一张图片大小是500kb,问,视频大小是多少M?
答:10 * 10 * 500 = 50000kb 大约为50M
问题二:假设相机尺寸设置为1000 x 1000,那一张图片是多少M?
答:1000 * 1000 * 1.5bit = 1.5M

说明:根据上述两个问题基本知道了视频和图片的大小计算了,准确的来说是知道了影响视频和图片大小的因素了。关于上述两道题,需说明几点:

  1. 问题一中每张图片大小是500kb,这只是个假设,便于计算。实际中,因为概念的存在,视频的每一张图片大小必然不会一样,具体概念见 音视频-帧、DTS、PTS
  2. 问题二中相机尺寸为1000 x 1000,实际中相机的尺寸也不是随意的,在设置相机尺寸时,相机有个支持的尺寸列表
  3. 问题二图片的计算为什么要乘以1.5bit。由于相机拍摄出的图片格式是NV21/NV12,也就是YUV420sp格式,这种格式占1.5个字节

六、为什么要编码

  1. 相机采集到的是视频像素数据,传输过程需转成视频码流,以保证与各端的兼容性
  2. 采集的数据太大了,需要进行相应压缩

以下,我们就说说压缩:
那具体从哪些方面压缩呢,回到之前那个例子,关于一段视频大小的计算,从问题一可以看出,影响视频大小的主要因素有:时长、每秒内帧数、每帧图片大小
这里引出两个概念:采样率和帧率
采样率:每秒传送的bit数 单位是bps
帧率:每秒刷新的图片帧数 单位是fps

所以这里,缩短视频时长是不可能的。那只能在允许的范围减少帧率,压缩每帧图片
关于图片压缩。图片压缩的最终目的就是为了减少一张图片上的像素点数。图片压缩有两个方向,第一是质量压缩,像上期我们分享了鲁班压缩,尺寸不变,把图片上的像素变稀。第二是尺寸压缩,像素密度不变,把图片变小以降低像素总数。
编码过程也提供了质量压缩和尺寸压缩,我们只要设置相应值,如下:

// 设置采样率
format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);

// 设置帧率大小
format.setInteger(MediaFormat.KEY_FRAME_RATE, nFrameFps);

// 尺寸压缩用得上
format.setInteger(MediaFormat.KEY_WIDTH, width);
format.setInteger(MediaFormat.KEY_HEIGHT, height);

关于视频编码,这里的编码和鲁班压缩还是不同的。视频编码,是对多张图片的压缩,这里的多张图片还是有关联的,连续的,而鲁班压缩仅仅是对自身图片进行一个压缩。这里引出三个概念I帧、P帧、B帧

I帧:又叫关键帧,是三者中最大的,是一个完整帧
P帧:又叫帧间预测帧,根据前一个I帧或者P帧才能算出一个完整帧
B帧:又叫双向预测编码帧,需要根据前后的I帧、P帧才能算出一个完整帧

这里除了I帧是完整帧,其他的两个都是帧间的差异值。
视频压缩是把每个帧处理成对应的I、P、B帧,经过这层处理把视频大幅度压缩

你可能感兴趣的:(直播-流程)