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
相关产品推荐
相关产品推荐

