You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android MediaCodec双质量录像实现的性能问题与求助

解决Android双质量4K分段录像的稳定性问题(Camera2 + MediaCodec)

核心解决方案

1. 规避多Surface兼容性问题:OpenGL纹理流转方案

放弃直接给Camera CaptureRequest添加多个Surface的方案,改用Camera输出到OpenGL纹理Surface,通过OpenGL将帧数据分别渲染到高低质量编码器的Surface。这种方式绕开了部分设备对多Surface CaptureRequest的硬件限制,同时实现高效的帧流转。

2. 解决Codec内存不足问题

  • Codec实例池化:避免每次分段都销毁重建编码器/解码器,仅在首次初始化或分辨率变更时创建实例,分段切换时仅调用flush()重置状态。
  • 分段资源强制清理:分段结束时,先处理完Codec所有待输出Buffer,再停止并释放MediaMuxer,最后触发GC回收闲置内存。
  • 限制Buffer大小:4K场景下,给编码器设置KEY_MAX_INPUT_SIZE为合理值(例如4K YUV帧大小的1.5倍),避免系统分配过大内存。

3. 修复4K场景下的码率与分段稳定性问题

  • 手动设置HQ码率:通过MediaFormat.KEY_BIT_RATE将HQ编码器码率设为20-30Mbps(4K 30fps的合理范围),避免系统自动降码导致画质过低。
  • 同步分段切换时机:分段切换时,等待Codec输出完所有帧(监听BUFFER_FLAG_END_OF_STREAM)再关闭当前Muxer,避免冻结帧。
  • 解码器重置机制:每次分段开始时,重新配置解码器的输入格式,解决第一段后无输出的问题。

核心实现代码

1. OpenGL纹理流转核心(替代多Surface输入)

// 初始化Camera输出的OpenGL纹理Surface
private Surface initCameraSurface(int textureId, int hqWidth, int hqHeight) {
    SurfaceTexture surfaceTexture = new SurfaceTexture(textureId);
    surfaceTexture.setDefaultBufferSize(hqWidth, hqHeight);
    return new Surface(surfaceTexture);
}

// OpenGL渲染线程中,将Camera纹理分别渲染到HQ/LQ编码器Surface
@Override
public void onDrawFrame(GL10 gl) {
    // 更新Camera捕获的纹理帧
    mCameraSurfaceTexture.updateTexImage();
    mCameraSurfaceTexture.getTransformMatrix(mTransformMatrix);

    // 渲染到HQ编码器Surface
    mEglCore.makeCurrent(mHqEncoderSurface);
    glUniformMatrix4fv(mMatrixLoc, 1, false, mTransformMatrix, 0);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    mEglCore.swapBuffers(mHqEncoderSurface);

    // 缩放后渲染到LQ编码器Surface
    mEglCore.makeCurrent(mLqEncoderSurface);
    glUniformMatrix4fv(mMatrixLoc, 1, false, mScaleTransformMatrix, 0);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    mEglCore.swapBuffers(mLqEncoderSurface);
}

2. Codec池化与分段资源管理

// 获取复用的HQ编码器实例
private MediaCodec getHqEncoder(int width, int height) throws IOException {
    if (mHqEncoder == null) {
        MediaFormat format = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, width, height);
        format.setInteger(MediaFormat.KEY_BIT_RATE, 25 * 1024 * 1024); // 25Mbps
        format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
        format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 2);
        format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, (3840 * 2160 * 3) / 2); // 4K YUV420帧大小
        mHqEncoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
        mHqEncoder.configure(format, mHqEncoderSurface, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
        mHqEncoder.start();
    }
    return mHqEncoder;
}

// 结束当前分段时的资源清理流程
private void finishSegment() {
    // 处理编码器剩余输出数据
    drainCodec(mHqEncoder, mHqMuxer, true);
    drainCodec(mLqEncoder, mLqMuxer, true);

    // 释放当前分段的Muxer
    mHqMuxer.stop();
    mHqMuxer.release();
    mLqMuxer.stop();
    mLqMuxer.release();

    // 重置编码器状态(复用实例)
    mHqEncoder.flush();
    mLqEncoder.flush();

    // 触发GC回收闲置内存
    System.gc();
}

// 通用Codec数据处理方法
private boolean drainCodec(MediaCodec codec, MediaMuxer muxer, boolean endOfStream) {
    if (endOfStream) {
        codec.signalEndOfInputStream();
    }
    MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
    while (true) {
        int outputBufferId = codec.dequeueOutputBuffer(bufferInfo, 1000);
        if (outputBufferId == MediaCodec.INFO_TRY_AGAIN_LATER) {
            return !endOfStream;
        } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
            // 初始化Muxer轨道
            int trackId = muxer.addTrack(codec.getOutputFormat());
            muxer.start();
            mTrackIdMap.put(codec, trackId);
        } else if (outputBufferId < 0) {
            continue;
        }

        ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);
        if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
            bufferInfo.size = 0;
        }
        if (bufferInfo.size > 0) {
            muxer.writeSampleData(mTrackIdMap.get(codec), outputBuffer, bufferInfo);
        }
        codec.releaseOutputBuffer(outputBufferId, false);

        if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
            return false;
        }
    }
}

3. 4K解码器持续输出修复

// 每次分段开始时重置LQ解码器
private void restartLqDecoder() throws IOException {
    if (mLqDecoder != null) {
        mLqDecoder.stop();
        mLqDecoder.release();
    }
    // 复用HQ编码器的输出格式初始化解码器
    MediaFormat inputFormat = mHqEncoder.getOutputFormat();
    mLqDecoder = MediaCodec.createDecoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
    mLqDecoder.configure(inputFormat, mLqDecoderSurface, null, 0);
    mLqDecoder.start();
}

内容的提问来源于stack exchange,提问作者Avaneesh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 13:04:48