视频直播开发的技能树

现在直播什么的很火,还有拍短视频之类。很多人在开发这方面的软件。我在一个这方面开发的讨论群里看了一段时间以后,有这种感觉。就是很多人是对多媒体方面的知识不太够,但是公司又要做,只能硬着头皮上。找资料的时候就会遇到这样一个问题,一些基础知识不会,看文章就看得有点迷糊或者看不懂。找别人写好的函数库来调用的话,只能实现一些简单的需求。所以我在想,我以前在字幕组搞过一段时间视频处理,这方面的知识能不能组织一下列出来,并不是全都要会才能做,里面很多我也不熟,只是在遇到问题时候好有个方向,知道要去查什么。

括号里的英文是用来方便搜索的。因为中文互联网里的资料搜起来很多实在不堪入目,搜英语资料虽然看起来比较累,不过根据经验确实是能少踩点坑

有想到的不完整的地方可能还会来补充。

多媒体数据的表示方法
	视频的基本参数
		分辨率(resolution)
			常说的480p、720p、1080p指的是扫描线数量,也就是宽和高里的高。
		逐行扫描(progressive)/基于帧(frame-based)/隔行扫描(interlaced)/基于场(field-based)
			电脑上的视频大多是逐行扫描,但DVD等的视频是隔行扫描的。很多摄像机录出来的视频也是隔行扫描的。
		帧速率(frame rate)
			手机拍摄的视频帧率常常不稳定,是可变的。DVD里常见视频是30FPS隔行扫描但实际是24FPS逐行扫描的电影格式。
		像素比例/画面比例(sample aspect,display aspect等)
			电脑显示器和手机屏幕的像素高宽比例是1:1,但有些视频例如DVD不是这样。
		采样格式/像素格式/色彩空间,RGB,YUV,NV12等,BT.709和BT.601范围,不同像素格式之间转换
			虽然光的三原色是RGB,但因为一些历史原因(从黑白电视机发展而来)视频里常用的是YUV。
			可以参考Avisynth的帮助文档,在Advanced Topics的Colorspace Conversions里面有相关公式和参数。
			可以参考维基百科,里面有数据的具体存储顺序。(基于Planes,或者交错保存)
			BT.709和BT.601因为数值范围不同,弄错了会造成画面颜色偏灰、太浅或者太深等
	音频的基本参数
		采样格式
			有整数有浮点数,其中整数有大端优先(big-endian)和小端优先(little-endian)两种。
			MP3有使用16位整数、24位整数的,AAC使用32位浮点数。保存方式也有按声道分开保存和两个声道交叉保存的。
		采样率
			常见的有22050、32000、44100(CD)、48000(DVD)等,每秒有多少个采样的意思。
		声道数
			单声道(mono),立体声(stereo)。单声道并不一定是只有一边耳朵会响的意思。
多媒体数据的处理
	视频的处理
		OpenGL ES
			(安卓上可以通过OpenGL把要送去编码的视频画面渲染到一个Surface上,
			苹果可以渲染好画面再把数据读回内存送去编码。
			因为手机性能有限,使用显卡加速就变得特别重要)
			OpenGL渲染线程
				使用单独一个线程来跑OpenGL操作是一个常用手段。
			安卓的TextureView控件
			苹果手机的GLKView控件
			Windows上的Angle函数库
				可以用于在Windows上调用OpenGL ES函数,Angle库转成Direct3D执行具体操作。
				因为可以单步步入到函数里,所以用于调试OpenGL ES代码比手机真机更好用。
			Vertex Shader的编写
				视频上叠加一张图片,视频上叠加一串文字等,需要用它计算定位
			Fragment Shader的编写
				摄像头美颜效果,画面的亮度调整等,需要用它计算颜色。
				用于YUV三个通道合成成RGB,播放器要用到
			内存数据加载到OpenGL纹理(Texture)
			OpenGL纹理读回到内存
				DirectTexture:安卓上执行这种操作的性能优化手段
				TextureCache:苹果手机上执行这种操作的性能优化手段
			OES纹理
				安卓上OpenGL ES的扩展里有一种带有OES标识的纹理。
				对于屏幕映射到Surface要输送到Texture里的情况,要使用这种Texture才行
			画布上贴图
				最基本操作,用于显示一个Texture,
				或者一个Texture到另一个Texture的复制,
				一个Texture上叠加另外一些什么东西,等等很多
				贴的时候因为经过了两个Shader的处理,所以可以实现很多效果
			共享的OpenGL上下文组(shared opengl context)
				画面处理的同时如果还要在屏幕上预览效果,
				那么会需要共享的上下文使得一个纹理在不同上下文中使用
			Surface和Texutre的关系,和Framebuffer的关系
				Framebuffer更靠近OpenGL,Surface更靠近操作系统实现。
				安卓上有SurfaceTexture提供桥梁
			安卓的Grafika示例代码
				这个工程里有大段代码可以抄……里面的EGLCore类也是很好用。发扬拿来主义(
			苹果手机的GPUImage库
				很多拿来就用的省事的东西。避免自己造轮子的坑。
	音频的处理
		混音(audio mixing)
			两路不同的音频采样流合并成一个。
			算平均值不一定是个好的办法,有个 A+B-AB 的我用着感觉还不错。
			(根据A和B的符号不同有不同处理,具体带上audio mixing关键字用搜索引擎可以搜出来)
		改变音量
			根据音量大小乘以系数会发现听感上不对。因为这东西不是线性的,要根据“分贝”的概念去调整大小。
多媒体数据的编码
	常用视频编码:H.264
		帧类型
			关键帧、I帧,P帧,B帧
			帧内分片编码(麒麟芯片的手机硬件编码)
		NALU(网络抽象层单元)
			NALU的分割,start code,裸流表示方式,MP4封装时的表示方式
		帧保存顺序/解码顺序
			B帧因为双向参考,依赖前面的帧和后面的帧,需要两边都解码完了才能解码出来
			所以有B帧的情况下帧的保存顺序、解码顺序和播放顺序不同
		编码参数
			码率,码率控制方式(可变码率,固定码率。或者abr vbr crf等),量化值
			不同帧类型间的量化值比例
			关键帧间隔,GOP(Open GOP和Close GOP,I帧和IDR帧),从特定时间开始播放(Seek,只能从I帧开始解码)
			运动预测范围(merange)
			参考帧数量(reference frame count)
			CABAC,CAVLC
			编码复杂度(Profile:baseline,main,high。Level)
		SEI
			一些额外附加数据,可以放自己用来调试或者确认故障用的信息等
		解码参数(SPS,PPS)
			也是NALU。没有这些参数将无法解码。
		常见编解码器
			软件(用CPU编码) OpenH264,x264,Libavcodec(FFMPEG)
			PC硬件(显卡) Nvidia encoder,Intel QuickSync,DXVA,DXVA2
			安卓(专用芯片) MediaCodec,OpenMax,stagefright
			苹果手机 VideoToolbox
	常用音频编码:AAC(音频方面我特别不熟,所以没什么东西可以写)
		码率(比特率)
		音频的帧(一帧包含若干多个采样,比如1024个)
		两种音频编码格式:LC-AAC,HE-AAC
多媒体数据的封装
	帧(frame)、包(packet)的概念,多个流封装在一起(mux)的概念
	直播常用封装格式(FLV,HLS)
		FLV的OnMetaData信息
		HLS的请求方式、播放流程
	视频封装
		SPS和PPS
			如果直播推流过程中断开重连了这些编码器最开始输出的参数信息要重发。
			听说有数据流每个I帧之前都自带SPS和PPS。这种应该是不需要再专门发吧?
		时间戳(播放时间戳pts,解码时间戳dts)
			安卓硬件编码的解码时间戳
				使用安卓硬件编码时,编码器给出的数据只带有播放时间戳pts不带解码时间戳dts。
				有些播放器,例如Html5用的FLV.js,
				在视频流的pts们和dts们不同属于一个离散数值的集合的情况下
				(简单说就是dts出现过的数值,也会出现在pts里),会出现音画不同步。
				dts是单调递增的,但因为手机编码的帧率不稳定性
				导致dts不容易“在保证一定出现在pts数值的集合”的要求下计算出来
				需要耍一些手段把它坑出来……
		低延迟和秒开优化
			视频只能从I帧开始解码,
			I帧间隔越长又会导致编码效率越低(同等数据量下画面更糊)
			所以对于FLV格式的直播视频流,
			有一种手段就是把最开头一堆视频帧的时间戳都调成0,或者帧间隔时间非常非常小。
			让解码器快速解码完这一段,跟上直播的进度。
		画面方向
			MP4文件中保存了一个变换矩阵,用于表示画面的方向。
			比如一个1280x720的视频,可能播放的时候是720x1280的。
			用视频的四个角的坐标乘以变换矩阵,就能知道它是表示什么方向了。
		播放时间
			苹果手机有一种把第一张画面的时间戳调成负的若干秒的手段实现媒体文件切割。
			这种方式FFMPEG不认,转码后那一段又出来了。
		按照关键帧剪辑
			去除不要的GOP、调整所需部分的时间戳,就能做到不重新转码而剪辑视频。
		安卓的MediaMuxer
			虽然MediaExtractor能支持B帧,但是这个MediaMuxer是不支持B帧的。
			如果想要支持B帧,工程里要用一些其他什么第三方库替代掉它。
	RTMP推流
视频音频捕捉
	Windows平台(可以参考开源项目OBS-Studio)
		桌面捕捉
			桌面DC对内存DC复制
			Desktop Duplicator
		窗口捕捉
			窗口DC对内存DC复制
		游戏捕捉
			注入DLL,挂钩Direct3D或者OpenGL的API。跨进程共享一块内存或者共享一个纹理
			多显卡笔记本如果共享纹理会遇到捕捉进程和游戏进程在不同显卡的资源上运行而抓不到画的问题
		摄像头捕捉
			用VFW或者DirectShow可以获取摄像头数据
		声音捕捉
			用WASAPI捕捉系统声音,API调用起来比较麻烦的样子,可以参考OBS-Studio这个软件怎么调用的。
			麦克风声音就有超多手段可以做到,这里不赘述……
	安卓
		屏幕捕捉
			用Virtual Display来映射屏幕到一个Surface上,
			可以通过Surface定期到Texture再渲染到编码器的Surface的方式来稳定帧率。
			这里的Texture要使用OpenGL ES的扩展里,带有OES标识的那种,否则会不出东西。
		摄像头捕捉和声音捕捉我不会
		目前为止还没有公开的API能不影响用户使用的情况下获取系统声音的样子。
	苹果手机
		屏幕捕捉
			使用ReplayKit接口能获取画面,但是需要App主动支持
		系统声音捕捉
			ReplayKit接口送进来的声音数据是大端优先(Big-endian)的,不同于麦克风的小端优先(little-endian)。
			如果不加处理会听到很大的噪音
		摄像头捕捉和麦克风捕捉我不会
可能有帮助的程序和库
	ijkplayer
		哔哩哔哩弹幕视频网的开源播放器。基于FFMPEG里的FFPlay开发,
		对硬件解码支持更好,在安卓和苹果手机上使用,
		很有名,有很多大公司的项目也在用。
		在Windows下补上几个Windows下没有的函数把ijkmedia那个部分编译过的话,
		自己实现一下声音播放(比如接入PortAudio库)(SDL_Aout),视频交给Angle(SDL_Vout),也能使用。
		没实现的情况下直接跑一下代码打开一个视频文件,看它崩哪里,能大概猜到要实现一个什么。
	MPV
		基于MPlayer开发的播放器。
		在Windows上通过调用它的EXE程序的方法,可以当作自己的播放器核心用,
		具体可以参考SMPlayer看看它是怎么送参数调用MPV的。
		是GPL协议。
	FFMPEG
		从视频文件的拆分装到解码到滤镜处理一应俱全的多媒体库,
		硬件软件编解码都有,在Windows、安卓、苹果手机上都能用。
		根据编译参数不同可以是LGPL协议或者GPL协议。
	GPUImage
		苹果手机上用显卡进行视频处理的库。除了处理也包含了保存成视频文件或者录摄像头什么的功能。
		有一些坑容易掉进去,不过因为使用的人多,网上资料好找,具体要怎么修这些坑的资料也好找。
		有人用Java写了安卓的克隆版。不过功能没苹果的全。
	LFLiveKit
		来疯直播的推流库。在我上次给项目里改它代码的时候它还有些地方做得不好比如打时间戳等。
		不过好在源代码都有,问题可以自己修修。
	Grafika
		说是谷歌官方的示例代码。拿来编译运行就能跑,里面大段代码可以参考。
		不过代码功能比较有限,基本还是要自己根据其他地方的知识结合起来才好使。
	Kickflip
		这个库其实我没用过不过部门里的开发有在用。听说现成的功能挺多的,然而同时坑也挺多的。
		我没用过我就不说什么感受了。
	OpenCV
		CV是计算机视觉的缩写。识别个人脸在哪里、什么方向什么角度,是这个的范畴。
		放这里凑个数……因为其实我也没用过它(捂脸
	OBS-Studio
		这是一个跨平台的从捕捉到直播场景编辑到推流甚至还有简单导播功能一应俱全的软件。
		有不知道怎么实现的地方可以看看他是怎么实现的,代码里做了什么,

发表评论

电子邮件地址不会被公开。 必填项已用*标注

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>