以下基于 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: 调整码率/请求关键帧
⚠️ 六、注意事项与调试
-
权限声明
ini<uses-permission android:name="android.permission.CAMERA"/> <uses-permission android:name="android.permission.RECORD_AUDIO"/> <uses-permission android:name="android.permission.INTERNET"/>
-
资源释放
scsspublic void release() { cameraDevice.close(); videoEncoder.stop(); videoEncoder.release(); rtmpClient.disconnect(); }
-
调试工具
- 推流测试 :VLC 播放
rtmp://server/live/stream
。 - 抓包分析:Wireshark 过滤 RTMP 协议。
- 推流测试 :VLC 播放
🔗 七、扩展功能
-
美颜滤镜
scss// 在OpenGL中处理纹理(参考 android-openGL-canvas) canvasGL.drawSurfaceTexture(texture, filter); // 应用滤镜
-
本地录制
ini// 复用FLV封装逻辑,写入本地文件 FileOutputStream fos = new FileOutputStream("record.flv"); fos.write(flvTag.array());
💎 总结
本方案完整实现了 Camera2 采集 → MediaCodec 硬编码 → FLV 封装 → RTMP 推流 全链路,关键技术点包括:
- Camera2 的
SessionConfiguration
配置与 YUV 处理; - MediaCodec 低延迟模式与动态码率;
- FLV 关键帧注入与时间戳同步;
- 基于 librtmp 的 JNI 推流优化。
推荐实践:
- 快速集成:使用开源库 rtmp-rtsp-stream-client-java 简化开发。