h264编码
h264的压缩方法:
-
1、分组:把几帧图像分为一组(GOP,也就是一个序列),为防止运动变化,帧数不宜取多
-
2、定义帧:将每组内各帧图像定义为三种类型,即 I 帧、B帧和P帧
-
3、预测帧:以帧作为基础帧,以帧预测P帧,再由 I 帧和P帧预测B帧
-
4、数据传输:最后将 I 帧数据与预测的差值信息进行存储和传输
帧内(Intraframe)
压缩也称为空间压缩(Spatial compression)。当压缩一帧图像时,仅考虑本帧的数据而不考虑相邻帧之间的冗余信息,这实际上与静态图像压缩类似。帧内一般采用有损压缩算法,由于帧内压缩是编码一个完整的图像,因此可以独立的解码、显示。帧内压缩一般达不到很高的压缩,跟编码jpeg差不多。
帧间(Interframe)
压缩的原理是:相邻几帧的数据有很大的相关性,或者说前后两帧信息变化很小的特点,也即连续的视频及其相邻帧之间具有冗余信息,根据这一特性,压缩相邻帧之间的冗余量就可以进一步提高压缩量,减少压缩比。帧间压缩也称为时间压缩,它通过比较时间轴上不同帧之间的数据进行压缩。帧间压缩一般是无损的。帧差值(Frame differencing)算法是一种典型的时间压缩发,它通过比较本帧与相邻帧之间的差异,仅记录本帧与其相邻帧的差值,这样可以大大减少数据量。
有损(Lossy)
压缩和无损(Lossy less)压缩。无损压缩也即压缩前和解压缩后的数据完全一致。多数的无损压缩都采用RLE行程编码算法。有损压缩意味着解压缩后的数据与压缩前的数据不一致。在压缩的过程中要丢失一些人眼和耳朵所不敏感的图像或音频信息,而且丢失的信息不可恢复。几乎所有高压缩的算法都采用有损压缩,这样才能达到低数据率的目标。丢失的数据率与压缩比有关,压缩比越小,丢失的数据越多,解压缩后的效果一般越差。此外,某些有损压缩算法采用多次重复压缩的方式,这样还会引起额外的数据丢失
硬解码流程
mediaCodec 是android系统提供的用于操作dps解码芯片的上层接口。它是流水线工作模式。
创建 - 配置 - 启动 - 解码 - 结束
mediaCodec创建
创建mediaCodec的解码器,解码器根据码流的格式可以有很多种,最常用的就是h264码流,在android定义为video/avc
mediaCodec = MediaCodec.createDecoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
mediaCodec配置
配置主要为了告知解码器一些关于流的信息,比如宽高,帧率等。最重要的是如果要渲染到页面中,需要提供surface,这个surface一般是由surfaceView初始化获得。
MediaFormat mediaformat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC,64, 368);
//设置关键帧间隔
mediaformat.setInteger(MediaFormat.KEY_FRAME_RATE, 15);
//加入的surface表示后续的内容输出到surface中,flag=0,如果是编码则需要设置flag为编码
mediaCodec.configure(mediaformat, surface, null, 0);
启动
mediaCodec.start();
解码流程
- 从MediaCodec获取ByteBuffer
int bufferIndex = mediaCodec.dequeueInputBuffer(10000);
if (bufferIndex >= 0){
//获取这块空闲的buffer
ByteBuffer byteBuffer = mediaCodec.getInputBuffer(bufferIndex);
}
- 从视频中获取每帧的数据,填充ByteBuffer,提交给mediaCodec
byte[] bytes = getBytes(path);
int nextFrameStart = findByFrame(bytes , startIndex + 2, bytes.length);
int size = nextFrameStart -startIndex;
byteBuffer.put(bytes , startIndex , size);
mediaCodec.queueInputBuffer(bufferIndex ,0 , size , 0 , 0);
- 获取输出信息,输出的yuv数据信息保存在BufferInfo中
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
int outIndex = mediaCodec.dequeueOutputBuffer(info , 1100);
if (outIndex >= 0){
//控制播放速度
Thread.sleep(33);
//将解码好的数据渲染到surface中
mediaCodec.releaseOutputBuffer(outIndex , true);
}
整体过程
byte[] bytes = getBytes(path);
int startIndex = 0;
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
while (true){
int nextFrameStart = findByFrame(bytes , startIndex + 2, bytes.length);
int size = nextFrameStart -startIndex;
if (size > 0 ){
//从mediaCodec中找到一块空闲的buffer,mediaCodec是为所有的app服务的,因此不能通过回调的形式传回处理好的数据
int bufferIndex = mediaCodec.dequeueInputBuffer(10000);
if (bufferIndex >= 0){
//获取这块空闲的buffer
ByteBuffer byteBuffer = mediaCodec.getInputBuffer(bufferIndex);
Log.i("hucaihua", "decodeH264: 输入 "+size);
//往buffer中放入要解码的数据
byteBuffer.put(bytes , startIndex , size);
//解码不需要设置pts,因为文件中的顺序,刚好就是解码顺序。
//告知mediaCodec已经放置好数据,可以开始解码了
mediaCodec.queueInputBuffer(bufferIndex ,0 , size , 0 , 0);
startIndex = nextFrameStart;
}else {
Log.e("hucaihua" , "没有合适的Buffer");
}
}
// info用来装decode出来的数据的信息,它是远远大于我们输入的信息的。
// 从mediaCodec中获取输出数据,输出数据的信息与输入数据信息不一样,因此mediaCodec通过BufferInfo来告知我们
int outIndex = mediaCodec.dequeueOutputBuffer(info , 1100);
if (outIndex >= 0){
//控制播放速度
Thread.sleep(33);
//将解码好的数据渲染到surface中
mediaCodec.releaseOutputBuffer(outIndex , true);
}
}
// 0x 00 00 00 01 或者 0x 00 00 01
private int findByFrame( byte[] bytes, int start, int totalSize) {
for (int i = start; i <= totalSize-4; i++) {
if (((bytes[i] == 0x00) && (bytes[i + 1] == 0x00) && (bytes[i + 2] == 0x00) && (bytes[i + 3] == 0x01))
||((bytes[i] == 0x00) && (bytes[i + 1] == 0x00) && (bytes[i + 2] == 0x01))) {
return i;
}
}
return -1;
}
结束
mediaCodec.stop();