Android 硬件编码 MediaCodec

MediaCodec 的整体流程如上图所示,从input 输入数据,从output 输出数据,编码的时候输入的是原始数据,输出的是编码后的数据。

初始化

configure 编码器的时候要将flag 设置为encode。

java 复制代码
public void startEncode(MediaFormat format) {
    mFormat = format;
    final String mimeType = format.getString(MediaFormat.KEY_MIME);

    // Check to see if this is actually a video mime type. If it is, then create
    // a codec that can decode this mime type.
    try {
        mCodec = MediaCodec.createEncoderByType(mimeType);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
    mCodec.configure(format,null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
    mCodec.start();
}

写数据

将原始数据送给 MediaCodec。

java 复制代码
public void sendFrame(byte[] input, long pts, int flags) {
    int size = input.length;
    int inputBufferId = mCodec.dequeueInputBuffer(timeOut);
    if (inputBufferId >= 0) {
        // fill inputBuffers[inputBufferId] with valid data
        ByteBuffer buffer = mCodec.getInputBuffer(inputBufferId);
        buffer.put(input);
        mCodec.queueInputBuffer(inputBufferId, 0, size, pts, flags);
    }
}

取数据

从 MediaCodec 中取出编码后的数据,这里要注意返回的outputBufferId,会有不同的处理。

java 复制代码
public VEAVPacket receivePacket() {
    VEAVPacket packet = new VEAVPacket();
    int outputBufferId = mCodec.dequeueOutputBuffer(mBufferInfo, timeOut);
    if (outputBufferId == MediaCodec.INFO_TRY_AGAIN_LATER) {
        Log.d(TAG, "INFO_TRY_AGAIN_LATER");
        return packet;
    } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
        // Subsequent data will conform to new format.
        packet.format = mCodec.getOutputFormat();
        return packet;
    } else if (outputBufferId < 0) {
        Log.d(TAG, "receivePacket:" + outputBufferId);
        return packet;
    }
    // outputBuffers[outputBufferId] is ready to be processed or rendered.
    ByteBuffer outputBuffer = mCodec.getOutputBuffer(outputBufferId);
    if (outputBuffer == null) {
        return packet;
    }
    byte[] outData = new byte[mBufferInfo.size];
    outputBuffer.get(outData);
    packet.buffer = ByteBuffer.wrap(outData);
    packet.bufferInfo = mBufferInfo;
    mCodec.releaseOutputBuffer(outputBufferId, timeOut);

    return packet;
}

结束

java 复制代码
public void stopEncode() {
  mCodec.stop();
  mCodec.release();
}

注意事项

封装的时候可能提前需要获取 sps 和 pps,然而编码器接收到一定数据才会吐出这些信息,因此,我们可以先送入一些空数据,将这个信息给挤出来。

java 复制代码
public MediaFormat getOutputFormat() {
    if (mOutFormat != null) {
        return mOutFormat;
    }
    byte [] data = new byte[1];

    long time = System.currentTimeMillis();
    sendFrame(data, time, 0);

    VEAVPacket videoPacket = receivePacket();
    if (videoPacket.format != null) {
        mOutFormat = videoPacket.format;
    }
    return mOutFormat;
}
相关推荐
计蒙不吃鱼8 小时前
一篇文章实现Android图片拼接并保存至相册
android·java·前端
LucianaiB9 小时前
如何做好一份优秀的技术文档:专业指南与最佳实践
android·java·数据库
duwei_wang14 小时前
[Android]-Admob配置过多导致的慢消息
android
雨白15 小时前
发送自定义广播
android
雨白16 小时前
深入理解广播机制 (BroadcastReceiver)
android
婵鸣空啼19 小时前
GD图像处理与SESSiON
android
sunly_20 小时前
Flutter:导航固定背景图,滚动时导航颜色渐变
android·javascript·flutter
用户20187928316721 小时前
简单了解android.permission.MEDIA_CONTENT_CONTROL权限
android
_一条咸鱼_21 小时前
Android Runtime类卸载条件与资源回收策略(29)
android·面试·android jetpack
顾林海21 小时前
Android Bitmap治理全解析:从加载优化到泄漏防控的全生命周期管理
android·面试·性能优化