Android 使用MediaMuxer+MediaCodec编码MP4视频同步方案
异步编码实现方案
-
核心改进
- 异步架构设计:
- 使用两个独立的线程池:mEncoderExecutor 处理帧输入,mDrainExecutor 处理编码输出
- 使用 LinkedBlockingQueue<byte[]> 作为帧数据队列,实现生产者-消费者模式
- 使用 AtomicBoolean 控制异步任务的启停
- 回调机制:
- 新增 AsyncEncoderCallback 接口,提供 onFrameEncoded()、onEncodingError()、onEncodingCompleted() 回调
- 支持异步通知编码状态和错误处理
- 非阻塞操作:
- 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;
}
}