如何基于Android现有相机预览byte[]实现局域网设备间RTSP流传输
我完全懂你的困扰——那些现成的RTSP库总是要重新初始化相机预览,直接把你已经搞定的运动检测和视频录制逻辑给绕过去了。其实咱们完全可以基于现有的预览byte[]数据来实现RTSP流传输,核心就是把原始YUV数据编码成H.264,再通过RTSP/RTP协议推出去,全程不用碰相机的重新初始化。下面是具体的实现思路和关键代码:
1. 先明确基础前提
相机返回的byte[]默认是NV21格式的YUV数据,而RTSP传输的视频流几乎都是H.264编码的,所以第一步必须完成「YUV → H.264」的编码,这里推荐用Android原生的MediaCodec硬编码,效率高且无额外依赖。
2. 初始化MediaCodec编码器
要保证编码器的参数和相机预览参数完全匹配(分辨率、帧率),否则会出现编码失败或画面异常:
// 替换成你实际的相机预览参数 private static final int PREVIEW_WIDTH = 1280; private static final int PREVIEW_HEIGHT = 720; private static final int FRAME_RATE = 30; private MediaCodec h264Codec; private void initH264Encoder() { try { // 创建H.264编码器 h264Codec = MediaCodec.createEncoderByType("video/avc"); MediaFormat encodeFormat = MediaFormat.createVideoFormat( "video/avc", PREVIEW_WIDTH, PREVIEW_HEIGHT ); // 配置编码器参数 encodeFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible); // 兼容NV21 encodeFormat.setInteger(MediaFormat.KEY_BIT_RATE, 2 * 1024 * 1024); // 2Mbps码率,可按需调整 encodeFormat.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE); encodeFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5); // 每5秒生成一个关键帧 // 启动编码器 h264Codec.configure(encodeFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); h264Codec.start(); } catch (IOException e) { e.printStackTrace(); } }
3. 在PreviewCallback中喂数据给编码器
把相机返回的byte[]直接传给编码器,同时处理编码后的H.264数据:
mPreviewCallBack = new android.hardware.Camera.PreviewCallback() { @Override public void onPreviewFrame(byte[] data, android.hardware.Camera camera) { // 保留你已有的运动检测和录制逻辑 detectMotion(data); recordVideo(data); // 处理RTSP流:喂数据给编码器 processFrameForRtsp(data); } }; private void processFrameForRtsp(byte[] yuvData) { try { // 获取编码器输入缓冲区 int inputBufferIdx = h264Codec.dequeueInputBuffer(10_000); // 10ms超时 if (inputBufferIdx >= 0) { ByteBuffer inputBuffer = h264Codec.getInputBuffer(inputBufferIdx); inputBuffer.clear(); inputBuffer.put(yuvData); // 计算时间戳(微秒),保证视频时序正确 long presentationTimeUs = System.nanoTime() / 1000; h264Codec.queueInputBuffer( inputBufferIdx, 0, yuvData.length, presentationTimeUs, 0 ); } // 获取编码器输出的H.264数据 MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); int outputBufferIdx = h264Codec.dequeueOutputBuffer(bufferInfo, 10_000); while (outputBufferIdx >= 0) { ByteBuffer outputBuffer = h264Codec.getOutputBuffer(outputBufferIdx); if (bufferInfo.size > 0) { // 判断是否是SPS/PPS配置帧(H.264必需的初始化数据) boolean isConfigFrame = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0; // 把H.264数据封装成RTP包并发送 sendH264OverRtp(outputBuffer, bufferInfo.presentationTimeUs, isConfigFrame); } h264Codec.releaseOutputBuffer(outputBufferIdx, false); outputBufferIdx = h264Codec.dequeueOutputBuffer(bufferInfo, 0); } } catch (Exception e) { e.printStackTrace(); } }
4. 实现RTSP/RTP传输逻辑
RTSP是控制协议(处理客户端的连接、播放请求),实际视频数据通过RTP/UDP传输。这里需要做两件事:
4.1 处理RTSP会话
你需要用ServerSocket监听RTSP端口(默认554),处理客户端发送的OPTIONS、DESCRIBE、SETUP、PLAY等请求,返回符合规范的响应,同时协商RTP传输的端口。
关键是在DESCRIBE响应中返回SDP描述,包含H.264的编码参数(SPS、PPS、profile-level-id等),这些参数可以从编码器输出的配置帧中提取。
4.2 H.264转RTP封装
H.264的NALU单元(编码后的帧数据)如果小于MTU(比如1400字节),可以直接作为RTP负载;如果大于,需要分片成FU-A包。同时RTP时间戳要基于90kHz时钟(H.264标准),转换方式如下:
// 把微秒时间戳转成90kHz的RTP时间戳 long rtpTimestamp = (presentationTimeUs * 90) / 1000;
你可以用DatagramSocket来发送RTP包,每个包需要包含RTP头部(版本、序列号、时间戳、SSRC等)和H.264负载。
5. 资源释放
在相机关闭或页面销毁时,一定要释放编码器和Socket资源:
private void releaseResources() { if (h264Codec != null) { h264Codec.stop(); h264Codec.release(); h264Codec = null; } // 关闭RTSP ServerSocket和RTP DatagramSocket // ... }
关键注意事项
- 确保相机预览的YUV格式和编码器颜色格式兼容,NV21一般兼容
COLOR_FormatYUV420Flexible,如果不行可以尝试COLOR_FormatYUV420SemiPlanar。 - SPS/PPS必须在视频流开始前发送给客户端,否则客户端无法解码。
- RTP包的序列号要严格递增,SSRC要保持唯一,保证客户端能正确拼接帧数据。
内容的提问来源于stack exchange,提问作者Tran Intent

