安卓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 推流优化。

推荐实践​:

相关推荐
2501_9151063232 分钟前
TF 上架全流程实战,从构建到 TestFlight 分发
android·ios·小程序·https·uni-app·iphone·webview
阳光明媚sunny1 小时前
OOM内存溢出产生原因和避免方法
android
whysqwhw3 小时前
安卓MediaCodec图片声音合成视频
android
Chesnut.3 小时前
【2025.08.06最新版】Android Studio下载、安装及配置记录(自动下载sdk)
android·java
fatiaozhang95274 小时前
咪咕MGV3200-KLH_GK6323V100C_板号E503744_安卓9_短接强刷包-可救砖
android·网络·电视盒子·刷机固件·机顶盒刷机
东风西巷4 小时前
My APK 安卓版:高效管理手机应用的工具软件
android·智能手机·软件需求
教程分享大师12 小时前
中兴B860AV5.2-U_S905L3SB安卓9.0系统带root权限当贝纯净版线刷包
android
_小马快跑_12 小时前
Android | LiveData 与 Flow 的异同点对比
android
whysqwhw15 小时前
安卓MediaCodec录像功能
android