Android硬件解码组件MediaCodec使用教程

作者:lpf_wei 时间:2023-03-14 01:35:36 

1.MediaCodec 是什么

MediaCodec类可以访问底层媒体编解码器框架(StageFright 或 OpenMAX),即编解码组件。是Android 的低层多媒体基础设施的一部分(通常与MediaExtractor、MediaSync、MediaMuxer、Image、Surface和AudioTrack一起使用),它本身并不具备Codec能力,通过调动底层编解码组件获得了Codec的能力。

2.创建MediaCodec的方式

2.1按照格式创建

  • createDecoderByType(String type):创建解码器

  • createEncoderByType(String type):创建编码器

type是数据解析阶段的mimeType,如"video/avc"

2.2按照名字创建

createByCodecName(String name)

OMX.google.h264.decoder: 软解码

OMX.MTK.VIDEO.DECODER>AVC:硬解码

3.MediaCode硬件解码并进行播放实例

private String mFilePath="/sdcard/DCIM/189017886849403.mp4";
   private DecodeThread mDecodeThread;
   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
//        setContentView(R.layout.activity_media_codec_decode);
       SurfaceView surfaceView=new SurfaceView(this);
       /*不自己维护缓冲区,等待屏幕的渲染引擎 将内容推送到用户前面*/
       surfaceView.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
       surfaceView.getHolder().addCallback(this);
       setContentView(surfaceView);
   }
  • 定义播放的视频路径

  • 定 * 码的线程

  • 创建SurfaceView,并设置Callback

@Override
public void surfaceCreated(@NonNull SurfaceHolder holder) {
}
@Override
public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
   if (mDecodeThread ==null){
       mDecodeThread =new DecodeThread(holder.getSurface());
       mDecodeThread.start();
   }
}
@Override
public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
   if (mDecodeThread !=null){
       mDecodeThread.interrupt();   //停止线程的正确姿势
   }
}
  • 在SurfaceView的回调函数surfaceChanged 开启线程

  • 在SurfaceView的回调函数surfaceDestroyed 打断线程

private class DecodeThread extends Thread{
       private MediaExtractor mMediaExtractor;
       private MediaCodec mMediaCodec;
       private Surface mSurface;
       /*通过构造方法将surface传递进来*/
       public DecodeThread(Surface surface){
           mSurface = surface;
       }
       @Override
       public void run() {
           super.run();
           mMediaExtractor = new MediaExtractor();
           try {
               mMediaExtractor.setDataSource(mFilePath);
           } catch (IOException e) {
               e.printStackTrace();
           }
           int trackCount = mMediaExtractor.getTrackCount();
           //从媒体提取器中拿到了 MIME 以及MediaFormat   通过MIME 创建的硬件解码器   通过MediaFormat配置的硬件解码器
           for (int i = 0; i < trackCount; i++) {
               MediaFormat trackFormat = mMediaExtractor.getTrackFormat(i);
               Log.d("lpf","trackFormat is "+trackFormat);
               String mime=trackFormat.getString(MediaFormat.KEY_MIME);
               Log.d("lpf","mime is "+mime);
               if (mime.startsWith("video/")){
                   mMediaExtractor.selectTrack(i);
                   try {
                       mMediaCodec=MediaCodec.createDecoderByType(mime);
                   } catch (IOException e) {
                       e.printStackTrace();
                   }
                   //这样配置之后,解码之后的数据就会 直接显示在mSurface 上边  这里是核心点
                   mMediaCodec.configure(trackFormat,mSurface,null,0);
                   break;
               }
           }
           if (mMediaCodec == null){
               return;
           }
           //调用Start 如果没有异常信息,表示成功构建组件
           mMediaCodec.start();
           ByteBuffer[] inputBuffers = mMediaCodec.getInputBuffers();
           ByteBuffer[] outputBuffers = mMediaCodec.getOutputBuffers();
           //每个Buffer的元数据包括具体的范围以及偏移大小,以及数据中心相关解码的buffer
           MediaCodec.BufferInfo info=new MediaCodec.BufferInfo();
           boolean isEOF=false;
           long startMs=System.currentTimeMillis();
           while (!Thread.interrupted()){//只要线程不中断
               if (!isEOF){
                   //返回有效的buffer 索引,如果没有相关的Buffer可用,就返回-1
                   //传入的timeoutUs为0表示立即返回
//                    如果数据的buffer可用,将无限期等待timeUs的单位是纳秒
                   int index =mMediaCodec.dequeueInputBuffer(10000);
                   if (index >= 0){
                       ByteBuffer byteBuffer=inputBuffers[index];
                       Log.d("lpf","bytebuffer is "+byteBuffer);
                       int sampleSize=mMediaExtractor.readSampleData(byteBuffer,0);
                       Log.d("lpf","sampleSize is "+sampleSize);
                       if (sampleSize < 0){
                           Log.d("lpf","inputBuffer is BUFFER_FLAG_END_OF_STREAMING");
                           mMediaCodec.queueInputBuffer(index,0,0,0,MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                           isEOF=true;
                       }else{
                           mMediaCodec.queueInputBuffer(index,0,sampleSize,mMediaExtractor.getSampleTime(),0);
                           mMediaExtractor.advance();  //下一帧数据
                       }
                   }
               }
               int outIndex=mMediaCodec.dequeueOutputBuffer(info,100000);
               switch (outIndex){
                   case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
                       //当buffer变化时,必须重新指向新的buffer
                       outputBuffers=mMediaCodec.getOutputBuffers();
                       break;
                   case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
                       //当Buffer的封装格式发生变化的时候,需重新指向新的buffer格式
                       Log.d("lpf","output  buffer changed");
                           break;
                   case MediaCodec.INFO_TRY_AGAIN_LATER:
                       //dequeueOutputBuffer 超时的时候会到这个case
                       Log.d("lpf","dequeueOutputBuffer timeout");
                       break;
                   default:
                       ByteBuffer buffer=outputBuffers[outIndex];
                       //由于配置的时候 将Surface 传进去了  所以解码的时候 将数据直接交给了Surface进行显示了
                       //使用简单的时钟的方式保持视频的fps(每秒显示的帧数),不然视频会播放的比较快
                       Log.d("lpf","解码之后的 buffer数据="+buffer);
                       while (info.presentationTimeUs/1000>System.currentTimeMillis()-startMs){
                           try {
                               Thread.sleep(10);
                           } catch (InterruptedException e) {
                               e.printStackTrace();
                           }
                       }
                       mMediaCodec.releaseOutputBuffer(outIndex,true);
                       break;
               }
               if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0){
                   Log.d("lpf","outputBuffer BUFFER_FLAG_END_OF_STREAM");
                   break;
               }
           }
           mMediaCodec.stop();
           mMediaCodec.release();// 释放组件
           mMediaExtractor.release();
       }
   }
  • 定义媒体提取器:MediaExtractor,通过媒体提取器,得到视频的MIME以及MediaFormat数据

  • 通过媒体提取器拿到的MIME 类型来创建硬件解码器MediaCodec,再通过上一步拿到的额MediaFormat来配置硬件解码器。

  • 配置完成后,调用硬件解码器的start函数,解码器就开始工作了

  • 从解码器上拿到输入和输出Buffer数组,用于解码使用

  • dequeueInputBuffer通过这个函数得到待解码的数据index,然后通过index拿到ByteBuffer

  • 然后mMediaExtractor调用readSampleData来读取数据,将数据得到ByteBuffer中去

  • 接下来将数据丢入编码队列,这个队列在MediaCodec中

  • 然后就可以从硬件解码器中获取数据了,由于配置的时候将Surface当做参数配置给了MediaCodec,所以数据会直接通过SurfaceView进行显示。

4.MediaCodec 异步解码进行播放

public void startSyncPlay(Surface surface){
   mMediaExtractor = new MediaExtractor();
   try {
       mMediaExtractor.setDataSource(mFilePath);
   } catch (IOException e) {
       e.printStackTrace();
   }
   int trackCount = mMediaExtractor.getTrackCount();
   //从媒体提取器中拿到了 MIME 以及MediaFormat   通过MIME 创建的硬件解码器   通过MediaFormat配置的硬件解码器
   for (int i = 0; i < trackCount; i++) {
       MediaFormat trackFormat = mMediaExtractor.getTrackFormat(i);
       Log.d("lpf","trackFormat is "+trackFormat);
       String mime=trackFormat.getString(MediaFormat.KEY_MIME);
       Log.d("lpf","mime is "+mime);
       if (mime.startsWith("video/")) {
           mMediaExtractor.selectTrack(i);
           try {
               mMediaCodec=MediaCodec.createDecoderByType(mime);
               if (mMediaCodec == null){
                   return;
               }
               //这样配置之后,解码之后的数据就会 直接显示在mSurface 上边  这里是核心点
               mMediaCodec.configure(trackFormat,surface,null,0);
               mMediaCodec.setCallback(new MediaCodec.Callback() {
                   @Override
                   public void onInputBufferAvailable(@NonNull MediaCodec codec, int index) {
                       ByteBuffer inputBuffer = codec.getInputBuffer(index);
                       int sampleSize=mMediaExtractor.readSampleData(inputBuffer,0);
                       if (sampleSize>0) {
                           codec.queueInputBuffer(index,0,sampleSize,mMediaExtractor.getSampleTime(),0);
                           mMediaExtractor.advance();  //下一帧数据
                       }else {
                           codec.queueInputBuffer(index,0,0,0,MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                       }
                   }
                   @Override
                   public void onOutputBufferAvailable(@NonNull MediaCodec codec, int index, @NonNull MediaCodec.BufferInfo info) {
                       if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0){
                           Log.d("lpf","outputBuffer BUFFER_FLAG_END_OF_STREAM");
                           codec.stop();
                           codec.release();// 释放组件
                           mMediaExtractor.release();
                           return;
                       }
                       if (index>0){
                           if (startMs==-1){
                               startMs=System.currentTimeMillis();
                           }
                           sleepRender(info,startMs);
                       }
                       codec.releaseOutputBuffer(index,true);  //释放缓冲区,并交给Surface 进行播放
                   }
                   @Override
                   public void onError(@NonNull MediaCodec codec, @NonNull MediaCodec.CodecException e) {
                   }
                   @Override
                   public void onOutputFormatChanged(@NonNull MediaCodec codec, @NonNull MediaFormat format) {
                   }
               });
               //调用Start 如果没有异常信息,表示成功构建组件
               mMediaCodec.start();
           } catch (IOException e) {
               e.printStackTrace();
           }
       }
   }
}

异步的方式进行解码操作,比较简单,推荐使用这个方式

  • onInputBufferAvailable,将需要编码的数据从这个回调方法中添加到解码队列

  • onOutputBufferAvailable 在这个回调方法中就能拿到编码好的数据,可以说非常便利,思路也比同步的时候更加简洁。

来源:https://blog.csdn.net/u014078003/article/details/127952067

标签:Android,MediaCodec,硬件解码
0
投稿

猜你喜欢

  • Java Iterator迭代器_动力节点Java学院整理

    2022-11-08 13:50:39
  • 华为鸿蒙系统应用开发工具 DevEco Studio的安装和使用图文教程

    2022-09-14 05:04:23
  • Java的Struts框架中<results>标签的使用方法

    2022-04-16 16:08:22
  • Java8 Collectors.toMap的坑

    2023-06-24 14:47:48
  • 详解Java中HashSet和TreeSet的区别

    2022-01-17 09:47:47
  • 详解Java枚举为什么是单例模式的最佳选择

    2022-07-16 20:42:47
  • 解决eclipse上传svn忽略target文件夹的坑

    2023-09-12 04:04:23
  • 基于Java8实现提高Excel读写效率

    2023-11-25 10:01:37
  • Android RxJava创建操作符Interval

    2023-08-14 01:26:24
  • SpringBoot @Import与@Conditional注解使用详解

    2023-12-14 16:42:45
  • Java 中 synchronized的用法详解(四种用法)

    2022-03-11 08:55:05
  • Android EasyPlayer声音自动停止、恢复,一键静音等功能

    2023-01-12 21:16:48
  • 解决Mybatis的@Param()注解导致分页失效的问题

    2022-05-01 13:24:53
  • 使用JPA主键@Id,@IdClass,@Embeddable,@EmbeddedId问题

    2022-07-07 18:35:41
  • 使用JavaWeb webSocket实现简易的点对点聊天功能实例代码

    2023-10-29 00:14:17
  • java快速生成数据库文档详情

    2023-11-10 05:25:20
  • 在IDEA中配置tomcat并创建tomcat项目的图文教程

    2023-08-11 11:33:59
  • 详解Spring boot/Spring 统一错误处理方案的使用

    2023-11-24 12:56:07
  • Java基于正则实现的日期校验功能示例

    2021-09-30 02:43:26
  • Java设计模式之享元模式示例详解

    2022-12-08 22:19:46
  • asp之家 软件编程 m.aspxhome.com