http://www.2cto.com/kf/201608/535440.html
导语 本文主要是围绕android直播助手的功能做了一些研究,因为之前对Android多媒体相关的内容知之甚少,只有概念,于是查阅了相关资料并做以总结。
由于我对音视频相关知识零基础所以补充了一些相关知识
采集音频原始数据---->压缩编码---->封装
采集视频原始数据---->压缩编码---->封装
音视频编码
压缩编码就是对数据进行压缩以节省空间便于存储和传输。
视频压缩编码就是将视频帧的像素数据RGB或YUV等压缩成视频码流,编码一般对YUV格式进行,视频编码方案H.264,MPEG2,MPEG4等。
音频压缩编码就是将采样的音频采样数据PCM等压缩成音频码流。音频编码方案:AAC,WMA,MP3等。
编码是音视频技术中最重要的技术之一,也是难点,所幸的是,Android提供了MediaCodec用来方便开发者进行视音频的编解码,并且对于某种编码器还可以指定帧格式,尽管如此,我们也不能指定任意格式,因为这需要硬件的支持,可以通过API查询支持的帧格式。
通过createEncoderByType方法只需要传入mime即可创建对应的解码器,mime可以时下面的形式。
MediaCodec类的使用逻辑大致如下图所示,简单来说,一个编码器用于处理输入数据并对其进行编码处理后输出,它内部有一系列的输入和输出缓冲区,使用时,从编码器申请空的输入缓冲区,然后填充数据后发送给编码器处理,编码完成后编码器会将编码后的数据放入输出缓冲区,只需要从输出缓冲区取出编码后的数据后用于处理,最后将空间返还给编码器。
使用MediaCodec可以有三种方式
1.使用Buffer数组的同步方式(API20以后deprecated)
2.使用Buffer同步方式
3.异步方式
前两种方式基本类似,只是使用Buffer的同步方式性能更好。以第一种方式为例:
当要中指编码时只需要在最后一个有效数据Buffer中或者额外发送一个空的Buffer指定其Flag标志位为BUFFER_FLAG_END_OF_STREAM,然后调用queueInputBuffer发送给编码器即可。
除了直接使用ByteBuffer作为MediaCodec的输入输出还可以通过Surface作为数据的载体,具体的有两种:使用InputSurface和使用OutputSurface。
以使用InputSurface为例,使用createInputSurface()方法创建一个InputSurface。
RequestsaSurfacetouseastheinputtoanencoder,inplaceofinputbuffers.表明使用这个Surface来代替Buffer作为编码器的输入。
编码器将会自动的从InputSurface读取帧数据送往编码器。此时访问不到输入Buffer缓冲区,使用getInputBuffers等方法抛出异常。编码流结束时调用signalEndOfInputStream函数,该函数调用后,Surface就会停止向编码器提供数据流。
显然,这种方式在不需要获取音视频流的原始数据时是非常方便的。
音视频混合
封装一般指的是将进行过压缩编码的音频流和视频流进行合并,封装格式种类很多,例如MP4,MKV等等,它的作用就是将压缩编码的视频和音频按照一定的格式封装在一起。例如,将H.264编码的视频码流和AAC编码的音频码流合并成MP4格式的数据,
Android也提供了MediaMuxer支持将编码后的音视频码流合并成MP4格式的文件。
使用MediaMuxer的关键代码如下
Android5.0录制视频的方案
通过MediaProjectionManager进行录制屏幕。关键代码如下
在onActivityResult中判断是否获得录屏权限。然后执行下面操作。
createVirtualDisplay:CreatesaVirtualDisplaytocapturethecontentsofthescreen
这个参数表示了录制的手机屏幕的内容要显示到哪个SurfaceView上,实际上表示了屏幕帧数据的走向,这个参数非常关键。
相关的几个类
ImageReader:TheImageReaderclassallowsdirectapplicationaccesstoimagedatarenderedintoaSurface
ImageReader类允许应用直接访问渲染到Surface上的image数据。使用MediaProjectionManager录制的屏幕内容可以直接渲染到一个Surface上,这个参数在createVirtualDisplay时传入,但是我们无法访问到渲染的内容。所以ImageReader类主要是用于使用Surface时访问不到原始视频数据流的情形。要想访问到每一帧的内容可以使用ImageReader类。
该类有一个函数getSurface获取一个Surface,通过这个函数获取一个Surface用来为ImageReader产生Images即视频流帧数据。
将这个Surface指定到录制屏幕时的Surface参数,就可以通过ImageReader读取到Surface上面渲染的每一帧的数据,通过acquireLatestImage()等方法可以获取一个Image对象。
Image:AsinglecompleteimagebuffertousewithamediasourcesuchasaMediaCodecoraCameraDevice.
也就是说Image类表示一个图片缓冲区,用来和MediaCodec一起使用。通过Image对象可以得到这一帧画面的像素数据,应该是RGB数据,转化为YUV格式的数据house,然后通过MediaCodec对其进行编码。
本地录屏方案
有了上面的准备知识,再来看直播助手的两个主要功能本地录屏和推流功能的实现逻辑容易多了。
本地录屏的逻辑:本地录屏不需要操作视频原始数据,因此使用InputSurface作为编码器的输入。
视频:MediaProjection通过createVirtualDisplay创建的VirtualDisplay传入的Surface是通过MediaCodec的createInputSurface方法返回的,表明编码器的输入其实来自于录制到的屏幕数据,于是只需要在MediaCodec的输出缓冲区中拿到编码后的ByteBuffer即可。
音频:录制程序获得音频原始数据PCM,传给MediaCodec编码,然后从MediaCodec的输出缓冲区拿到编码后的ByteBuffer即可。
最终通过合并模块将音视频混合。
推流的逻辑:推流SDK提供了一个编码器TVLiveSdk_LiveEncoder,它接受YUV420的视频数据格式和PCM编码的原始音频流。因此要获得视频的原始帧数据才行,可以通过ImageReader实现该功能。
视频:MediaProjection通过createVirtualDisplay创建的VirtualDisplay传入的Surface是通过ImageReader的getSurface方法返回的,表明录制的屏幕帧数据传递到ImageReader,于是通过ImageReader的相关API可以读取到录制的屏幕每一帧的数据,但这个数据时RGB格式的,转化为YUV格式后传到推流SDK即可。
音频:由于推流SDK需要的就是原始PCM编码的音频数据,因此录制到音频数据后直接调用推流SDK即可。
简单说就是重定向了屏幕录制的数据的方向,这个Surface提供的是什么,录制的视频数据就传到哪里。Surface提供的是本地某个SurfaceView控件,那么就会将屏幕内容显示到这个控件上,提供MediaCodec就是作为编码器的输入源最终获得编码后的数据,提供ImageReader就会作为ImageReader的数据源,最终获得了视频的原始数据流。