Android MediaCodec硬解码H264文件
来源:互联网 发布:淘宝灯笼是什么意思 编辑:程序博客网 时间:2024/05/16 10:07
转载请注明出处:http://blog.csdn.net/a512337862/article/details/72629779
最近开始接触Android MediaCodec,经过学习之后总算是有点简单的收获,所以在这里总结一下,希望能帮到一些有需要的人。今天主要是关于利用MediaCodec解码H264文件之后利用SurfaceView进行显示:
封装解码器
这部分代码主要功能是对MediaCodec进行封装,实现MediaCodec初始化以及一些配置,并提供解码的h264视频帧的方法:
/** * Created by ZhangHao on 2016/8/5. * 用于硬件解码(MediaCodec)H264的工具类 */public class MediaCodecUtil { //自定义的log打印,可以无视 Logger logger = Logger.getLogger(); private String TAG = "MediaCodecUtil"; //解码后显示的surface及其宽高 private SurfaceHolder holder; private int width, height; //解码器 private MediaCodec mCodec; private boolean isFirst = true; // 需要解码的类型 private final static String MIME_TYPE = "video/avc"; // H.264 Advanced Video private final static int TIME_INTERNAL = 5; /** * 初始化解码器 * * @param holder 用于显示视频的surface * @param width surface宽 * @param height surface高 */ public MediaCodecUtil(SurfaceHolder holder, int width, int height) {// logger.d("MediaCodecUtil() called with: " + "holder = [" + holder + "], " +// "width = [" + width + "], height = [" + height + "]"); this.holder = holder; this.width = width; this.height = height; } public MediaCodecUtil(SurfaceHolder holder) { this(holder, holder.getSurfaceFrame().width(), holder.getSurfaceFrame().height()); } public void startCodec() { if (isFirst) { //第一次打开则初始化解码器 initDecoder(); } } private void initDecoder() { try { //根据需要解码的类型创建解码器 mCodec = MediaCodec.createDecoderByType(MIME_TYPE); } catch (IOException e) { e.printStackTrace(); } //初始化MediaFormat MediaFormat mediaFormat = MediaFormat.createVideoFormat(MIME_TYPE, width, height); //配置MediaFormat以及需要显示的surface mCodec.configure(mediaFormat, holder.getSurface(), null, 0); //开始解码 mCodec.start(); isFirst = false; } int mCount = 0; public boolean onFrame(byte[] buf, int offset, int length) { // 获取输入buffer index ByteBuffer[] inputBuffers = mCodec.getInputBuffers(); //-1表示一直等待;0表示不等待;其他大于0的参数表示等待毫秒数 int inputBufferIndex = mCodec.dequeueInputBuffer(-1); if (inputBufferIndex >= 0) { ByteBuffer inputBuffer = inputBuffers[inputBufferIndex]; //清空buffer inputBuffer.clear(); //put需要解码的数据 inputBuffer.put(buf, offset, length); //解码 mCodec.queueInputBuffer(inputBufferIndex, 0, length, mCount * TIME_INTERNAL, 0); mCount++; } else { return false; } // 获取输出buffer index MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); int outputBufferIndex = mCodec.dequeueOutputBuffer(bufferInfo, 100); //循环解码,直到数据全部解码完成 while (outputBufferIndex >= 0) { //logger.d("outputBufferIndex = " + outputBufferIndex); //true : 将解码的数据显示到surface上 mCodec.releaseOutputBuffer(outputBufferIndex, true); outputBufferIndex = mCodec.dequeueOutputBuffer(bufferInfo, 0); } if (outputBufferIndex < 0) { //logger.e("outputBufferIndex = " + outputBufferIndex); } return true; } /** *停止解码,释放解码器 */ public void stopCodec() { try { mCodec.stop(); mCodec.release(); mCodec = null; isFirst = true; } catch (Exception e) { e.printStackTrace(); mCodec = null; } }}
读取文件线程
这部分代码的主要功能是利用线程去读取指定的h264文件,通过判断I帧或者P帧的帧头来读取每一帧的数据送入解码器进行解码,并根据帧率进行休眠。
/** * Created by ZhangHao on 2017/5/5. * 读取H264文件送入解码器解码线程 */public class MediaCodecThread extends Thread { //自定义的log打印,可以无视 Logger logger = Logger.getLogger(); //解码器 private MediaCodecUtil util; //文件路径 private String path; //文件读取完成标识 private boolean isFinish = false; //这个值用于找到第一个帧头后,继续寻找第二个帧头,如果解码失败可以尝试缩小这个值 private int FRAME_MIN_LEN = 1024; //一般H264帧大小不超过200k,如果解码失败可以尝试增大这个值 private static int FRAME_MAX_LEN = 300 * 1024; //根据帧率获取的解码每帧需要休眠的时间,根据实际帧率进行操作 private int PRE_FRAME_TIME = 1000 / 25; /** * 初始化解码器 * * @param util 解码Util * @param path 文件路径 */ public MediaCodecThread(MediaCodecUtil util, String path) { this.util = util; this.path = path; } /** * 寻找指定buffer中h264头的开始位置 * * @param data 数据 * @param offset 偏移量 * @param max 需要检测的最大值 * @return h264头的开始位置 ,-1表示未发现 */ private int findHead(byte[] data, int offset, int max) { int i; for (i = offset; i <= max; i++) { //发现帧头 if (isHead(data, i)) break; } //检测到最大值,未发现帧头 if (i == max) { i = -1; } return i; } /** * 判断是否是I帧/P帧头: * 00 00 00 01 65 (I帧) * 00 00 00 01 61 / 41 (P帧) * * @param data * @param offset * @return 是否是帧头 */ private boolean isHead(byte[] data, int offset) { boolean result = false; // 00 00 00 01 x if (data[offset] == 0x00 && data[offset + 1] == 0x00 && data[offset + 2] == 0x00 && data[3] == 0x01 && isVideoFrameHeadType(data[offset + 4])) { result = true; } // 00 00 01 x if (data[offset] == 0x00 && data[offset + 1] == 0x00 && data[offset + 2] == 0x01 && isVideoFrameHeadType(data[offset + 3])) { result = true; } return result; } /** * I帧或者P帧 */ private boolean isVideoFrameHeadType(byte head) { return head == (byte) 0x65 || head == (byte) 0x61 || head == (byte) 0x41; } @Override public void run() { super.run(); File file = new File(path); //判断文件是否存在 if (file.exists()) { try { FileInputStream fis = new FileInputStream(file); //保存完整数据帧 byte[] frame = new byte[FRAME_MAX_LEN]; //当前帧长度 int frameLen = 0; //每次从文件读取的数据 byte[] readData = new byte[10 * 1024]; //开始时间 long startTime = System.currentTimeMillis(); //循环读取数据 while (!isFinish) { if (fis.available() > 0) { int readLen = fis.read(readData); //当前长度小于最大值 if (frameLen + readLen < FRAME_MAX_LEN) { //将readData拷贝到frame System.arraycopy(readData, 0, frame, frameLen, readLen); //修改frameLen frameLen += readLen; //寻找第一个帧头 int headFirstIndex = findHead(frame, 0, frameLen); while (headFirstIndex >= 0 && isHead(frame, headFirstIndex)) { //寻找第二个帧头 int headSecondIndex = findHead(frame, headFirstIndex + FRAME_MIN_LEN, frameLen); //如果第二个帧头存在,则两个帧头之间的就是一帧完整的数据 if (headSecondIndex > 0 && isHead(frame, headSecondIndex)) { logger.e("headSecondIndex:" + headSecondIndex); //视频解码 onFrame(frame, headFirstIndex, headSecondIndex - headFirstIndex); //截取headSecondIndex之后到frame的有效数据,并放到frame最前面 byte[] temp = Arrays.copyOfRange(frame, headSecondIndex, frameLen); System.arraycopy(temp, 0, frame, 0, temp.length); //修改frameLen的值 frameLen = temp.length; //线程休眠 sleepThread(startTime, System.currentTimeMillis()); //重置开始时间 startTime = System.currentTimeMillis(); //继续寻找数据帧 headFirstIndex = findHead(frame, 0, frameLen); } else { //找不到第二个帧头 headFirstIndex = -1; } } } else { //如果长度超过最大值,frameLen置0 frameLen = 0; } } else { //文件读取结束 isFinish = true; } } } catch (Exception e) { e.printStackTrace(); } } else { logger.e("File not found"); } } //视频解码 private void onFrame(byte[] frame, int offset, int length) { if (util != null) { try { util.onFrame(frame, offset, length); } catch (Exception e) { e.printStackTrace(); } } else { logger.e("mediaCodecUtil is NULL"); } } //修眠 private void sleepThread(long startTime, long endTime) { //根据读文件和解码耗时,计算需要休眠的时间 long time = PRE_FRAME_TIME - (endTime - startTime); if (time > 0) { try { Thread.sleep(time); } catch (InterruptedException e) { e.printStackTrace(); } } } //手动终止读取文件,结束线程 public void stopThread() { isFinish = true; }}
Activity
这里的代码就是在activity中对一个h264文件进行解码播放,布局文件只有一个surfaceView和一个Button,我就不贴出来了,下面的是activity的代码:
public class H264FileDecodeActivity extends AppCompatActivity { @Bind(R.id.test_surface_view) SurfaceView testSurfaceView; private SurfaceHolder holder; //解码器 private MediaCodecUtil codecUtil; //读取文件解码线程 private MediaCodecThread thread; //文件路径 private String path = Environment.getExternalStorageDirectory().toString() + "/SONA/test.h264"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_h264_file_decodec); ButterKnife.bind(this); initSurface(); } //初始化播放相关 private void initSurface() { holder = testSurfaceView.getHolder(); holder.addCallback(new SurfaceHolder.Callback() { @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceCreated(SurfaceHolder holder) { if (codecUtil == null) { codecUtil = new MediaCodecUtil(holder); codecUtil.startCodec(); } if (thread == null) { //解码线程第一次初始化 thread = new MediaCodecThread(codecUtil, path); } } @Override public void surfaceDestroyed(SurfaceHolder holder) { if (codecUtil != null) { codecUtil.stopCodec(); codecUtil = null; } if (thread != null && thread.isAlive()) { thread.stopThread(); thread = null; } } }); } public void onClick(View view) { switch (view.getId()) { case R.id.play_h264_file: if (thread != null) { thread.start(); } break; } }}
结语
1.因为文字功底太差,所以主要以贴代码为主,有什么大家疑问可以给我留言。接下来有空的话,我会简单介绍如何利用MediaCodec将AAC解码成PCM并利用AudioTrack进行播放。
2.2.H264,AAC解码Demo下载地址:http://download.csdn.net/detail/a512337862/9882200 ,附带aac以及H264文件以及源码。
阅读全文
0 0
- Android MediaCodec硬解码H264文件
- Android MediaCodec硬解码H264文件
- Android使用MediaCodec硬解码播放H264格式视频文件
- 【多媒体】Android使用MediaCodec硬解码播放H264格式视频文件
- Android使用MediaCodec硬解码播放H264格式视频文件
- android硬编码h264-MediaCodec
- android硬编码h264-MediaCodec
- android硬编码h264-MediaCodec
- android硬编码h264-MediaCodec
- android硬编码h264-MediaCodec
- MediaCodec : H264硬解码核心代码总结
- android利用MediaCodec硬解码
- Android Mediacodec硬解H264并显示
- android硬编码h264——MediaCodec
- Android 利用MediaCodec 实现硬编码 h264
- Android 用MediaCodec实现视频硬解码
- Android 用MediaCodec实现视频硬解码
- Android 用MediaCodec实现视频硬解码
- struts2拦截action多种方法
- 【java回调】同步/异步回调机制的原理和使用方法
- 如何将数组中奇数放在偶数前面。
- 关于Hibernate
- GIMP为证件照更换背景颜色
- Android MediaCodec硬解码H264文件
- nginx控制缓存
- Linux系统下的Xshell运行命令
- Ubuntu 16.04LTS修改开机启动项
- 10--MySQL数据查询应用(实战)(一)
- 进阶9_多线程3_线程池的基本
- Android之内置和外置sdcard路径显示并且写入数据
- css reset 解释****
- 对于Spring AOP的理解