Android MediaCodec硬件解码视频播放

Posted 若之灵动

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android MediaCodec硬件解码视频播放相关的知识,希望对你有一定的参考价值。

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 在这个回调方法中就能拿到编码好的数据,可以说非常便利,思路也比同步的时候更加简洁。

Android 音视频 - MediaCodec 编解码音视频

我们知道 Camera 采集回传的是 YUV 数据,AudioRecord 是 PCM,我们要对这些数据进行编码(压缩编码),这里我们来说在 Android 上音视频编解码逃不过的坑-MediaCodec。

MediaCodec

PSMediaCodec 可以用来编/解码 音/视频

MediaCodec 简单介绍

MediaCodec 类可用于访问低级媒体编解码器,即编码器/解码器组件。 它是 Android 低级多媒体支持基础结构的一部分(通常与 MediaExtractor,MediaSync,MediaMuxer,MediaCrypto,MediaDrm,Image,Surface 和 AudioTrack 一起使用)。关于 MediaCodec 的描述可参看官方介绍MediaCodec

广义而言,编解码器处理输入数据以生成输出数据。 它异步处理数据,并使用一组输入和输出缓冲区。 在简单的情况下,您请求(或接收)一个空的输入缓冲区,将其填充数据并将其发送到编解码器进行处理。 编解码器用完了数据并将其转换为空的输出缓冲区之一。 最后,您请求(或接收)已填充的输出缓冲区,使用其内容并将其释放回编解码器。

PS 读者如果对生产者-消费者模型还有印象的话,那么 MediaCodec 的运行模式其实也不难理解。

下面是 MediaCodec 的简单类图

MediaCodec 状态机

在 MediaCodec 生命周期内,编解码器从概念上讲处于以下三种状态之一:Stopped,Executing 或 Released。Stopped 的集体状态实际上是三个状态的集合:Uninitialized,Configured 和 Error,而 Executing 状态从概念上讲经过三个子状态:Flushed,Running 和 Stream-of-Stream。

使用工厂方法之一创建编解码器时,编解码器处于未初始化状态。首先,您需要通过 configure(…)对其进行配置,使它进入已配置状态,然后调用 start()将其移至执行状态。在这种状态下,您可以通过上述缓冲区队列操作来处理数据。

执行状态具有三个子状态:Flushed,Running 和 Stream-of-Stream。在 start()之后,编解码器立即处于 Flushed 子状态,其中包含所有缓冲区。一旦第一个输入缓冲区出队,编解码器将移至“Running”子状态,在此状态下将花费大部分时间。当您将输入缓冲区与流结束标记排队时,编解码器将转换为 End-of-Stream 子状态。在这种状态下,编解码器将不再接受其他输入缓冲区,但仍会生成输出缓冲区,直到在输出端达到流结束为止。在执行状态下,您可以使用 flush()随时返回到“刷新”子状态。

调用 stop()使编解码器返回 Uninitialized 状态,随后可以再次对其进行配置。使用编解码器完成操作后,必须通过调用 release()释放它。

在极少数情况下,编解码器可能会遇到错误并进入“错误”状态。使用来自排队操作的无效返回值或有时通过异常来传达此信息。调用 reset()使编解码器再次可用。您可以从任何状态调用它,以将编解码器移回“Uninitialized”状态。否则,请调用 release()以移至终端的“Released”状态。

PSMediaCodec 数据处理的模式可分为同步和异步,下面我们会一一分析

MediaCodec 同步模式

上代码

    public H264MediaCodecEncoder(int width, int height) {
      //设置MediaFormat的参数
        MediaFormat mediaFormat = MediaFormat.createVideoFormat(MIMETYPE_VIDEO_AVC, width, height);
        mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);
        mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, width * height * 5);
        mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 30);//FPS
        mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);

        try {
          //通过MIMETYPE创建MediaCodec实例
            mMediaCodec = MediaCodec.createEncoderByType(MIMETYPE_VIDEO_AVC);
            //调用configure,传入的MediaCodec.CONFIGURE_FLAG_ENCODE表示编码
            mMediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
            //调用start
            mMediaCodec.start();


        } catch (Exception e) {
            e.printStackTrace();
        }

    }

调用 putData 向队列中 add 原始 YUV 数据

  public void putData(byte[] buffer) {
        if (yuv420Queue.size() >= 10) {
            yuv420Queue.poll();
        }
        yuv420Queue.add(buffer);
 }

//开启编码
   public void startEncoder() {
       isRunning = true;
       ExecutorService executorService = Executors.newSingleThreadExecutor();
       executorService.execute(new Runnable() {
           @Override
           public void run() {
               byte[] input = null;
               while (isRunning) {
                   
                   if (yuv420Queue.size() > 0) {
                       //从队列中取数据
                       input = yuv420Queue.poll();
                   }
                   if (input != null) {
                       try {
                           //【1】dequeueInputBuffer
                           int inputBufferIndex = mMediaCodec.dequeueInputBuffer(TIMEOUT_S);
                           if (inputBufferIndex >= 0) {
                              //【2】getInputBuffer
                               ByteBuffer inputBuffer = null;
                               if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
                                   inputBuffer = mMediaCodec.getInputBuffer(inputBufferIndex);
                               } else {
                                   inputBuffer = mMediaCodec.getInputBuffers()[inputBufferIndex];
                               }
                               inputBuffer.clear();
                               inputBuffer.put(input);
                               //【3】queueInputBuffer
                               mMediaCodec.queueInputBuffer(inputBufferIndex, 0, input.length, getPTSUs(), 0);
                           }

                           MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
                           //【4】dequeueOutputBuffer
                           int outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_S);
                           if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                               MediaFormat newFormat = mMediaCodec.getOutputFormat();
                               if (null != mEncoderCallback) {
                                   mEncoderCallback.outputMediaFormatChanged(H264_ENCODER, newFormat);
                               }
                               if (mMuxer != null) {
                                   if (mMuxerStarted) {
                                       throw new RuntimeException("format changed twice");
                                   }
                                   // now that we have the Magic Goodies, start the muxer
                                   mTrackIndex = mMuxer.addTrack(newFormat);
                                   mMuxer.start();

                                   mMuxerStarted = true;
                               }
                           }

                           while (outputBufferIndex >= 0) {
                               ByteBuffer outputBuffer = null;
                                //【5】getOutputBuffer
                               if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
                                   outputBuffer = mMediaCodec.getOutputBuffer(outputBufferIndex);
                               } else {
                                   outputBuffer = mMediaCodec.getOutputBuffers()[outputBufferIndex];
                               }
                               if (bufferInfo.flags == MediaCodec.BUFFER_FLAG_CODEC_CONFIG) {
                                   bufferInfo.size = 0;
                               }

                               if (bufferInfo.size > 0) {

                                   // adjust the ByteBuffer values to match BufferInfo (not needed?)
                                   outputBuffer.position(bufferInfo.offset);
                                   outputBuffer.limit(bufferInfo.offset + bufferInfo.size);
                                   // write encoded data to muxer(need to adjust presentationTimeUs.
                                   bufferInfo.presentationTimeUs = getPTSUs();

                                   if (mEncoderCallback != null) {
                                       //回调
                                       mEncoderCallback.onEncodeOutput(H264_ENCODER, outputBuffer, bufferInfo);
                                   }
                                   prevOutputPTSUs = bufferInfo.presentationTimeUs;
                                   if (mMuxer != null) {
                                       if (!mMuxerStarted) {
                                           throw new RuntimeException("muxer hasn't started");
                                       }
                                       mMuxer.writeSampleData(mTrackIndex, outputBuffer, bufferInfo);
                                   }

                               }
                               mMediaCodec.releaseOutputBuffer(outputBufferIndex, false);
                               bufferInfo = new MediaCodec.BufferInfo();
                               outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_S);
                           }
                       } catch (Throwable throwable) {
                           throwable.printStackTrace();
                       }
                   } else {
                       try {
                           Thread.sleep(500);
                       } catch (InterruptedException e) {
                           e.printStackTrace();
                       }
                   }
               }

           }
       });
   }

PS 编解码这种耗时操作要在单独的线程中完成,我们这里有个缓冲队列ArrayBlockingQueue<byte[]> yuv420Queue = new ArrayBlockingQueue<>(10);,用来接收从 Camera 回调中传入的 byte[] YUV 数据,我们又新建立了一个现成来从缓冲队列 yuv420Queue中循环读取数据交给 MediaCodec 进行编码处理,编码完成的格式是由 mMediaCodec = MediaCodec.createEncoderByType(MIMETYPE_VIDEO_AVC);指定的,这里输出的是目前最为广泛使用的H264格式

完整代码请看H264MediaCodecEncoder

MediaCodec 异步模式

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public H264MediaCodecAsyncEncoder(int width, int height) {
        MediaFormat mediaFormat = MediaFormat.createVideoFormat(MIMETYPE_VIDEO_AVC, width, height);
        mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);
        mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, width * height * 5);
        mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 30);//FPS
        mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);

        try {
            mMediaCodec = MediaCodec.createEncoderByType(MIMETYPE_VIDEO_AVC);
            mMediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
      //设置回调
            mMediaCodec.setCallback(new MediaCodec.Callback() {
                @Override
                 /**
             * Called when an input buffer becomes available.
             *
             * @param codec The MediaCodec object.
             * @param index The index of the available input buffer.
             */
                public void onInputBufferAvailable(@NonNull MediaCodec codec, int index) {
                    Log.i("MFB", "onInputBufferAvailable:" + index);
                    byte[] input = null;
                    if (isRunning) {
                        if (yuv420Queue.size() > 0) {
                            input = yuv420Queue.poll();
                        }
                        if (input != null) {
                            ByteBuffer inputBuffer = codec.getInputBuffer(index);
                            inputBuffer.clear();
                            inputBuffer.put(input);
                            codec.queueInputBuffer(index, 0, input.length, getPTSUs(), 0);
                        }
                    }
                }

                @Override
                  /**
             * Called when an output buffer becomes available.
             *
             * @param codec The MediaCodec object.
             * @param index The index of the available output buffer.
             * @param info Info regarding the available output buffer {@link MediaCodec.BufferInfo}.
             */
                public void onOutputBufferAvailable(@NonNull MediaCodec codec, int index, @NonNull MediaCodec.BufferInfo info) {
                    Log.i("MFB", "onOutputBufferAvailable:" + index);
                    ByteBuffer outputBuffer = codec.getOutputBuffer(index);

                    if (info.flags == MediaCodec.BUFFER_FLAG_CODEC_CONFIG) {
                        info.size = 0;
                    }

                    if (info.size > 0) {

                        // adjust the ByteBuffer values to match BufferInfo (not needed?)
                        outputBuffer.position(info.offset);
                        outputBuffer.limit(info.offset + info.size);
                        // write encoded data to muxer(need to adjust presentationTimeUs.
                        info.presentationTimeUs = getPTSUs();

                        if (mEncoderCallback != null) {
                            //回调
                            mEncoderCallback.onEncodeOutput(H264_ENCODER, outputBuffer, info);
                        }
                        prevOutputPTSUs = info.presentationTimeUs;
                        if (mMuxer != null) {
                            if (!mMuxerStarted) {
                                throw new RuntimeException("muxer hasn't started");
                            }
                            mMuxer.writeSampleData(mTrackIndex, outputBuffer, info);
                        }

                    }
                    codec.releaseOutputBuffer(index, false);
                }

                @Override
                public void onError(@NonNull MediaCodec codec, @NonNull MediaCodec.CodecException e) {

                }

                @Override
                    /**
               * Called when the output format has changed
               *
               * @param codec The MediaCodec object.
               * @param format The new output format.
               */
                public void onOutputFormatChanged(@NonNull MediaCodec codec, @NonNull MediaFormat format) {
                    if (null != mEncoderCallback) {
                        mEncoderCallback.outputMediaFormatChanged(H264_ENCODER, format);
                    }
                    if (mMuxer != null) {
                        if (mMuxerStarted) {
                            throw new RuntimeException("format changed twice");
                        }
                        // now that we have the Magic Goodies, start the muxer
                        mTrackIndex = mMuxer.addTrack(format);
                        mMuxer.start();

                        mMuxerStarted = true;
                    }
                }
            });
            mMediaCodec.start();

        } catch (Exception e) {
            e.printStackTrace();
        }

    }

完整代码请看H264MediaCodecAsyncEncoder

MediaCodec 小结

MediaCodec 用来音视频的编解码工作(这个过程有的文章也称为硬解),通过MediaCodec.createEncoderByType(MIMETYPE_VIDEO_AVC)函数中的参数来创建音频或者视频的编码器,同理通过MediaCodec.createDecoderByType(MIMETYPE_VIDEO_AVC)创建音频或者视频的解码器。对于音视频编解码中需要的不同参数用MediaFormat来指定。

小结

本篇文章详细的对 MediaCodec 进行了分析,读者可根据博客对应 Demo 来进行实际操练。

放上 Demo 地址详细Demo

以上是关于Android MediaCodec硬件解码视频播放的主要内容,如果未能解决你的问题,请参考以下文章

Android 用MediaCodec实现视频硬解码

Android 音视频 - MediaCodec 编解码音视频

Android 音视频 - MediaCodec 编解码音视频

Android 音视频 - MediaCodec 编解码音视频

Android MediaCodec+OpenGL视频编解码实践笔记

Android MediaCodec+OpenGL视频编解码实践笔记