问题描述: 在 Android 中使用 MediaMuxer 和 MediaExtractor 对 PCM 流进行音视频合成时,导致最终生成的视频帧损坏。
解决方法: 问题的根本原因是 PCM 流的采样率和编码格式与视频的要求不匹配。为了解决这个问题,我们需要对 PCM 流进行适当的转换和编码。
以下是一个可能的解决方法,包含了代码示例:
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaCodecList;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.media.MediaMuxer;
import android.os.Build;
import android.util.Log;
import java.io.IOException;
import java.nio.ByteBuffer;
public class VideoEncoder {
private static final String TAG = "VideoEncoder";
private static final String OUTPUT_VIDEO_MIME_TYPE = "video/avc";
private static final int OUTPUT_VIDEO_BIT_RATE = 4000000;
private static final int OUTPUT_VIDEO_FRAME_RATE = 30;
private static final int OUTPUT_VIDEO_IFRAME_INTERVAL = 10;
public void encodePcmToVideo(String inputPcmPath, String outputVideoPath, int sampleRate, int channels, int bitRate) {
MediaExtractor pcmExtractor = new MediaExtractor();
MediaMuxer videoMuxer = null;
try {
pcmExtractor.setDataSource(inputPcmPath);
// 创建视频编码器
MediaCodec videoEncoder = createVideoEncoder();
// 创建视频参数
MediaFormat videoFormat = createVideoFormat(outputVideoPath);
// 创建视频复用器
videoMuxer = new MediaMuxer(outputVideoPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
// 添加视频轨道
int videoTrackIndex = videoMuxer.addTrack(videoFormat);
// 开始复用器
videoMuxer.start();
// 配置视频编码器
videoEncoder.configure(videoFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
videoEncoder.start();
// 读取 PCM 数据并进行编码
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
int sampleSize;
long presentationTimeUs = 0;
ByteBuffer[] encoderInputBuffers = videoEncoder.getInputBuffers();
ByteBuffer[] encoderOutputBuffers = videoEncoder.getOutputBuffers();
pcmExtractor.selectTrack(0);
while ((sampleSize = pcmExtractor.readSampleData(encoderInputBuffers[0], 0)) >= 0) {
long pts = pcmExtractor.getSampleTime();
if (pts == 0) {
pts = presentationTimeUs;
}
presentationTimeUs = pts;
// 将 PCM 数据传递给视频编码器
videoEncoder.queueInputBuffer(0, 0, sampleSize, pts, 0);
pcmExtractor.advance();
// 处理编码后的视频帧
int encoderStatus;
do {
encoderStatus = videoEncoder.dequeueOutputBuffer(bufferInfo, 0);
if (encoderStatus >= 0) {
ByteBuffer encodedData = encoderOutputBuffers[encoderStatus];
// 设置视频帧的时间戳和标志位
if (bufferInfo.presentationTimeUs != 0) {
bufferInfo.flags |= MediaCodec.BUFFER_FLAG_SYNC_FRAME;
}
// 将编码后的视频数据写入输出文件
videoMuxer.writeSampleData(videoTrackIndex, encodedData, bufferInfo);
videoEncoder.releaseOutputBuffer(encoderStatus, false);
} else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
// 更新输出格式
videoFormat = videoEncoder.getOutputFormat();
Log.d(TAG, "Video format changed: " + videoFormat);
}
} while (encoderStatus >= 0);
}
// 停止编码器和复用器
videoEncoder.stop();
videoEncoder.release();
videoMuxer.stop();
videoMuxer.release();
} catch (IOException e) {
e.printStackTrace();
} finally {
pcmExtractor.release();
}
}
private MediaCodec createVideoEncoder() throws IOException {
MediaCodec encoder;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
encoder = MediaCodec.createEncoderByType(OUTPUT_VIDEO_MIME_TYPE);
} else {
MediaCodecInfo codecInfo = selectCodec(OUTPUT_VIDEO_MIME_TYPE);
encoder = MediaCodec.createByCodecName(codecInfo.getName());
}
return encoder