安卓MediaCodec录像推流

以下基于 ​Android Camera2 API + MediaCodec + RTMP​ 的摄像头直播全链路实现方案,涵盖摄像头采集、YUV处理、硬编码、FLV封装、RTMP推流等核心模块,结合设备兼容性与性能优化策略,提供完整代码及技术解析。


📷 ​一、Camera2 初始化与图像采集

1. ​相机配置(使用 SessionConfiguration API)​

java 复制代码
private void openCamera(int width, int height) {
    CameraManager manager = (CameraManager) context.getSystemService(CAMERA_SERVICE);
    String cameraId = manager.getCameraIdList()[0];
    HandlerThread cameraThread = new HandlerThread("CameraBackground");
    cameraThread.start();
    Handler cameraHandler = new Handler(cameraThread.getLooper());

    manager.openCamera(cameraId, new CameraDevice.StateCallback() {
        @Override
        public void onOpened(@NonNull CameraDevice camera) {
            cameraDevice = camera;
            // 创建预览Surface
            SurfaceTexture texture = textureView.getSurfaceTexture();
            texture.setDefaultBufferSize(width, height);
            Surface previewSurface = new Surface(texture);
            
            // 创建ImageReader获取YUV数据(格式:YUV_420_888)
            ImageReader imageReader = ImageReader.newInstance(width, height, ImageFormat.YUV_420_888, 2);
            imageReader.setOnImageAvailableListener(imageListener, cameraHandler);

            // 配置OutputConfiguration
            List<OutputConfiguration> outputs = new ArrayList<>();
            outputs.add(new OutputConfiguration(previewSurface));
            outputs.add(new OutputConfiguration(imageReader.getSurface()));

            // 构建SessionConfiguration(API 28+)
            SessionConfiguration sessionConfig = new SessionConfiguration(
                SessionConfiguration.SESSION_REGULAR,
                outputs,
                new HandlerExecutor(cameraHandler), // 自定义Executor
                new CameraCaptureSession.StateCallback() {
                    @Override
                    public void onConfigured(@NonNull CameraCaptureSession session) {
                        // 创建预览请求
                        CaptureRequest.Builder builder = cameraDevice.createCaptureRequest(TEMPLATE_PREVIEW);
                        builder.addTarget(previewSurface);
                        session.setRepeatingRequest(builder.build(), null, cameraHandler);
                    }
                    @Override
                    public void onConfigureFailed(@NonNull CameraCaptureSession session) {
                        Log.e(TAG, "Session配置失败");
                    }
                }
            );
            cameraDevice.createCaptureSession(sessionConfig);
        }
    }, cameraHandler);
}

// 自定义Executor(避免主线程阻塞)
public class HandlerExecutor implements Executor {
    private final Handler handler;
    public HandlerExecutor(Handler handler) { this.handler = handler; }
    @Override public void execute(Runnable command) { handler.post(command); }
}

关键点​:

  • 使用 SessionConfiguration 替代过时的 createCaptureSession
  • ImageReader 获取原始 YUV 数据(格式 YUV_420_888)用于编码。

2. ​YUV 格式转换(NV21 → YUV420P)​

ini 复制代码
private byte[] convertYUV420888ToNV21(Image image) {
    ByteBuffer yBuffer = image.getPlanes()[0].getBuffer();
    ByteBuffer uBuffer = image.getPlanes()[1].getBuffer();
    ByteBuffer vBuffer = image.getPlanes()[2].getBuffer();
    
    int ySize = yBuffer.remaining();
    int uvSize = uBuffer.remaining() + vBuffer.remaining();
    byte[] nv21 = new byte[ySize + uvSize];
    
    // 复制Y分量
    yBuffer.get(nv21, 0, ySize);
    
    // 交错排列UV分量(NV21格式:YYYY... + VUVU...)
    for (int i = 0; i < uvSize / 2; i++) {
        nv21[ySize + 2*i] = vBuffer.get(); // V
        nv21[ySize + 2*i + 1] = uBuffer.get(); // U
    }
    return nv21;
}

注意​:部分设备需调整 UV 顺序(绿屏问题)。


🔧 ​二、MediaCodec 硬编码配置

1. ​视频编码器初始化(H.264)​

ini 复制代码
MediaCodec videoEncoder = MediaCodec.createEncoderByType("video/avc");
MediaFormat format = MediaFormat.createVideoFormat("video/avc", width, height);
format.setInteger(MediaFormat.KEY_BIT_RATE, 2_000_000); // 码率
format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);      // 帧率
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1); // 关键帧间隔(秒)
videoEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
videoEncoder.start();

2. ​音频编码器初始化(AAC)​

ini 复制代码
MediaCodec audioEncoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC);
MediaFormat audioFormat = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, 44100, 1);
audioFormat.setInteger(MediaFormat.KEY_BIT_RATE, 64_000);
audioFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
audioEncoder.configure(audioFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
audioEncoder.start();

性能优化​:

  • 启用低延迟模式(API 29+):format.setInteger(MediaFormat.KEY_LATENCY, 1)
  • 动态码率调整:根据网络反馈实时修改 KEY_BIT_RATE

🔁 ​三、FLV 封装与 RTMP 推流

1. ​FLV 视频标签封装

scss 复制代码
private void sendVideoFrame(ByteBuffer data, MediaCodec.BufferInfo info) {
    byte[] frameData = new byte[info.size];
    data.get(frameData);

    // 构造FLV Tag头
    ByteBuffer flvTag = ByteBuffer.allocate(15 + frameData.length);
    flvTag.put((byte) 0x09); // 视频类型
    flvTag.put(new byte[3]);  // 数据长度占位符
    flvTag.put(new byte[] {   // 时间戳(毫秒)
        (byte) (info.presentationTimeUs >>> 16),
        (byte) (info.presentationTimeUs >>> 8),
        (byte) info.presentationTimeUs
    });
    flvTag.put((byte) 0);    // StreamID

    // 关键帧处理(拼接SPS/PPS)
    if ((info.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0) {
        byte[] avcHeader = createAVCDecoderConfigurationRecord(spsPps);
        flvTag.put(avcHeader);
    }
    flvTag.put(frameData);

    // 回填数据长度
    flvTag.putInt(1, flvTag.position() - 11);
    rtmpClient.sendVideoData(flvTag.array());
}

// 生成AVC序列头(包含SPS/PPS)
private byte[] createAVCDecoderConfigurationRecord(byte[] spsPps) {
    ByteBuffer config = ByteBuffer.allocate(spsPps.length + 16);
    config.put((byte) 0x17); // FrameType:关键帧 | CodecID:AVC
    config.put((byte) 0x01); // AVC NALU
    config.put(new byte[3]);  // CompositionTime
    config.put(spsPps);
    return config.array();
}

2. ​RTMP 客户端实现(基于 librtmp JNI)​

java 复制代码
public class RtmpClient {
    static { System.loadLibrary("rtmp-jni"); }
    public native int connect(String url);
    public native int sendVideoData(byte[] data);
    public native void disconnect();

    public void startStream(String rtmpUrl) {
        new Thread(() -> {
            if (connect(rtmpUrl) == 0) {
                Log.d(TAG, "RTMP连接成功");
            }
        }).start();
    }
}

协议细节​:

  • 时间戳单位转换:MediaCodec 输出微秒 → FLV 需毫秒。
  • SPS/PPS 必须在关键帧前发送,否则播放器无法解码。

⚙️ ​四、全链路整合与优化

1. ​音视频同步策略

ini 复制代码
// 统一时间基准(System.nanoTime())
long baseTime = System.nanoTime();

// 音频时间戳计算(AAC)
long audioPts = 1_000_000L * samplesWritten / sampleRate;

// 视频时间戳对齐
long videoPts = (System.nanoTime() - baseTime) / 1000;

2. ​设备兼容性处理

less 复制代码
// 检查编码器支持的YUV格式
MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
for (MediaCodecInfo info : codecList.getCodecInfos()) {
    if (info.isEncoder() && info.getName().contains("h264")) {
        int[] colorFormats = info.getCapabilitiesForType("video/avc").colorFormats;
        // 优先选择COLOR_FormatSurface或COLOR_FormatYUV420Flexible
    }
}

3. ​抗弱网策略

  • 动态码率:根据 RTMP 发送队列积压情况调整码率。
  • 关键帧请求 :网络抖动时主动调用 videoEncoder.createSyncFrame()

📐 ​五、全链路时序图

sequenceDiagram participant Camera as Camera2 participant Encoder as MediaCodec participant Muxer as FLV封装 participant RTMP as 推流服务器 Camera->>Encoder: YUV数据 (ImageReader回调) Encoder->>Muxer: H.264 NAL单元 Muxer->>RTMP: FLV Tag (含时间戳) RTMP-->>Muxer: 网络延迟反馈 Muxer->>Encoder: 调整码率/请求关键帧

⚠️ ​六、注意事项与调试

  1. 权限声明

    ini 复制代码
    <uses-permission android:name="android.permission.CAMERA"/>
    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
    <uses-permission android:name="android.permission.INTERNET"/>
  2. 资源释放

    scss 复制代码
    public void release() {
        cameraDevice.close();
        videoEncoder.stop();
        videoEncoder.release();
        rtmpClient.disconnect();
    }
  3. 调试工具

    • 推流测试 :VLC 播放 rtmp://server/live/stream
    • 抓包分析:Wireshark 过滤 RTMP 协议。

🔗 ​七、扩展功能

  1. 美颜滤镜

    scss 复制代码
    // 在OpenGL中处理纹理(参考 android-openGL-canvas)
    canvasGL.drawSurfaceTexture(texture, filter); // 应用滤镜
  2. 本地录制

    ini 复制代码
    // 复用FLV封装逻辑,写入本地文件
    FileOutputStream fos = new FileOutputStream("record.flv");
    fos.write(flvTag.array());

💎 ​总结

本方案完整实现了 ​Camera2 采集 → MediaCodec 硬编码 → FLV 封装 → RTMP 推流​ 全链路,关键技术点包括:

  1. Camera2 的 SessionConfiguration 配置与 YUV 处理;
  2. MediaCodec 低延迟模式与动态码率;
  3. FLV 关键帧注入与时间戳同步;
  4. 基于 librtmp 的 JNI 推流优化。

推荐实践​:

相关推荐
阿巴斯甜5 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker6 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq95277 小时前
Andorid Google 登录接入文档
android
黄林晴8 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab21 小时前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿1 天前
Android MediaPlayer 笔记
android
Jony_1 天前
Android 启动优化方案
android
阿巴斯甜1 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇1 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_1 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android