如何在Android应用中接收H264格式IP视频流并实现逐帧采集
实现Android端接收UDP H264流并逐帧采集的方案
所需核心组件
你之前查询的AudioManager是音频相关组件,和当前视频流接收场景完全无关,无需关注。核心用到的组件如下:
- 网络层:
DatagramSocket,Colasoft Packet Player默认发的是UDP协议裸流,不需要TCP握手逻辑,直接用该类监听对应端口收包即可 - 解封装层:自研H264 NALU拼接逻辑,UDP传输时H264帧会被拆成多个分片,需要拼接成完整的I/P/B帧再送解码
- 解码层:Android原生
MediaCodec硬解码器,性能远高于软解码,直接喂入完整NALU即可输出解码后的YUV/RGB帧,满足逐帧采集需求 - 可选渲染层:
SurfaceView/TextureView,仅需要逐帧采集不需要画面展示的话可以不用
具体实现步骤
步骤1:添加必要权限
在AndroidManifest.xml中添加如下权限:
<!-- 网络操作权限 --> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <!-- 采集的帧需要存储到本地再加存储权限 --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
步骤2:启动UDP收包线程
网络操作必须在子线程执行,禁止放到主线程,参考逻辑如下:
private DatagramSocket mUdpSocket; private final byte[] mReceiveBuf = new byte[2048]; // 普通UDP包最大长度1500,设2048足够 private boolean mIsReceiving = false; // 该方法需要在子线程调用 public void startReceive(int targetPort) { mIsReceiving = true; try { mUdpSocket = new DatagramSocket(targetPort); DatagramPacket packet = new DatagramPacket(mReceiveBuf, mReceiveBuf.length); while (mIsReceiving) { mUdpSocket.receive(packet); int dataLen = packet.getLength(); byte[] realData = new byte[dataLen]; System.arraycopy(mReceiveBuf, 0, realData, 0, dataLen); // 收到的包送入NALU拼接队列处理 addToNaluCache(realData); } } catch (Exception e) { e.printStackTrace(); } finally { if (mUdpSocket != null) { mUdpSocket.close(); } } }
步骤3:拼接完整H264 NALU帧
H264帧有固定起始码,要么是0x00 0x00 0x00 0x01,要么是0x00 0x00 0x01,只需要缓存收到的数据包,检测到下一个起始码时,就把之前缓存的内容作为一个完整的NALU帧输出即可。
注意:如果你的流带RTP封装,需要先去掉12字节的RTP头再做NALU拼接,可以提前用Wireshark抓包确认包结构。
步骤4:MediaCodec解码获取逐帧数据
硬解码器初始化参考逻辑:
private MediaCodec mMediaCodec; private boolean mIsDecoding = false; public void initDecoder(int videoWidth, int videoHeight) { try { MediaFormat format = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, videoWidth, videoHeight); mMediaCodec = MediaCodec.createDecoderByType(MediaFormat.MIMETYPE_VIDEO_AVC); // 第二个参数传Surface可直接渲染画面,仅需要采集帧的话传null,解码后数据会存在ByteBuffer中 mMediaCodec.configure(format, null, null, 0); mMediaCodec.start(); mIsDecoding = true; // 启动独立解码线程,从NALU队列取帧喂入解码器,再从输出缓冲区读取解码后的帧数据即可 startDecodeWorker(); } catch (Exception e) { e.printStackTrace(); } }
注意事项
- 收包、解码逻辑都要放在独立子线程执行,禁止阻塞主线程,避免出现ANR
- 解码前必须先拿到SPS、PPS参数帧,一般是流开头的前两个NALU,缺少参数帧解码器无法正常工作
- 出现花屏优先排查丢包、NALU拼接错误两个问题
- 如果H264流带B帧,解码顺序和显示顺序不一致,需要按PTS排序后再做帧采集
内容的提问来源于stack exchange,提问作者nirkov
相关产品推荐
相关产品推荐

