博客
关于我
Android MediaMuxer+MediaCodec 编码yuv数据成mp4
阅读量:128 次
发布时间:2019-02-26

本文共 8808 字,大约阅读时间需要 29 分钟。

使用 MediaCodec 和 MediaMuxer 编码 AVC 视频并混合到 MP4 文件

一、简介

使用 MediaCodec 对 YUV 数据进行编码,输出格式为 H.264(AVC)。随后,使用 MediaMuxer 将视频轨道和音频轨道混合到 MP4 容器中。视频编码通常使用 H.264(AVC),音频编码通常使用 AAC。

二、流程分析

1. 创建编码器并配置

首先,创建一个 MediaCodec 实例,并配置其编码参数。以下是详细的配置步骤:

MediaFormat mediaFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, videoWidth, videoHeight);mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, width * height * 6);mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 30);mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);encoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);encoder.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);encoder.start();
  • MediaFormat 创建:使用 MediaFormat.createVideoFormat 方法创建视频格式,指定 MIME 媒体类型、宽度和高度。
  • 颜色格式设置:设置编码器的颜色格式为 YUV420Flexible,适用于大多数移动设备。
  • 比特率设置:比特率计算为宽度乘以高度乘以 6,确保视频质量。
  • 帧率设置:设置帧率为 30 帧每秒。
  • 关键帧间隔:设置 I 帧间隔为 1 秒,确保视频流的稳定性。

2. 编码一帧数据

在编码过程中,需要将 YUV 数据提交给编码器,并获取编码后的输出数据:

private void encode(byte[] yuv, long presentationTimeUs) {    int inputBufferIndex = mEncoder.dequeueInputBuffer(DEFAULT_TIMEOUT_US);    if (inputBufferIndex == -1) {        return;    }    ByteBuffer inputBuffer = mEncoder.getInputBuffer(inputBufferIndex);    inputBuffer.put(yuv);    mEncoder.queueInputBuffer(inputBufferIndex, 0, yuv.length, presentationTimeUs, 0);    while (!mStop) {        MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();        int outputBufferIndex = mEncoder.dequeueOutputBuffer(bufferInfo, DEFAULT_TIMEOUT_US);        if (outputBufferIndex >= 0) {            ByteBuffer outputBuffer = mEncoder.getOutputBuffer(outputBufferIndex);            if (mVideoTrackIndex == -1) {                mVideoTrackIndex = writeHeadInfo(outputBuffer, bufferInfo);            }            mMediaMuxer.writeSampleData(mVideoTrackIndex, outputBuffer, bufferInfo);            mEncoder.releaseOutputBuffer(outputBufferIndex, false);            break;        }    }}

3. MediaMuxer 写入数据

在编码后,需要将编码数据写入 MediaMuxer 中。需要注意的是,H.264 视频编码需要配置一些编解码参数(csd),否则可能会导致编码错误。以下是写入头部信息的示例:

private int writeHeadInfo(ByteBuffer outputBuffer, MediaCodec.BufferInfo bufferInfo) {    byte[] csd = new byte[bufferInfo.size];    outputBuffer.limit(bufferInfo.offset + bufferInfo.size);    outputBuffer.position(bufferInfo.offset);    outputBuffer.get(csd);    ByteBuffer sps = null;    ByteBuffer pps = null;    for (int i = bufferInfo.size - 1; i > 3; i--) {        if (csd[i] == 1 && csd[i - 1] == 0 && csd[i - 2] == 0 && csd[i - 3] == 0) {            sps = ByteBuffer.allocate(i - 3);            pps = ByteBuffer.allocate(bufferInfo.size - (i - 3));            sps.put(csd, 0, i - 3).position(0);            pps.put(csd, i - 3, bufferInfo.size - (i - 3)).position(0);        }    }    MediaFormat outputFormat = mEncoder.getOutputFormat();    if (sps != null && pps != null) {        outputFormat.setByteBuffer("csd-0", sps);        outputFormat.setByteBuffer("csd-1", pps);    }    int videoTrackIndex = mMediaMuxer.addTrack(outputFormat);    mMediaMuxer.start();    return videoTrackIndex;}

4. 结束释放资源

在编码完成后,需要正确释放编码器和 MediaMuxer

mEncoder.stop();mEncoder.release();mMediaMuxer.release();

三、完整代码

以下是完整的编码类代码:

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;public class VideoEncoder {    private static final String TAG = "VideoEncoder";    private final static String MIME_TYPE = MediaFormat.MIMETYPE_VIDEO_AVC;    private static final long DEFAULT_TIMEOUT_US = 10000;    private MediaCodec mEncoder;    private MediaMuxer mMediaMuxer;    private int mVideoTrackIndex;    private boolean mStop = false;    public void init(String outPath, int width, int height) throws IOException {        mStop = false;        mVideoTrackIndex = -1;        mMediaMuxer = new MediaMuxer(outPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);        mEncoder = MediaCodec.createEncoderByType(MIME_TYPE);        MediaFormat mediaFormat = MediaFormat.createVideoFormat(MIME_TYPE, width, height);        mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);        mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, width * height * 6);        mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 30);        mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);        mEncoder.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);        mEncoder.start();    }    public void release() {        mStop = true;        if (mEncoder != null) {            mEncoder.stop();            mEncoder.release();            mEncoder = null;        }        if (mMediaMuxer != null) {            mMediaMuxer.release();            mMediaMuxer = null;        }    }    public void encode(byte[] yuv, long presentationTimeUs) {        if (mEncoder == null || mMediaMuxer == null) {            Log.e(TAG, "mEncoder or mMediaMuxer is null");            return;        }        if (yuv == null) {            Log.e(TAG, "input yuv data is null");            return;        }        int inputBufferIndex = mEncoder.dequeueInputBuffer(DEFAULT_TIMEOUT_US);        if (inputBufferIndex == -1) {            Log.e(TAG, "no valid buffer available");            return;        }        ByteBuffer inputBuffer = mEncoder.getInputBuffer(inputBufferIndex);        inputBuffer.put(yuv);        mEncoder.queueInputBuffer(inputBufferIndex, 0, yuv.length, presentationTimeUs, 0);        while (!mStop) {            MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();            int outputBufferIndex = mEncoder.dequeueOutputBuffer(bufferInfo, DEFAULT_TIMEOUT_US);            if (outputBufferIndex >= 0) {                ByteBuffer outputBuffer = mEncoder.getOutputBuffer(outputBufferIndex);                if (mVideoTrackIndex == -1) {                    mVideoTrackIndex = writeHeadInfo(outputBuffer, bufferInfo);                }                if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) {                    mMediaMuxer.writeSampleData(mVideoTrackIndex, outputBuffer, bufferInfo);                }                mEncoder.releaseOutputBuffer(outputBufferIndex, false);                break;            }        }    }    private int writeHeadInfo(ByteBuffer outputBuffer, MediaCodec.BufferInfo bufferInfo) {        byte[] csd = new byte[bufferInfo.size];        outputBuffer.limit(bufferInfo.offset + bufferInfo.size);        outputBuffer.position(bufferInfo.offset);        outputBuffer.get(csd);        ByteBuffer sps = null;        ByteBuffer pps = null;        for (int i = bufferInfo.size - 1; i > 3; i--) {            if (csd[i] == 1 && csd[i - 1] == 0 && csd[i - 2] == 0 && csd[i - 3] == 0) {                sps = ByteBuffer.allocate(i - 3);                pps = ByteBuffer.allocate(bufferInfo.size - (i - 3));                sps.put(csd, 0, i - 3).position(0);                pps.put(csd, i - 3, bufferInfo.size - (i - 3)).position(0);            }        }        MediaFormat outputFormat = mEncoder.getOutputFormat();        if (sps != null && pps != null) {            outputFormat.setByteBuffer("csd-0", sps);            outputFormat.setByteBuffer("csd-1", pps);        }        int videoTrackIndex = mMediaMuxer.addTrack(outputFormat);        mMediaMuxer.start();        return videoTrackIndex;    }}

四、调用示例

以下是一个使用该编码器的示例:

VideoDecoder mVideoDecoder = new VideoDecoder();mVideoDecoder.setOutputFormat(VideoDecoder.COLOR_FORMAT_NV12);VideoEncoder mVideoEncoder = null;new Thread(new Runnable() {    @Override    public void run() {        mVideoDecoder.decode("/sdcard/test.mp4", new VideoDecoder.DecodeCallback() {            @Override            public void onDecode(byte[] yuv, int width, int height, int frameCount, long presentationTimeUs) {                if (mVideoEncoder == null) {                    mVideoEncoder = new VideoEncoder();                    mVideoEncoder.init("/sdcard/test_out.mp4", width, height);                }                mVideoEncoder.encode(yuv, presentationTimeUs);            }            @Override            public void onFinish() {                if (mVideoEncoder != null) {                    mVideoEncoder.release();                }            }            @Override            public void onStop() {                if (mVideoEncoder != null) {                    mVideoEncoder.release();                }            }        });    }}).start();

五、注意事项

  • 编码性能:YUV 数据的处理速度是关键,确保编码器能够按时处理输入数据。
  • 音频支持:除了视频编码,可以选择将 AAC 音频添加到 MediaMuxer 中,完成音频编码和混合。
  • 多线程处理:可以考虑在另一个线程中执行 MediaMuxer 的写入操作,以避免 UI 阻塞。

通过以上流程,可以实现将 YUV 数据编码为 H.264 格式,并将其与音频混合到 MP4 文件中。这对于实现视频编码和传输的需求非常有用。

转载地址:http://xvru.baihongyu.com/

你可能感兴趣的文章
Node实现小爬虫
查看>>
Node提示:error code Z_BUF_ERROR,error error -5,error zlib:unexpected end of file
查看>>
Node提示:npm does not support Node.js v12.16.3
查看>>
Node搭建静态资源服务器时后缀名与响应头映射关系的Json文件
查看>>
Node服务在断开SSH后停止运行解决方案(创建守护进程)
查看>>
node模块化
查看>>
node模块的本质
查看>>
node环境下使用import引入外部文件出错
查看>>
node环境:Error listen EADDRINUSE :::3000
查看>>
Node的Web应用框架Express的简介与搭建HelloWorld
查看>>
Node第一天
查看>>
node编译程序内存溢出
查看>>
Node读取并输出txt文件内容
查看>>
node防xss攻击插件
查看>>
noi 1996 登山
查看>>
noi 7827 质数的和与积
查看>>
NOI-1.3-11-计算浮点数相除的余数
查看>>
noi.ac #36 模拟
查看>>
NOI2010 海拔(平面图最大流)
查看>>
NOIp2005 过河
查看>>