最近公司在做视频编辑相关的功能,现在将做项目过程中遇到的问题总结如下:
1、I帧
I帧又称帧内编码帧,是一种自带全部信息的独立帧,无需参考其他图像便可独立进行解码,可以简单理解为一张静态画面。视频序列中的第一个帧始终都是I帧,因为它是关键帧。
2、P帧
P帧又称帧间预测编码帧,需要参考前面的I帧才能进行编码。表示的是当前帧画面与前一帧(前一帧可能是I帧也可能是P帧)的差别。解码时需要用之前缓存的画面叠加上本帧定义的差别,生成最终画面。与I帧相比,P帧通常占用更少的数据位,但不足是,由于P帧对前面的P和I参考帧有着复杂的依耐性,因此对传输错误非常敏感。
3、B帧
B帧又称双向预测编码帧,也就是B帧记录的是本帧与前后帧的差别。也就是说要解码B帧,不仅要取得之前的缓存画面,还要解码之后的画面,通过前后画面的与本帧数据的叠加取得最终的画面。B帧压缩率高,但是对解码性能要求较高。动漫中用到的这种编码方式比较普遍,表现在代码层面,就是通过MediaExtractor将视频帧分离出来之后,时间戳不是按照时间顺序排列。

MediaExtractor extractor = new MediaExtractor();
extractor.setDataSource(…);//设置视频地址(可以在线地址,本地地址)
int numTracks = extractor.getTrackCount();//得到轨道数
for (int i = 0; i < numTracks; ++i) {
MediaFormat format = extractor.getTrackFormat(i);
String mime = format.getString(MediaFormat.KEY_MIME);
if (weAreInterestedInThisTrack) {//如果需要这个轨道,则直接selectTrack选中
extractor.selectTrack(i);
}
}
ByteBuffer inputBuffer = ByteBuffer.allocate(...)
while (extractor.readSampleData(inputBuffer, ...) >= 0) {//选中之后,就可以进行读取数据到buffer中
int trackIndex = extractor.getSampleTrackIndex();
long presentationTimeUs = extractor.getSampleTime();
...
extractor.advance();//步进到下一个帧
}
extractor.release();//释放资源
extractor = null;

- 解码

- 渲染
- 编码
mediacodec的编码器支持从surface中读取数据,然后进行输入编码器,比直接将数据写入inputbuffer快很多。这里我们调用swapbuffer将离屏渲染的视频和字幕,交换到mediacodec创建出来的inputsurface上,然后再次通过mediacodec编码器,最后从编码器的outputbuffer中将数据写入混合器.
- 音视频混合写入文件
上一步将视频编码后,获取到了编码后的视频数据,然后,我们利用mediamuxer将数据写入指定的视频轨道。在这里视频处理的部分结束了。对于音频部分,由于目前音频目前不需要编辑,所以把直接用mediaextrator读取的音频数据,直接通过mediamuxer写入文件中。
MediaMuxer muxer = new MediaMuxer("temp.mp4", OutputFormat.MUXER_OUTPUT_MPEG_4); // More often, the MediaFormat will be retrieved from MediaCodec.getOutputFormat() // or MediaExtractor.getTrackFormat(). MediaFormat audioFormat = new MediaFormat(...); MediaFormat videoFormat = new MediaFormat(...); int audioTrackIndex = muxer.addTrack(audioFormat); int videoTrackIndex = muxer.addTrack(videoFormat); ByteBuffer inputBuffer = ByteBuffer.allocate(bufferSize); boolean finished = false; BufferInfo bufferInfo = new BufferInfo(); muxer.start(); while(!finished) { // getInputBuffer() will fill the inputBuffer with one frame of encoded // sample from either MediaCodec or MediaExtractor, set isAudioSample to // true when the sample is audio data, set up all the fields of bufferInfo, // and return true if there are no more samples. finished = getInputBuffer(inputBuffer, isAudioSample, bufferInfo); if (!finished) { int currentTrackIndex = isAudioSample ? audioTrackIndex : videoTrackIndex; muxer.writeSampleData(currentTrackIndex, inputBuffer, bufferInfo); } }; muxer.stop(); muxer.release();