mediacodec压缩成h264数据_基于Camera、AudioRecord 、MediaCodec 和 MediaMuxer 录制 MP4

一.前言

在 AAC 音频编码保存和解码播放 和 Camera 视频采集,H264 编码保存两篇文章中介绍了如何通过 AudioRecord 和 MediaCodec 录制 AAC 音频以及如何通过 Camera 和 MediaCodec 录制 H264 视频。本文将介绍如何通过 MediaMuxer 合成 MP4 文件。

MP4

在 音视频开发基础概念 中有介绍过,MP4 (或者称 MPEG-4) 是一种标准的数字多媒体容器格式,可以存储 音频数据和视频数据。对于视频格式,常见的是 H264 和 H265; 对于音频格式通常是 AAC 。

MediaMuxer

MediaMuxer 是 Android 用来产生一个混合音频和视频多媒体文件的 API ,只支持下面几种格式。

public static final inMUXER_OUTPUT_3GPP = 2;public static final inMUXER_OUTPUT_HEIF = 3;public static final inMUXER_OUTPUT_MPEG_4 = 0;public static final inMUXER_OUTPUT_OGG = 4;public static final inMUXER_OUTPUT_WEBM = 1;

1. 初始化

mMediaMuxer = new MediaMuxer(path, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);

path 表示 MP4 文件的输出路径

2. 添加音频轨和视频轨

if (type == AAC_ENCODER) {
        mAudioTrackIndex = mMediaMuxer.addTrack(mediaFormat);}if (type == H264_ENCODER) {
        mVideoTrackIndex = mMediaMuxer.addTrack(mediaFormat);}

传入 MediaFormat 对象从 MediaCodec 中获取。

3. 开始合成

mMediaMuxer.start();

4. 写入数据

mMediaMuxer.writeSampleData(avData.trackIndex, avData.byteBuffer, avData.bufferInfo);

5. 停止并释放资源

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

二. 录制 MP4

AudioTrack、Camera、MediaCodec 和 MediaMuxer 录制 MP4 流程如下图所示:6b960b32f08d48acb8d12bfe0c57fdbd.png

1. 音频录制

音频录制使用 AudioRecord

public class AudioRecorder {
        private int mAudioSource;    private int mSampleRateInHz;    private int mChannelConfig;    private int mAudioFormat;    private int mBufferSizeInBytes;    private AudioRecord mAudioRecord;    private volatile boolean mIsRecording;    private Callback mCallback;    private byte[] mBuffer;    public void setCallback(Callback callback) {
            mCallback = callback;    }    public interface Callback {
            void onAudioOutput(byte[] data);    }    public AudioRecorder(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes) {
            mAudioSource = audioSource;        mSampleRateInHz = sampleRateInHz;        mChannelConfig = channelConfig;        mAudioFormat = audioFormat;        mBufferSizeInBytes = bufferSizeInBytes;        mAudioRecord = new AudioRecord(audioSource, sampleRateInHz, channelConfig, audioFormat, bufferSizeInBytes);        mIsRecording = false;        int minBufferSize = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, AudioFormat.ENCODING_PCM_16BIT);        mBuffer = new byte[Math.min(2048, minBufferSize)];    }    public void start() {
            if (mIsRecording) {
                return;        }        new Thread(new Runnable() {
                @Override            public void run() {
                    onStart();            }        }).start();    }    public void onStart() {
            if (mAudioRecord == null) {
                mAudioRecord = new android.media.AudioRecord(mAudioSource, mSampleRateInHz, mChannelConfig, mAudioFormat, mBufferSizeInBytes);        }        mAudioRecord.startRecording();        mIsRecording = true;        while (mIsRecording) {
                int len = mAudioRecord.read(mBuffer, 0, mBuffer.length);            if (len > 0) {
                    if (mCallback != null) {
                        mCallback.onAudioOutput(mBuffer);                }            }        }        mAudioRecord.stop();        mAudioRecord.release();        mAudioRecord = null;    }    public void stop() {
            mIsRecording = false;    }}

2. 音频编码

音频编码使用阻塞队列 BlockingQueue 来缓冲数据,编码成 AAC 格式。

public class AacEncoder {
        public static final int AAC_ENCODER = 2;    private MediaCodec mAudioEncoder;    private MediaFormat mMediaFormat;    private BlockingQueue mDataQueue;    private volatile boolean mIsEncoding;    private Callback mCallback;    private static final String AUDIO_MIME_TYPE = "audio/mp4a-latm";//就是 aac    public void setCallback(Callback callback) {
            mCallback = callback;    }    public interface Callback {
            void outputMediaFormat(int type, MediaFormat mediaFormat);        void onEncodeOutput(ByteBuffer byteBuffer, MediaCodec.BufferInfo bufferInfo);        void onStop(int type);    }    public AacEncoder(int sampleRateInHz, int channelConfig, int bufferSizeInBytes) {
            try {
                mAudioEncoder = MediaCodec.createEncoderByType(AUDIO_MIME_TYPE);            mMediaFormat = MediaFormat.createAudioFormat(AUDIO_MIME_TYPE, sampleRateInHz, channelConfig == AudioFormat.CHANNEL_OUT_MONO ? 1 : 2);            mMediaFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);            mMediaFormat.setInteger(MediaFormat.KEY_CHANNEL_MASK, AudioFormat.CHANNEL_IN_STEREO);//CHANNEL_IN_STEREO 立体声            int bitRate = sampleRateInHz * 16 * channelConfig == AudioFormat.CHANNEL_IN_MONO ? 1 : 2;            mMediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);            mMediaFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, channelConfig == AudioFormat.CHANNEL_IN_MONO ? 1 : 2);            mMediaFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, sampleRateInHz);            mAudioEncoder.configure(mMediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);        } catch (IOException e) {
            }        mDataQueue = new ArrayBlockingQueue<>(10);        mIsEncoding = false;    }    public void start() {
            if (mIsEncoding) {
                return;        }        new Thread(new Runnable() {
                @Override            public void run() {
                    onStart();            }        }).start();    }    public void stop() {
            mIsEncoding = false;    }    private void onStart() {
            mIsEncoding = true;        mAudioEncoder.start();        byte[] pcmData;        int inputIndex;        ByteBuffer inputBuffer;        ByteBuffer[] inputBuffers = mAudioEncoder.getInputBuffers();        int outputIndex;        ByteBuffer outputBuffer;        ByteBuffer[] outputBuffers = mAudioEncoder.getOutputBuffers();        MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();        while (mIsEncoding || !mDataQueue.isEmpty()) {
                pcmData = dequeueData();            if (pcmData == null) {
                    cont
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
是的,您可以使用Android的MediaProjectionManager API来获取屏幕捕获的权限,然后使用MediaCodec API将捕获的屏幕数据编码为H.264格式。具体步骤如下: 1. 创建一个MediaProjectionManager对象并请求屏幕捕获的权限: ``` MediaProjectionManager mediaProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE); Intent permissionIntent = mediaProjectionManager.createScreenCaptureIntent(); startActivityForResult(permissionIntent, REQUEST_CODE); ``` 2. 在onActivityResult()方法中获取MediaProjection对象: ``` @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == REQUEST_CODE) { if (resultCode != Activity.RESULT_OK) { // 用户拒绝了屏幕共享请求 return; } // 获取MediaProjection对象 MediaProjection mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, data); } } ``` 3. 创建一个MediaCodec对象并配置编码器: ``` MediaFormat format = MediaFormat.createVideoFormat("video/avc", width, height); format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate); format.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate); format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, iFrameInterval); MediaCodec codec = MediaCodec.createEncoderByType("video/avc"); codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); codec.start(); ``` 4. 在屏幕捕获回调中获取屏幕数据,并将数据编码后写入到输出流: ``` class ScreenCaptureCallback extends MediaProjection.Callback { private Surface surface; private MediaCodec codec; public ScreenCaptureCallback(Surface surface, MediaCodec codec) { this.surface = surface; this.codec = codec; } @Override public void onStop() { codec.stop(); codec.release(); } @Override public void onScreenCaptureStarted(MediaProjection projection) { // 创建一个虚拟屏幕Surface DisplayManager displayManager = (DisplayManager) getSystemService(Context.DISPLAY_SERVICE); Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY); VirtualDisplay virtualDisplay = mediaProjection.createVirtualDisplay( "ScreenCapture", width, height, dpi, DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY, surface, null, null); // 开始屏幕捕获 codec.setCallback(new MediaCodec.Callback() { @Override public void onInputBufferAvailable(MediaCodec codec, int index) { // do nothing } @Override public void onOutputBufferAvailable(MediaCodec codec, int index, MediaCodec.BufferInfo info) { ByteBuffer buffer = codec.getOutputBuffer(index); // 将编码后的数据写入到输出流 outputStream.write(buffer.array(), info.offset, info.size); codec.releaseOutputBuffer(index, false); } @Override public void onError(MediaCodec codec, MediaCodec.CodecException e) { // do nothing } @Override public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) { // do nothing } }); codec.getInputBuffers(); } } ``` 注意:需要在AndroidManifest.xml文件中添加录屏权限: ``` <uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL" /> <uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT" /> ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值