Android 使用MediaMuxer+MediaCodec编码MP4视频异步方案

Android 使用MediaMuxer+MediaCodec编码MP4视频同步方案
异步编码实现方案

  • 核心改进

    1. 异步架构设计:
      • 使用两个独立的线程池:mEncoderExecutor 处理帧输入,mDrainExecutor 处理编码输出
      • 使用 LinkedBlockingQueue<byte[]> 作为帧数据队列,实现生产者-消费者模式
      • 使用 AtomicBoolean 控制异步任务的启停
    2. 回调机制:
      • 新增 AsyncEncoderCallback 接口,提供 onFrameEncoded()、onEncodingError()、onEncodingCompleted() 回调
      • 支持异步通知编码状态和错误处理
    3. 非阻塞操作:
      • feedFrame() 方法现在只是将帧数据加入队列,立即返回,不阻塞主线程
      • 编码和输出处理都在后台线程中异步执行
  • 性能优势

    • 主线程不阻塞:feedFrame() 调用立即返回,不会因为编码处理而卡顿
    • 并发处理:帧输入和编码输出可以并行进行,提高整体吞吐量
    • 内存管理:队列机制可以平滑处理帧率波动,避免内存溢出

完整方案:

java 复制代码
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import android.media.MediaMuxer;
import android.util.Log;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;

public class YuvToMp4Encoder {
    private static final String TAG = "YuvToMp4Encoder";
    private static final String MIME_TYPE = "video/avc"; // H.264编码
    private static final int TIMEOUT_USEC = 10000; // 10ms超时

    // 编码器状态
    public enum EncoderState {
        IDLE, PREPARED, ENCODING, STOPPED
    }

    // 异步编码回调接口
    public interface AsyncEncoderCallback {
        void onFrameEncoded();
        void onEncodingError(Exception error);
        void onEncodingCompleted();
    }

    private MediaCodec mMediaCodec;
    private MediaMuxer mMediaMuxer;
    private int mTrackIndex;
    private boolean mMuxerStarted;
    private EncoderState mState = EncoderState.IDLE;
    private int mWidth;
    private int mHeight;
    private int mFrameRate;
    private int mBitRate;
    private long mPresentationTimeUs;
    private long mFirstFrameTime = -1;
    private int duration = 0;
    private int conunt;

    // 异步编码相关
    private ExecutorService mEncoderExecutor;
    private ExecutorService mDrainExecutor;
    private LinkedBlockingQueue<byte[]> mFrameQueue;
    private AtomicBoolean mIsEncoding = new AtomicBoolean(false);
    private AsyncEncoderCallback mCallback;

    /**
     * 初始化编码器
     * @param width 视频宽度
     * @param height 视频高度
     * @param frameRate 帧率
     * @param bitRate 比特率 (bps)
     */
    public YuvToMp4Encoder(int width, int height, int frameRate, int bitRate) {
        this.mWidth = width;
        this.mHeight = height;
        this.mFrameRate = frameRate;
        this.mBitRate = bitRate;
        duration = (int) (1000*1000 * 1.0f / frameRate);
        
        // 初始化异步编码组件
        mEncoderExecutor = Executors.newSingleThreadExecutor();
        mDrainExecutor = Executors.newSingleThreadExecutor();
        mFrameQueue = new LinkedBlockingQueue<>();
        mIsEncoding.set(false);
    }

    /**
     * 准备编码器
     * @param outputPath 输出文件路径
     * @throws IOException 初始化异常
     */
    public void prepare(String outputPath) throws IOException {
        if (mState != EncoderState.IDLE && mState != EncoderState.STOPPED) {
            throw new IllegalStateException("Encoder must be in IDLE state");
        }

        try {
            // 1. 创建并配置MediaCodec
            mMediaCodec = MediaCodec.createEncoderByType(MIME_TYPE);
            MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, mWidth, mHeight);
            format.setInteger(MediaFormat.KEY_BIT_RATE, mBitRate);
            format.setInteger(MediaFormat.KEY_FRAME_RATE, mFrameRate);
            format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
                    MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedPlanar);
            format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1); // 关键帧间隔(秒)

            mMediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
            mMediaCodec.start();

            // 2. 创建MediaMuxer
            mMediaMuxer = new MediaMuxer(outputPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
            mTrackIndex = -1;
            mMuxerStarted = false;

            mState = EncoderState.PREPARED;
            mPresentationTimeUs = 0;
            Log.i(TAG, "Encoder prepared successfully");
        } catch (Exception e) {
            e.printStackTrace();
            release();
        }
    }

    long startTime = 0;
    /**
     * 开始编码
     */
    public void start() {
        start(null);
    }

    /**
     * 开始异步编码
     * @param callback 编码回调
     */
    public void start(AsyncEncoderCallback callback) {
        if (mState != EncoderState.PREPARED) {
            throw new IllegalStateException("Encoder must be prepared before starting");
        }
        mState = EncoderState.ENCODING;
        mFirstFrameTime = -1;
        startTime = System.nanoTime();
        conunt = 0;
        mCallback = callback;
        mIsEncoding.set(true);
        
        // 启动异步编码任务
        startAsyncEncoding();
        Log.i(TAG, "Async encoder started");
    }

    /**
     * 异步输入一帧YUV420数据
     * @param yuvData YUV420字节数组
     */
    public void feedFrame(byte[] yuvData) {
        if (mState != EncoderState.ENCODING || !mIsEncoding.get()) {
            Log.w(TAG, "Ignoring frame - encoder not in encoding state");
            return;
        }

        // 将帧数据加入队列,由后台线程处理
        try {
            mFrameQueue.offer(yuvData);
        } catch (Exception e) {
            Log.e(TAG, "Failed to queue frame", e);
            if (mCallback != null) {
                mCallback.onEncodingError(e);
            }
        }
    }

    /**
     * 启动异步编码任务
     */
    private void startAsyncEncoding() {
        // 启动帧处理任务
        mEncoderExecutor.execute(new Runnable() {
            @Override
            public void run() {
                processFrames();
            }
        });
        
        // 启动输出处理任务
        mDrainExecutor.execute(new Runnable() {
            @Override
            public void run() {
                processOutput();
            }
        });
    }

    /**
     * 处理帧队列中的帧数据
     */
    private void processFrames() {
        while (mIsEncoding.get() || !mFrameQueue.isEmpty()) {
            try {
                byte[] yuvData = mFrameQueue.poll();
                if (yuvData == null) {
                    Thread.sleep(1); // 短暂休眠避免CPU占用过高
                    continue;
                }
                
                conunt++;
                encodeFrame(yuvData);
                
            } catch (Exception e) {
                Log.e(TAG, "Error processing frame", e);
                if (mCallback != null) {
                    mCallback.onEncodingError(e);
                }
            }
        }
    }

    /**
     * 编码单帧数据
     */
    private void encodeFrame(byte[] yuvData) {
        try {
            // 1. 获取输入缓冲区
            int inputBufferIndex = mMediaCodec.dequeueInputBuffer(TIMEOUT_USEC);
            if (inputBufferIndex < 0) {
                Log.w(TAG, "No available input buffer, skipping frame");
                return;
            }

            // 2. 填充YUV数据
            ByteBuffer inputBuffer = mMediaCodec.getInputBuffer(inputBufferIndex);
            if (inputBuffer != null) {
                inputBuffer.clear();
                inputBuffer.put(yuvData);

                // 3. 计算PTS (呈现时间戳)
                long presentationTimeUs = conunt * duration;

                // 4. 提交到编码器
                mMediaCodec.queueInputBuffer(
                        inputBufferIndex,
                        0,
                        yuvData.length,
                        presentationTimeUs,
                        0
                );
            }
        } catch (Exception e) {
            Log.e(TAG, "Error encoding frame", e);
            if (mCallback != null) {
                mCallback.onEncodingError(e);
            }
        }
    }

    /**
     * 处理编码器输出
     */
    private void processOutput() {
        while (mIsEncoding.get() || mState == EncoderState.ENCODING) {
            try {
                drainEncoder(false);
                Thread.sleep(1); // 短暂休眠避免CPU占用过高
            } catch (Exception e) {
                Log.e(TAG, "Error processing output", e);
                if (mCallback != null) {
                    mCallback.onEncodingError(e);
                }
            }
        }
    }

    /**
     * 停止编码并释放资源
     */
    public void stop() {
        if (mState == EncoderState.STOPPED || mState == EncoderState.IDLE) {
            return;
        }

        try {
            // 停止异步编码
            mIsEncoding.set(false);
            
            if (mState == EncoderState.ENCODING) {
                // 发送结束信号
                drainEncoder(true);

                // 停止编码器
                if (mMediaCodec != null) {
                    mMediaCodec.stop();
                }
            }

            // 停止混合器
            if (mMediaMuxer != null && mMuxerStarted) {
                mMediaMuxer.stop();
            }

            Log.i(TAG, "Async encoder stopped successfully");
            
            // 通知回调
            if (mCallback != null) {
                mCallback.onEncodingCompleted();
            }
        } catch (Exception e) {
           e.printStackTrace();
           if (mCallback != null) {
               mCallback.onEncodingError(e);
           }
        } finally {
            release();
            mState = EncoderState.STOPPED;
        }
    }

    // 释放所有资源
    private void release() {
        try {
            // 停止异步任务
            mIsEncoding.set(false);
            
            // 关闭线程池
            if (mEncoderExecutor != null) {
                mEncoderExecutor.shutdown();
                mEncoderExecutor = null;
            }
            if (mDrainExecutor != null) {
                mDrainExecutor.shutdown();
                mDrainExecutor = null;
            }
            
            // 清空队列
            if (mFrameQueue != null) {
                mFrameQueue.clear();
            }
            
            // 释放编码器资源
            if (mMediaCodec != null) {
                mMediaCodec.release();
                mMediaCodec = null;
            }
            if (mMediaMuxer != null) {
                mMediaMuxer.release();
                mMediaMuxer = null;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        mMuxerStarted = false;
        mTrackIndex = -1;
    }

    // 处理编码器输出
    private void drainEncoder(boolean endOfStream) {
        if (endOfStream) {
            // 发送结束信号
            int inputBufferIndex = mMediaCodec.dequeueInputBuffer(TIMEOUT_USEC);
            if (inputBufferIndex >= 0) {
                mMediaCodec.queueInputBuffer(
                        inputBufferIndex, 0, 0, 0,
                        MediaCodec.BUFFER_FLAG_END_OF_STREAM
                );
            }
        }

        // 处理所有可用输出
        while (true) {
            MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
            int outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC);

            if (outputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
                if (!endOfStream) break;
            } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                // 格式改变时设置混合器
                if (mMuxerStarted) {
                    throw new RuntimeException("Format changed after muxer started");
                }
                MediaFormat newFormat = mMediaCodec.getOutputFormat();
                mTrackIndex = mMediaMuxer.addTrack(newFormat);
                mMediaMuxer.start();
                mMuxerStarted = true;
            } else if (outputBufferIndex < 0) {
//                Log.w(TAG, "Unexpected result from dequeueOutputBuffer: " + outputBufferIndex);
            } else {
                ByteBuffer outputBuffer = mMediaCodec.getOutputBuffer(outputBufferIndex);
                if (outputBuffer == null) {
                    Log.e(TAG, "Output buffer was null");
                } else {
                    // 写入混合器
                    if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0 && bufferInfo.size > 0) {
                        if (mMuxerStarted) {
                            outputBuffer.position(bufferInfo.offset);
                            outputBuffer.limit(bufferInfo.offset + bufferInfo.size);
                            mMediaMuxer.writeSampleData(mTrackIndex, outputBuffer, bufferInfo);
                        }
                    }

                    // 检查结束标志
                    if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                        break;
                    }
                }
                mMediaCodec.releaseOutputBuffer(outputBufferIndex, false);
                
                // 通知帧编码完成
                if (mCallback != null && !endOfStream) {
                    mCallback.onFrameEncoded();
                }
            }
        }
    }

    /**
     * 获取当前编码器状态
     */
    public EncoderState getState() {
        return mState;
    }
}
相关推荐
cccccc语言我来了2 小时前
深入理解 Linux(7) 命令与动态库:从文件操作到程序链接的实践指南
android·linux·运维
程序员卷卷狗3 小时前
MySQL 慢查询优化:从定位、分析到索引调优的完整流程
android·mysql·adb
写点啥呢3 小时前
Android Studio 多语言助手插件:让多语言管理变得简单高效
android·ai·ai编程·多语言
泥嚎泥嚎5 小时前
【Android】给App添加启动画面——SplashScreen
android·java
全栈派森5 小时前
初见 Dart:这门新语言如何让你的 App「动」起来?
android·flutter·ios
q***98525 小时前
图文详述:MySQL的下载、安装、配置、使用
android·mysql·adb
恋猫de小郭6 小时前
Dart 3.10 发布,快来看有什么更新吧
android·前端·flutter
EasyCVR6 小时前
视频汇聚平台EasyCVR:构建通信基站“可视、可管、可控”的智慧安防体系
服务器·数据库·音视频
恋猫de小郭7 小时前
Flutter 3.38 发布,快来看看有什么更新吧
android·前端·flutter