最近在处理海康视频流,因为要推到RTMP服务器上,所以需要把海康读取的Ps流转为H.264流。但网上代码实现的较少,或质量一般,于是参考大神博客 https://blog.csdn.net/wwyyxx26/article/details/15224879 实现了相关代码。
使用的语言为C#,使用.Net Core实现。代码是简单实现,只考虑了单线程问题。
完整代码链接在最下端
好了不啰嗦了,切入正题。
首先是找寻包头
for (int i = 0; i < PsBufferCount - 4; i++)
{
//判断包头
if (this.psBuffer[i] == 0x00 && this.psBuffer[i + 1] == 0x00
&& this.psBuffer[i + 2] == 0x01 && this.psBuffer[i + 3] == 0xBA)
{
if (i > 0)
{
//找到包头,把包头之前部分遗弃
this.psBuffer.RemoveRange(0, i);
}
int length;
//判断包头长度是否为完整包头
if (this.PsBufferCount >= 14
&& this.PsBufferCount >= (length = 14 + (this.psBuffer[13] & 0x07)))
{
//移除包头并继续下一步
this.psBuffer.RemoveRange(0, length);
this.parseState = 1;
return true;
}
}
}
//找寻失败,解析停止
this.parseState = 0;
return false;
然后找到包头后,需要验证是否有系统头
//判断是否包含系统头
if (this.psBuffer[0] == 0x00 && this.psBuffer[1] == 0x00
&& this.psBuffer[2] == 0x01 && this.psBuffer[3] == 0xBB)
{
int length;
//包含系统头,判断系统头是否完整
if (this.PsBufferCount >= 12
&& this.PsBufferCount > (length = this.psBuffer[4] * 256 + this.psBuffer[5] + 6))
{
//系统头完整,去除系统头,解析继续
this.psBuffer.RemoveRange(0, length);
this.parseState = 2;
return true;
}
else
{
//系统头不完整,解析停止
this.parseState = 1;
return false;
}
}
else
{
//不包含系统头,解析继续
this.parseState = 2;
return true;
}
系统头后可能跟随Program Stream Map,然后判断是否为Program Stream Map
//判断是否包含Program Stream Map
if (this.psBuffer[0] == 0x00 && this.psBuffer[1] == 0x00
&& this.psBuffer[2] == 0x01 && this.psBuffer[3] == 0xBC)
{
int length;
//包含Program Stream Map,判断Program Stream Map是否完整
if (this.PsBufferCount >= 16
&& this.PsBufferCount > (length = this.psBuffer[4] * 256 + this.psBuffer[5] + 6))
{
//Program Stream Map完整,去除Program Stream Map,解析继续
this.psBuffer.RemoveRange(0, length);
this.parseState = 3;
return true;
}
else
{
//Program Stream Map不完整,解析停止
this.parseState = 2;
return false;
}
}
else
{
// 不包含Program Stream Map,解析继续
this.parseState = 3;
return true;
}
最后是解析PES包
//判断包头
if (this.psBuffer[0] == 0x00 && this.psBuffer[1] == 0x00
&& this.psBuffer[2] == 0x01)
{
int length;
//判断是否具有完整的PES包
if (this.PsBufferCount >= 9
&& this.PsBufferCount > (length = this.psBuffer[4] * 256 + this.psBuffer[5] + 6))
{
//获取一个完整的PES包
List<byte> buffer = this.psBuffer.GetRange(0, length);
this.psBuffer.RemoveRange(0, length);
//判断是否符合,符合写入H264流
if (buffer[3] == 0xE0 && length > 0)
{
this.h264Buffer.AddRange(buffer.GetRange(9 + buffer[8], buffer.Count - 9 - buffer[8]));
}
//else if (buffer[3] == 0xC0 && length > 0) { }
//解析完成,寻找下一个包,解析继续
this.parseState = 1;
return true;
}
else
{
//PES不完整,解析停止
this.parseState = 3;
return false;
}
}
else
{
//数据包错误,寻找包头,解析继续
this.parseState = 0;
return true;
}
然后就是把整个代码串起来,需要注意一个包中可能存在多个PES包,所以,解析完PES包不能直接找包头。此处在每次循环前都添加了找包头的代码,这样就可以在包头解析和PES包解析自由切换。
//循环解析
while (this.PsBufferCount >= 4)
{
//判断是否包含包头
if (this.psBuffer[0] == 0x00 && this.psBuffer[1] == 0x00
&& this.psBuffer[2] == 0x01 && this.psBuffer[3] == 0xBA)
{
this.parseState = 0;
}
switch (this.parseState)
{
case 0:
if (!FindAndRemoveHeader()) return;
break;
case 1:
if (!RemoveSystemHeader()) return;
break;
case 2:
if (!RemoveProgramStreamMap()) return;
break;
case 3:
if (!ParsingPakcet()) return;
break;
default:
this.parseState = 0;
return;
}
}
最后赋上我完整代码的GitHub地址:https://github.com/kevinfromcn/PsToH264.git
如果对你有帮助,希望给个Star