蛙仔老窦 發表於 2025-9-12 08:35:19

Android利用MediaCodec组件实现音视频编解码功能

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li>概述</li><li>MediaCodec架构简介</li><ul class="second_class_ul"><li>基本工作原理</li><li>状态管理</li></ul><li>视频解码实现</li><ul class="second_class_ul"><li>创建和配置解码器</li><li>异步解码处理</li></ul><li>视频编码实现</li><ul class="second_class_ul"><li>编码器初始化</li><li>编码数据处理</li></ul><li>音频编解码</li><ul class="second_class_ul"><li>音频解码示例</li><li>音频编码示例</li></ul><li>性能优化策略</li><ul class="second_class_ul"><li>1. 缓冲区管理</li><li>2. 线程优化</li><li>3. 内存管理</li></ul><li>错误处理与调试</li><ul class="second_class_ul"><li>常见错误类型</li><li>调试技巧</li></ul><li>最佳实践</li><ul class="second_class_ul"><li>1. 选择合适的编解码器</li><li>2. 配置参数优化</li><li>3. 同步和时间戳管理</li></ul><li>总结</li><ul class="second_class_ul"></ul></ul></div><p class="maodian"></p><h2>概述</h2>
<p>Android MediaCodec是Android平台提供的底层音视频编解码API,它为开发者提供了直接访问设备硬件编解码器的能力。通过MediaCodec,我们可以高效地处理音频和视频数据的编码与解码操作,实现高性能的多媒体应用。</p>
<p>本文将深入探讨MediaCodec的核心概念、使用方法以及在实际开发中的最佳实践。</p>
<p class="maodian"></p><h2>MediaCodec架构简介</h2>
<p class="maodian"></p><h3>基本工作原理</h3>
<p>MediaCodec采用异步的、基于缓冲区的处理模式。其核心架构包括:</p>
<ul><li><strong>输入缓冲区队列(Input Buffer Queue)</strong>:存放待处理的原始数据</li><li><strong>输出缓冲区队列(Output Buffer Queue)</strong>:存放处理后的数据</li><li><strong>编解码引擎</strong>:执行实际的编解码操作</li><li><strong>回调机制</strong>:通知应用程序缓冲区状态变化</li></ul>
<p class="maodian"></p><h3>状态管理</h3>
<p>MediaCodec具有明确的状态机制:</p>
<blockquote><p>Uninitialized &rarr; Configured &rarr; Executing &rarr; Released<br />&nbsp; &nbsp; &nbsp;&darr; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&darr; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&darr;<br />&nbsp; &nbsp;Error &larr;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;</p></blockquote>
<p class="maodian"></p><h2>视频解码实现</h2>
<p class="maodian"></p><h3>创建和配置解码器</h3>
<div class="jb51code"><pre class="brush:java;">public class VideoDecoder {
    private MediaCodec decoder;
    private Surface surface;
   
    public void initDecoder(String mimeType, int width, int height, Surface outputSurface) {
      try {
            // 创建解码器实例
            decoder = MediaCodec.createDecoderByType(mimeType);
            
            // 配置MediaFormat
            MediaFormat format = MediaFormat.createVideoFormat(mimeType, width, height);
            
            // 设置输出Surface
            this.surface = outputSurface;
            
            // 配置解码器
            decoder.configure(format, surface, null, 0);
            decoder.start();
            
      } catch (IOException e) {
            e.printStackTrace();
      }
    }
}
</pre></div>
<p class="maodian"></p><h3>异步解码处理</h3>
<div class="jb51code"><pre class="brush:java;">public void startDecoding() {
    decoder.setCallback(new MediaCodec.Callback() {
      @Override
      public void onInputBufferAvailable(MediaCodec codec, int index) {
            // 处理输入缓冲区
            ByteBuffer inputBuffer = codec.getInputBuffer(index);
            
            // 从数据源读取数据到inputBuffer
            int sampleSize = readSampleData(inputBuffer);
            
            if (sampleSize &gt; 0) {
                // 提交输入数据
                codec.queueInputBuffer(index, 0, sampleSize,
                  getCurrentTimestamp(), 0);
            } else {
                // 数据结束
                codec.queueInputBuffer(index, 0, 0, 0,
                  MediaCodec.BUFFER_FLAG_END_OF_STREAM);
            }
      }
      
      @Override
      public void onOutputBufferAvailable(MediaCodec codec, int index,
                MediaCodec.BufferInfo info) {
            // 处理输出缓冲区
            if ((info.flags &amp; MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                // 解码完成
                handleDecodingComplete();
            }
            
            // 释放输出缓冲区(渲染到Surface)
            codec.releaseOutputBuffer(index, true);
      }
      
      @Override
      public void onError(MediaCodec codec, MediaCodec.CodecException e) {
            // 错误处理
            handleError(e);
      }
      
      @Override
      public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
            // 输出格式变化处理
            handleFormatChange(format);
      }
    });
}
</pre></div>
<p class="maodian"></p><h2>视频编码实现</h2>
<p class="maodian"></p><h3>编码器初始化</h3>
<div class="jb51code"><pre class="brush:java;">public class VideoEncoder {
    private MediaCodec encoder;
    private Surface inputSurface;
    private MediaMuxer muxer;
   
    public void initEncoder(String outputPath, int width, int height, int bitRate) {
      try {
            // 创建编码器
            encoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
            
            // 配置编码参数
            MediaFormat format = MediaFormat.createVideoFormat(
                MediaFormat.MIMETYPE_VIDEO_AVC, width, height);
            format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
                MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
            format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
            format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
            format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 2);
            
            // 配置编码器
            encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
            
            // 获取输入Surface
            inputSurface = encoder.createInputSurface();
            
            // 创建MediaMuxer用于输出
            muxer = new MediaMuxer(outputPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
            
            encoder.start();
            
      } catch (IOException e) {
            e.printStackTrace();
      }
    }
}
</pre></div>
<p class="maodian"></p><h3>编码数据处理</h3>
<div class="jb51code"><pre class="brush:java;">private int videoTrackIndex = -1;
private boolean muxerStarted = false;

public void startEncoding() {
    encoder.setCallback(new MediaCodec.Callback() {
      @Override
      public void onInputBufferAvailable(MediaCodec codec, int index) {
            // 对于Surface输入,这个回调通常不使用
      }
      
      @Override
      public void onOutputBufferAvailable(MediaCodec codec, int index,
                MediaCodec.BufferInfo info) {
            ByteBuffer outputBuffer = codec.getOutputBuffer(index);
            
            if ((info.flags &amp; MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
                // 配置数据,通常是SPS/PPS
                info.size = 0;
            }
            
            if (info.size &gt; 0) {
                if (!muxerStarted) {
                  throw new RuntimeException("Muxer hasn't started");
                }
               
                // 调整ByteBuffer位置
                outputBuffer.position(info.offset);
                outputBuffer.limit(info.offset + info.size);
               
                // 写入媒体数据
                muxer.writeSampleData(videoTrackIndex, outputBuffer, info);
            }
            
            // 释放输出缓冲区
            codec.releaseOutputBuffer(index, false);
            
            if ((info.flags &amp; MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                // 编码完成
                handleEncodingComplete();
            }
      }
      
      @Override
      public void onError(MediaCodec codec, MediaCodec.CodecException e) {
            handleError(e);
      }
      
      @Override
      public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
            if (muxerStarted) {
                throw new RuntimeException("Format changed twice");
            }
            
            // 添加轨道到muxer
            videoTrackIndex = muxer.addTrack(format);
            muxer.start();
            muxerStarted = true;
      }
    });
}
</pre></div>
<p class="maodian"></p><h2>音频编解码</h2>
<p class="maodian"></p><h3>音频解码示例</h3>
<div class="jb51code"><pre class="brush:java;">public class AudioDecoder {
    private MediaCodec audioDecoder;
    private MediaExtractor extractor;
   
    public void initAudioDecoder(String filePath) {
      try {
            extractor = new MediaExtractor();
            extractor.setDataSource(filePath);
            
            // 找到音频轨道
            int audioTrack = selectAudioTrack(extractor);
            if (audioTrack &gt;= 0) {
                extractor.selectTrack(audioTrack);
                MediaFormat format = extractor.getTrackFormat(audioTrack);
               
                String mimeType = format.getString(MediaFormat.KEY_MIME);
                audioDecoder = MediaCodec.createDecoderByType(mimeType);
                audioDecoder.configure(format, null, null, 0);
                audioDecoder.start();
            }
      } catch (IOException e) {
            e.printStackTrace();
      }
    }
   
    private int selectAudioTrack(MediaExtractor extractor) {
      int trackCount = extractor.getTrackCount();
      for (int i = 0; i &lt; trackCount; i++) {
            MediaFormat format = extractor.getTrackFormat(i);
            String mimeType = format.getString(MediaFormat.KEY_MIME);
            if (mimeType.startsWith("audio/")) {
                return i;
            }
      }
      return -1;
    }
}
</pre></div>
<p class="maodian"></p><h3>音频编码示例</h3>
<div class="jb51code"><pre class="brush:java;">public void initAudioEncoder(int sampleRate, int channelCount, int bitRate) {
    try {
      MediaFormat format = MediaFormat.createAudioFormat(
            MediaFormat.MIMETYPE_AUDIO_AAC, sampleRate, channelCount);
      format.setInteger(MediaFormat.KEY_AAC_PROFILE,
            MediaCodecInfo.CodecProfileLevel.AACObjectLC);
      format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
      
      encoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC);
      encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
      encoder.start();
      
    } catch (IOException e) {
      e.printStackTrace();
    }
}
</pre></div>
<p class="maodian"></p><h2>性能优化策略</h2>
<p class="maodian"></p><h3>1. 缓冲区管理</h3>
<div class="jb51code"><pre class="brush:java;">// 合理设置缓冲区大小
private static final int BUFFER_SIZE = 64 * 1024; // 64KB

// 重用ByteBuffer避免频繁分配
private ByteBuffer reusableBuffer = ByteBuffer.allocate(BUFFER_SIZE);
</pre></div>
<p class="maodian"></p><h3>2. 线程优化</h3>
<div class="jb51code"><pre class="brush:java;">// 使用专用线程处理编解码
private HandlerThread codecThread;
private Handler codecHandler;

private void initCodecThread() {
    codecThread = new HandlerThread("CodecThread");
    codecThread.start();
    codecHandler = new Handler(codecThread.getLooper());
}
</pre></div>
<p class="maodian"></p><h3>3. 内存管理</h3>
<div class="jb51code"><pre class="brush:java;">// 及时释放资源
private void releaseResources() {
    if (decoder != null) {
      decoder.stop();
      decoder.release();
      decoder = null;
    }
   
    if (surface != null) {
      surface.release();
      surface = null;
    }
}
</pre></div>
<p class="maodian"></p><h2>错误处理与调试</h2>
<p class="maodian"></p><h3>常见错误类型</h3>
<div class="jb51code"><pre class="brush:java;">private void handleCodecError(MediaCodec.CodecException e) {
    if (e.isTransient()) {
      // 暂时性错误,可以重试
      Log.w(TAG, "Transient codec error", e);
      retryOperation();
    } else if (e.isRecoverable()) {
      // 可恢复错误,重新配置编解码器
      Log.w(TAG, "Recoverable codec error", e);
      reconfigureCodec();
    } else {
      // 致命错误,需要完全重建
      Log.e(TAG, "Fatal codec error", e);
      recreateCodec();
    }
}
</pre></div>
<p class="maodian"></p><h3>调试技巧</h3>
<ul><li><strong>日志记录</strong>:详细记录缓冲区状态和时间戳</li><li><strong>性能监控</strong>:监控帧率、码率和延迟</li><li><strong>内存检查</strong>:使用Profiler检查内存泄漏</li></ul>
<p class="maodian"></p><h2>最佳实践</h2>
<p class="maodian"></p><h3>1. 选择合适的编解码器</h3>
<div class="jb51code"><pre class="brush:java;">// 检查硬件编解码器支持
private boolean isHardwareAccelerated(String codecName) {
    return !codecName.startsWith("OMX.google.");
}

// 优先选择硬件编解码器
private MediaCodec createOptimalDecoder(String mimeType) {
    MediaCodecList codecList = new MediaCodecList(MediaCodecList.ALL_CODECS);
    for (MediaCodecInfo codecInfo : codecList.getCodecInfos()) {
      if (codecInfo.isEncoder()) continue;
      
      String[] types = codecInfo.getSupportedTypes();
      for (String type : types) {
            if (type.equals(mimeType) &amp;&amp; isHardwareAccelerated(codecInfo.getName())) {
                try {
                  return MediaCodec.createByCodecName(codecInfo.getName());
                } catch (IOException e) {
                  continue;
                }
            }
      }
    }
   
    // 回退到默认编解码器
    try {
      return MediaCodec.createDecoderByType(mimeType);
    } catch (IOException e) {
      return null;
    }
}
</pre></div>
<p class="maodian"></p><h3>2. 配置参数优化</h3>
<div class="jb51code"><pre class="brush:java;">private MediaFormat createOptimalVideoFormat(int width, int height, int bitRate) {
    MediaFormat format = MediaFormat.createVideoFormat(
      MediaFormat.MIMETYPE_VIDEO_AVC, width, height);
   
    // 设置关键参数
    format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
    format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
    format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 2);
   
    // 设置编码质量
    format.setInteger(MediaFormat.KEY_BITRATE_MODE,
      MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR);
   
    // 设置颜色格式
    format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
      MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
   
    return format;
}
</pre></div>
<p class="maodian"></p><h3>3. 同步和时间戳管理</h3>
<div class="jb51code"><pre class="brush:java;">private long generatePresentationTime() {
    return System.nanoTime() / 1000; // 微秒时间戳
}

private void adjustTimestamp(MediaCodec.BufferInfo info, long baseTime) {
    info.presentationTimeUs = info.presentationTimeUs - baseTime;
}
</pre></div>
<p class="maodian"></p><h2>总结</h2>
<p>Android MediaCodec是一个强大而灵活的音视频编解码框架,通过合理使用其API可以实现高性能的多媒体应用。关键要点包括:</p>
<ul><li><strong>理解异步处理模式</strong>:正确处理回调和缓冲区管理</li><li><strong>优化性能</strong>:选择硬件编解码器,合理配置参数</li><li><strong>错误处理</strong>:实现健壮的错误恢复机制</li><li><strong>资源管理</strong>:及时释放编解码器和相关资源</li><li><strong>线程安全</strong>:在合适的线程中执行编解码操作</li></ul>
<p>通过遵循这些最佳实践,开发者可以充分发挥MediaCodec的能力,构建稳定、高效的音视频应用。</p>
<p>以上就是Android利用MediaCodec组件实现音视频编解码功能的详细内容,更多关于Android音视频解码的资料请关注琼殿技术社区其它相关文章!</p>
                           
                            <div class="art_xg">
                              <b>您可能感兴趣的文章:</b><ul><li>Android使用OpenGL和MediaCodec录制功能</li><li>Android音视频开发只硬件解码组件MediaCodec讲解</li><li>Android硬件解码组件MediaCodec使用教程</li><li>Android音视频开发之MediaCodec的使用教程</li><li>Android开发MediaCodec和lamemp3多段音频截取拼接</li><li>Android使用MediaCodec将摄像头采集的视频编码为h264</li><li>Android使用FFmpeg实现视频解码的全流程指南</li><li>Android&nbsp;FFmpeg音视频解码播放示例详解</li></ul>
                            </div>

                        </div>
                        <!--endmain-->
頁: [1]
查看完整版本: Android利用MediaCodec组件实现音视频编解码功能