首先是捕获,这里采用了DirectShow的方式,对它进行了一定程度的封装,包括音视频。好处是直接使用native api,你可以做想做的任何修改,坏处是,不能跨平台,采集音视频这种应用,linux平台也是需要滴呀。有跨平台的做法,对视频,可以使用OpenCV,对音频,可以使用OpenAL或PortAudio等,这样就行了。
编码可以选择的余地比较大,对视频来讲,有H264, MPEG-4, WebM/VP8, Theora等,音频有Speex, AAC, Ogg/Vorbis等,它们都有相应的开源项目方案,我采用的是x264进行H264编码,libfaac进行aac编码,之后是否更改编码方案,等具体项目需求再说了。这里提一下WebM,Google牵头的项目,完全开放和自由,使用VP8和Vorbis编码,webm(mkv)封装,有多家巨头支持,目的是想要取代当前的H264视频编码,号称比后者更加优秀,我没有测试过实际效果。不过有商业公司牵头就是不一样,各项支持都很全面,有时间了关注一下。
2. 逻辑和流程
基本的思想是实现dshow ISampleGrabberCB接口,通过回调来保存每一个buffer。除了界面线程和dshow自己的线程之外,我们启动了两个线程,AudioEncoderThread和VideoEncoderThread,分别从SampleGrabber中取出数据,调用编码器进行编码,编码后的文件可以直接输出。看图:
程序是用VS2010构建的,看张工程截图:
Base下面的是对系统API的一些简单封装,主要是线程和锁。我这里简单也封装的了一下dshow的捕获过程,包括graph builder的创建,filter的连接等。directshow是出了名的难用,没办法,难用也得用。因为是VS2010,调用的Windows SDK 7.1中的dshow,没有qedit.h这个文件,而它正式定义ISampleGrabberCB的。不急,系统中还是有qedit.dll的,我们要做的就是从Windows SDK 6.0中,把它拷过来,然后在stdafx.h中加入这几行代码,就可以了
1 #pragma include_alias( "dxtrans.h", "qedit.h" )
2 #define __IDxtCompositor_INTERFACE_DEFINED__
3 #define __IDxtAlphaSetter_INTERFACE_DEFINED__
4 #define __IDxtJpeg_INTERFACE_DEFINED__
5 #define __IDxtKey_INTERFACE_DEFINED__
6 #include "qedit.h"
3. 音视频编码
相关文件:
Encoder下就是音视频编码相关的代码。X264Encoder封装了调用x264编码器的 *** 作,FAACEncoder封装了调用libfaac编码器的 *** 作,VideoEncoderThread和AudioEncoderThread负责主要的流程。下面我把关键代码贴出来,大家可以参考一下。
A. 视频编码线程
主要流程是首先初始化x264编码器,然后开始循环调用DSVideoGraph,从SampleGrabber中取出视频帧,调用x264进行编码,流程比较简单,调用的频率就是你想要获取的视频帧率。要注意的一点是,x264进行编码比较耗时,在计算线程Sleep时间时,要把这个过程消耗的时间算上,以免采集的视频帧率错误。
B. 音频编码线程
主要流程和视频编码线程相同,也是初始化FAAC编码器,然后循环调用DSAudioGraph,从SampleGrabber中取出视频帧,调用faac进行编码。和视频不同的是,音频的sample的频率是非常快的,所以几乎要不断的进行采集,但前提是SampleGrabber中捕获到新数据了才行,不然你的程序cpu就100%了,下面代码中IsBufferAvailaber()就是做这个检测的。
调用faac进行编码的时候,有点需要注意,大家特别注意下,不然编码出来的音频会很不正常,搞不好的话会很头疼的。先看下faac.h的相关接口
1 faacEncHandle FAACAPI faacEncOpen(unsigned long sampleRate, unsigned int numChannels,2 unsigned long *inputSamples, unsigned long *maxOutputBytes)3 4 int FAACAPI faacEncEncode(faacEncHandle hEncoder, int32_t * inputBuffer, unsigned int samplesInput,5 unsigned char *outputBuffer, unsigned int bufferSize)
faacEncEncode第三个参数指的是传入的sample的个数,这个值要和调用faacEncOpen返回的inputSamples相等。要做到这点,就要在dshow中设置好buffsize,公式是:
BufferSize = aac_frame_len * channels * wBytesPerSample// aac_frame_len = 1024
本篇介绍下H264和H264的编码格式,包括avcc,annexb,以及转换方法。annexb 用于实时流的场景,avcc用于多媒体文件,如mp4,mkv等场景。
h264是一种编码格式,NAL(Network Abstraction Layer)和VCL(Video Coding Layer)是其中2个主要内容。NAL是网络抽象层,用于对数据进行打包和发送。VCL是视频编码层,负责高效地视频内容显示。
H264编码过程中的3种数据:
SODB(string of data byte),数据比特流,最原始的编码数据,也就是VCL数据,没有附加其他数据。
RBSP(Raw byte sequence payload),原始字节序列载荷,在SODB的后面加入了结尾比特。
EBSP(Encapsulation Byte Sequence Packets)扩展字节序列载荷,在RBSP的基础上添加了仿校验字节(0x03),这是因为视频数据以annexb格式出现时,会有起始码(Start Code)0x00000001, 如果数解码器发现Start Code 就会认为是当前NAL结束,新的NAL开始。如果NAL数据中也有0x00000001数据,那么就会出现误判,因此需要添加仿校验字节,如果编码器发现连续2个字节为0,那么就会添加0x03,在解码的时候再把0x03去掉。
VCL层是对块,宏块以及片语法级别的定义,最终输出压缩编码后的数据SODB。 VAL在存储和传输前会对SODB打包成RBSP,并添加NAL头,组成一个NALU单元。
每个NALU包含一字节的头信息,再加可变长度的RBSP。每个NALU可以携带一个编码片,A/B/C型数据分割,或者是一个序列参数集,或者是一个图像参数集。
nalu头结构大小是1字节,具体可以参考 rfc3984
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|F|NRI| Type |
+---------------+
F:
forbidden_bit:初始为0,当网络发现NAL有网络错误时将该比特设置为1
NRI:
nal_reference_bit:nal重要性指示,值越大,重要性越高,当解码器处理不过来的时候,可以丢弃值为0的nalu。
Type:
nal_unit_type:标识NAL单元中的RBSP数据类型,其中值为1,2,3,4,5的nal单元成为VCL的nal单元,其余为非VCL的nal单元。
序列参数集SPS:
两个IDR帧之间的图形序列所有信息,包括标识符(seq_parameter_set_id),帧数,POC的数目,参考帧的数目,解码图像尺寸,帧场编码模式选择标识等
图像参数集PPS:
PPS对应的是一个序列中的某一幅或某几幅图像,包括一个图像所有的Slice的相关信息,如图像类型,序列号,标识符,可选的序列标识符,熵编码模式选择标识,片组数目,初始化编码参数,去方块滤波系数调整标识等
数据分割:
组成片的编码数据存放在3个独立的数据分割中,分割A包含片头和片中每个宏块头数据,分割B包含帧内和SI片的宏块编码数据,分割C包含帧间的编码残差数据
H264的AnnexB格式:
使用startcode来分割nal,startcode一般是3字节(0x000001)或4字节(0x00000001),sps,pps也在nal数据头部。
找一个mp4 文件,执行下面命令
这时候就可以看到对应的annexb数据:
H264的AVCC格式
在头部保存长度信息,并将长度信息所用的字节长度和sps,pps等数据放到extradata中。
可以使用如下命令查看mp4中的avcc数据包:
H265的nalu结构如下
+---------------+---------------+
|0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|F| Type| LayerId | TID |
+-------------+-----------------+
这时候nalu 头是2字节
F:
forbidden_zero_bit:要求是0,当出现错误的时候设置为1
Type:
nal_unit_type:nalu类型,具体数值如下, 和H264是不一样的:
LayerId:
nuh_layer_id:现在需要设置为0,是为后续的3D图像准备的。
TID:
nuh_temporal_id_plus1:NALU 的标识符,类似于H264的NRI。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)