从PS视频流中提取H264数据
最近一线同事反映,视频流解码后出现花屏现象。于是我让现场人员用wireshark抓一下包,发现服务器拉流走的是UDP协议的流,怪不得会花屏,网络差的时候,丢包是肯定的了。将花屏的视频文件下载下来后,发现是PS封装的H264。重点是有的PS文件能用ffplay播放,有的不能。我就纳闷了,本着上次被海康平台坑过一回的阴影,还是自己写一个从PS文件里面提取H264裸码流数据的小工具为好。
结果发现,出现问题的PS文件的第一个PS-packet的pes_packet_length比实际的大,而剩下的其他PS包都是正常的,说明第一个PS包出现了问题,从而导致ffmpeg打开失败,ffmpeg解析PS的代码文件为 ffmpeg-3.2.4/libavformat/mpeg.c
static int mpegps_read_pes_header(AVFormatContext *s,
int64_t *ppos, int *pstart_code,
int64_t *ppts, int64_t *pdts)
{
MpegDemuxContext *m = s->priv_data;
int len, size, startcode, c, flags, header_len;
int pes_ext, ext2_len, id_ext, skip;
int64_t pts, dts;
int64_t last_sync = avio_tell(s->pb);
... ...
/* find matching stream */
if (!((startcode >= 0x1c0 && startcode <= 0x1df) ||
(startcode >= 0x1e0 && startcode <= 0x1ef) ||
(startcode == 0x1bd) ||
(startcode == PRIVATE_STREAM_2) ||
(startcode == 0x1fd)))
goto redo;
if (ppos) {
*ppos = avio_tell(s->pb) - 4;
}
len = avio_rb16(s->pb); //此处即为 pes_packet_length (16bits)
pts =
dts = AV_NOPTS_VALUE;
if (startcode != PRIVATE_STREAM_2)
{
/* stuffing */
for (;;) {
if (len < 1)
goto error_redo;
c = avio_r8(s->pb);
len--;
/* XXX: for MPEG-1, should test only bit 7 */
if (c != 0xff)
break;
}
... ...
}
ffmpeg中直接读取的pes_packet_length值作为PES包的大小,而实际上,此PES包所在的PS包总大小比pes_packet_length小,从而导致ffmpeg将下一个PS Header误认为是H264数据,后续的所有PS包都会出现定位不准。这种情况,有两种解决办法,
1. 不相信pes_packet_length的值,先找到两个相邻的PS包头的位置,即找到相邻的两个 00 00 01 BA 包起始码位置,比如pos1和pos2,那么只需要在 [pos1, pos2] 范围内查找所有的PES包起始码 00 00 01 E0 ,就可以找到每个PS包中含有的所有PES包,对于每一个PES包,跳过PES header,剩下的就是H264裸码流数据了,把这些H264数据保存到同一个文件中就可以了
2. 既然是 pes_packet_length 的值不正确,那么将原始的pes_packet_length值对应修改正确就可以了
最后说一句,下载的PS文件中的program_stream_map_length跟ISO/IEC文档定义的不一样,HIK又一个坑
上一篇: AQS-预备-背景
下一篇: JavaScript——类还有对象
推荐阅读