• 媒体编解码器MediaCodec


    目录

    1.介绍MediaCodec类

    2.创建MediaCodec的方式

    3.MediaCodec流程

    (1)配置编码参数

    (2)创建编码器

    (3)创建混合器

    (4)开始编码 

    4.MediaCodec编码的工作方式

     5.MediaCodec状态周期图

     6.使用缓冲区的异步和同步处理

    7.录制纯视频demo


    1.介绍MediaCodec类


         

    final public class MediaCodec

    MediaCodec类可以访问底层媒体编解码框架(Stage或OpenMAX),即编解码组件。通常与MediaExtractor、MediaSync、MediaMuxer、Image、Surface和AudioTrack一起使用。它本身并不是Codec,它通过调用底层编解码组件获得了Codec的能力。

    2.创建MediaCodec的方式


    创建MediaCodec(按格式创建):

    • MediaCodec createDecoderByType(String type):创建编码器。
    • MediaCodec createEncoderByType(String type):创建解码器。
    • type:数据解析阶段的mimeType,如”video/avc“。

    创建MediaCodec(按Codec名字创建):

    • MediaCodec createByCodecName(String name)。
    • OMX.google.h264.decoder:软解码
    • OMX.MTK.VIDEO.DECODER.AVC:硬解码

    3.MediaCodec流程


    (1)配置编码参数

    视频类型的Mediaformat

    可以通过如下代码创建视频类型Mediaformat:

    MediaFormat videoFormat = MediaFormat.createVideoFormat(videoType, width, height);
    

    videoType常用的有两种:

    • MediaFormat.MIMETYPE_VIDEO_AVC(H.264)
    • MediaFormat.MIMETYPE_VIDEO_HEVC(H.265)

    以下配置是必须要指定的,否则会报错。

    1. MediaFormat format = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, width, height);
    2. //色彩空间
    3. format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
    4. MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar);
    5. //码率
    6. format.setInteger(MediaFormat.KEY_BIT_RATE,500_000);
    7. //帧率fps
    8. format.setInteger(MediaFormat.KEY_FRAME_RATE,20);
    9. //关键帧间隔
    10. format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL,2);

    音频类型的Mediaformat

    可以通过如下代码创建音频类型Mediaformat:

    MediaFormat.createAudioFormat(audioType, sampleRate, channelCount);
    
    • audioType:常用的是MediaFormat.MIMETYPE_AUDIO_AAC;
    • sampleRate:采样率
    • channelCount:声道数

    以下配置是必须要指定的,否则会报错。

    1. //音频比特率(码率)
    2. audioFormat.setInteger(MediaFormat.KEY_BIT_RATE, 96000);
    (2)创建编码器

    配置解码器或者编码器configure函数

    public void configure( @Nullable MediaFormat format,

    @Nullable Surface surface,
    @Nullable MediaCrypto crypto,

    @ConfigureFlag int flags) 

    函数的参数介绍:

    • MediaFormat format:输入数据的格式(解码器)或输出数据的所需格式(编码器)。传null等同于传递MediaFormat.MediaFormat作为空的MediaFormat。
    • Surface surface:指定Surface,用于解码器输出的渲染。如果编解码器不生成原始视频输出(例如,不是视频解码器)和/或想配置解码器输出ByteBuffer,则传null。
    • MediaCrypto crypto:指定一个crypto对象,用于对媒体数据进行安全解密。对于非安全的编解码器,传null。
    • int flags:当组件是编码器时,flags指定为常量CONFIGURE_FLAG_ENCODE。
       

    使用示例:

      1. mediaCodec=MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
      2. mediaCodec.configure(format,null,null,MediaCodec.CONFIGURE_FLAG_ENCODE);
      3. mediaCodec.start();
    (3)创建混合器

    创建混合器MediaMuxer函数

     public MediaMuxer(@NonNull String path, @Format int format)

    函数的参数介绍:

    • path:输出文件的路径
    • format:输出的格式

    示例代码:

    1. mMuxer=new MediaMuxer(path,
    2. MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4
    3. );
    4. mMuxer.setOrientationHint(degress);
    (4)开始编码 

    介绍MediaCodec编码用到的四个方法:
    1. dequeueInputBuffer

    返回用于填充有效数据的输入buffer的索引,如果当前没有可用的buffer,则返回-1。

    public final int dequeueInputBuffer(long timeoutUs)

    函数的参数介绍:

    • long timeoutUs:等待可用的输入buffer的时间。
    • 如果timeoutUs == 0,则立即返回。
    • 如果timeoutUs < 0,则无限期等待可用的输入buffer。
    • 如果timeoutUs > 0,则等待“timeoutUs”微秒。

    2. queueInputBuffer

    在指定索引处填充输入buffer后,使用queueInputBuffer将buffer提交给组件。


    public native final void queueInputBuffer(
        int index,
        int offset, int size, long presentationTimeUs, int flags)

    • int index:以前调用dequeueInputBuffer(long)返回的输入buffer的索引。
    • int offset:数据开始时输入buffer中的字节偏移量。
    • int size:有效输入数据的字节数。
    • long presentationTimeUs:此buffer的PTS(以微秒为单位)。
    • int flags:一个由BUFFER_FLAG_CODEC_CONFIG和BUFFER_FLAG_END_OF_STREAM标志组成的位掩码。虽然没有被禁止,但是大多数codec并不对输入buffer使用BUFFER_FLAG_KEY_FRAME标志。

    3. dequeueOutputBuffer

    从MediaCodec获取输出buffer。

        public final int dequeueOutputBuffer(
                @NonNull BufferInfo info, long timeoutUs) 

    • BufferInfo info:输出buffer的metadata。
    • long timeoutUs:含义同dequeueInputBuffer中的timeoutUs参数。
    • 返回值:已成功解码的输出buffer的索引或INFO_*常量之一(INFO_TRY_AGAIN_LATER, INFO_OUTPUT_FORMAT_CHANGED 或 INFO_OUTPUT_BUFFERS_CHANGED)。返回INFO_TRY_AGAIN_LATER而timeoutUs指定为了非负值,表示超时了。返回INFO_OUTPUT_FORMAT_CHANGED表示输出格式已更改,后续数据将遵循新格式。

    4. releaseOutputBuffer

    使用此方法将输出buffer返回给codec或将其渲染在输出surface。

    public void releaseOutputBuffer (int index, boolean render)

    • index:以前调用dequeueOutputBuffer(long)返回的输入buffer的索引。
    • boolean render:如果在配置codec时指定了一个有效的surface,则传递true会将此输出buffer在surface上渲染。一旦不再使用buffer,该surface将把buffer释放回codec。

    示例代码:

    1. public void queueEncode(byte[] buffer){
    2. if(!isRecording){
    3. Log.e("xxx","没开始录制");
    4. return;
    5. }
    6. Log.e("xxx","开始录制");
    7. mHandler.post(new Runnable() {
    8. @Override
    9. public void run() {
    10. //立即得到有效输入缓冲区
    11. int index=mediaCodec.dequeueInputBuffer(0);
    12. if(index>=0){
    13. ByteBuffer inputBuffer=mediaCodec.getInputBuffer(index);
    14. inputBuffer.clear();
    15. inputBuffer.put(buffer,0,buffer.length);
    16. //填充数据后再加入队列
    17. mediaCodec.queueInputBuffer(index,0,buffer.length,System.nanoTime()/1000,0);
    18. }
    19. while (true){
    20. //获取输出缓冲区(编码后的数据从缓冲区获取)
    21. MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
    22. int encoderStatus = mediaCodec.dequeueOutputBuffer(bufferInfo, 10_000);
    23. //稍后重试
    24. if(encoderStatus== MediaCodec.INFO_TRY_AGAIN_LATER){
    25. break;
    26. }else if(encoderStatus==MediaCodec.INFO_OUTPUT_FORMAT_CHANGED){
    27. //输出格式发生变化 第一次总会调用,所以在这里开启混合器
    28. MediaFormat newFormat=mediaCodec.getOutputFormat();
    29. videoTrack=mMuxer.addTrack(newFormat);
    30. mMuxer.start();
    31. }else if(encoderStatus==MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED){
    32. //可以忽略
    33. }else{
    34. //正常则encoderStatus 获取缓冲区下标
    35. ByteBuffer encodedData=mediaCodec.getOutputBuffer(encoderStatus);
    36. //如果当前的buffer是配置信息,不管它,不用写出去
    37. if((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG)!=0){
    38. bufferInfo.size=0;
    39. }
    40. if(bufferInfo.size!=0){
    41. //设置从哪里开始读数据(读出来就是编码后的数据)
    42. encodedData.position(bufferInfo.offset);
    43. //设置能读数据的总长度
    44. encodedData.limit(bufferInfo.offset+bufferInfo.size);
    45. //写出为MP4
    46. mMuxer.writeSampleData(videoTrack,encodedData,bufferInfo);
    47. }
    48. //释放这个缓冲区,后续可以存放新的编码后的数据
    49. mediaCodec.releaseOutputBuffer(encoderStatus,false);
    50. }
    51. }
    52. }
    53. });
    54. }

    4.MediaCodec编码的工作方式


    简单来说,可以看做是一个生产者-消费者模式,在MediaCodec里面有两个队列,一个是输入队列,一个是输出队列,数据放入输入队列,MediaCodec拿出数据进行编码,把编码完的数据放入输出队列。

     5.MediaCodec状态周期图


    在MediaCodec的生命周期内存在3种状态,即Stopped、Executing和Released。

    Stopped状态还可处于三个状态:Uninitialized、Configures和Error。

    Executing状态概念上的进展通过3个子状态进行:Flushed、Running和end-of-Stream。

    MediaCodec状态周期图如下图:

     6.使用缓冲区的异步和同步处理


    使用缓冲区的异步处理:

    在异步模式下,媒体编解码器通常像这样使用:

    1.  MediaCodec codec = MediaCodec.createByCodecName(name);
    2.  MediaFormat mOutputFormat; // member variable
    3.  codec.setCallback(new MediaCodec.Callback() {
    4.   @Override
    5.   void onInputBufferAvailable(MediaCodec mc, int inputBufferId) {
    6.     ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferId);
    7.     // fill inputBuffer with valid data
    8.     …
    9.     codec.queueInputBuffer(inputBufferId, …);
    10.   }
    11.  
    12.   @Override
    13.   void onOutputBufferAvailable(MediaCodec mc, int outputBufferId, …) {
    14.     ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);
    15.     MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A
    16.     // bufferFormat is equivalent to mOutputFormat
    17.     // outputBuffer is ready to be processed or rendered.
    18.     …
    19.     codec.releaseOutputBuffer(outputBufferId, …);
    20.   }
    21.  
    22.   @Override
    23.   void onOutputFormatChanged(MediaCodec mc, MediaFormat format) {
    24.     // Subsequent data will conform to new format.
    25.     // Can ignore if using getOutputFormat(outputBufferId)
    26.     mOutputFormat = format; // option B
    27.   }
    28.  
    29.   @Override
    30.   void onError(…) {
    31.     …
    32.   }
    33.   @Override
    34.   void onCryptoError(…) {
    35.     …
    36.   }
    37.  });
    38.  codec.configure(format, …);
    39.  mOutputFormat = codec.getOutputFormat(); // option B
    40.  codec.start();
    41.  // wait for processing to complete
    42.  codec.stop();
    43.  codec.release();

     使用缓冲区的同步处理:

    媒体编解码器在同步模式下通常这样使用:

    1. MediaCodec codec = MediaCodec.createByCodecName(name);
    2. codec.configure(format, …);
    3. MediaFormat outputFormat = codec.getOutputFormat(); // option B
    4. codec.start();
    5. for (;;) {
    6. int inputBufferId = codec.dequeueInputBuffer(timeoutUs);
    7. if (inputBufferId >= 0) {
    8. ByteBuffer inputBuffer = codec.getInputBuffer(…);
    9. // fill inputBuffer with valid data
    10. codec.queueInputBuffer(inputBufferId, …);
    11. }
    12. int outputBufferId = codec.dequeueOutputBuffer(…);
    13. if (outputBufferId >= 0) {
    14. ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);
    15. MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A
    16. // bufferFormat is identical to outputFormat
    17. // outputBuffer is ready to be processed or rendered.
    18. codec.releaseOutputBuffer(outputBufferId, …);
    19. } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
    20. // Subsequent data will conform to new format.
    21. // Can ignore if using getOutputFormat(outputBufferId)
    22. outputFormat = codec.getOutputFormat(); // option B
    23. }
    24. }
    25. codec.stop();
    26. codec.release();

    7.录制纯视频demo

    功能介绍:

    实现Camera1采集相机数据,设置预览画布TextureView显示采集的数据,然后MediaCode对图像数据编码,最后MediaMuxer把图像数据放入MP4盒子。

    运行图:

    这是我的项目代码:

    (小白一个,代码只是能简单地实现功能)

    https://github.com/Bookonpillow/mediacode

  • 相关阅读:
    向日葵远程分辨率过低解决办法
    查看Git用户名/密码/邮箱,及设置git配置
    numpy(2)
    想要精通算法和SQL的成长之路 - 最长序列问题
    华为机试 - 最大化控制资源成本
    Linux命令200例:Yum强大的包管理工具使用(常用)
    机器学习 —— PCA降维和交叉验证
    KNN分类算法
    2024年最新测评,6款好用的在线代码编辑器推荐
    尚好房 08_前端登录、注册与关注房源
  • 原文地址:https://blog.csdn.net/weixin_63357306/article/details/133382562