GB28181学习之路——PS流解析H264
磕磕绊绊的做了出来,也算为自己留个资料吧。先讲理论再上代码。挑些重点讲。
1. 首先就是获取到 rtp 包,rtp包的结构是:rtp包头+payload,payload就是我们要的ps包,rtp包头的长度是12个字节,所以rtp包去掉前12字节就是ps包了。
比如这个 rtp 包,跳过12个字节,从00 00 01 ba 开始就是ps包了。
2. 找到ps包之后就要从它的格式入手开始解析,ps荷载h264是把一帧帧的数据打包传过来,一个完整的ps包会包含一帧的数据。
而h264的帧分为 i 帧和 p 帧,i 帧的结构是 ps header + ps system header + ps system map + pes + h264 (+ pes + 音频)。
p 帧的结构为 ps header + pes + h264 (+ pes + 音频)。
3. 首先我们要找到第一个 i 帧,找到ps头 00 00 01 ba,ps头长度为14个字节,最后一个字节的后三位的数字标识扩展字节,
这里最后一个字节是 fe ,所以跳过6个字节,到00 00 01 bb。
4. 00 00 01 bb标识 ps system header,它后面的两个字节共同表示ps system header的长度,注意是表示这之后的数据长度,
比如这里ps system header 表示长度的两个字节是 00 12,换算成十进制,就是后面还有18个字节,跳过这18个字节就到了 00 00 01 bc。
5. 00 00 01 bc表示ps system map,它后面的两个字节代表ps system map 的长度。也是这之后的数据长度。
比如这个长度是 00 5e,跳过这 94 个字节就到了 00 00 01 e0,
6,00 00 01 e0就是我们要找的pes 数据包,它后面的两个字节表示pes包剩余数据的长度,跳过两个字节的下一个字节代码pes包的扩展长度。
比如这里 00 22表示后面还有 34个字节,08表示有8个扩展字节,扩展字节之后就是h264的数据,h264数据的长度为pes的长度减去到扩展字节的长度再减去扩展字节。也就是34-3-8 = 23个字节。
如图我们就取到了一个h264的数据了。看到后面又是00 00 01 e0开始的,所以又是一个视频帧。循环往复即可。
下面上代码,这里我使用的是 jrtp,方便获取 rtp 包,测试过海康和大华的两款设备都是没问题的。
代码已上传CSDN:https://download.csdn.net/download/qq_39805297/12566110
MyRTPSession.h
#ifndef MYRTPSESSION_H
#define MYRTPSESSION_H
#include <jrtplib3/rtpsession.h>
#include <jrtplib3/rtppacket.h>
#include <jrtplib3/rtpudpv4transmitter.h>
#include <jrtplib3/rtpipv4address.h>
#include <jrtplib3/rtpsessionparams.h>
#include <jrtplib3/rtperrors.h>
#include <jrtplib3/rtpsourcedata.h>
#include <stdlib.h>
#include <stdio.h>
#include <iostream>
#include <string>
using namespace jrtplib;
//
// The new class routine
//
class MyRTPSession : public RTPSession
{
protected:
void OnNewSource(RTPSourceData *dat)
{
if (dat->IsOwnSSRC())
return;
uint32_t ip;
uint16_t port;
if (dat->GetRTPDataAddress() != 0)
{
const RTPIPv4Address *addr = (const RTPIPv4Address *)(dat->GetRTPDataAddress());
ip = addr->GetIP();
port = addr->GetPort();
}
else if (dat->GetRTCPDataAddress() != 0)
{
const RTPIPv4Address *addr = (const RTPIPv4Address *)(dat->GetRTCPDataAddress());
ip = addr->GetIP();
port = addr->GetPort() - 1;
}
else
return;
RTPIPv4Address dest(ip, port);
AddDestination(dest);
struct in_addr inaddr;
inaddr.s_addr = htonl(ip);
std::cout << "Adding destination " << std::string(inet_ntoa(inaddr)) << ":" << port << std::endl;
}
void OnBYEPacket(RTPSourceData *dat)
{
if (dat->IsOwnSSRC())
return;
uint32_t ip;
uint16_t port;
if (dat->GetRTPDataAddress() != 0)
{
const RTPIPv4Address *addr = (const RTPIPv4Address *)(dat->GetRTPDataAddress());
ip = addr->GetIP();
port = addr->GetPort();
}
else if (dat->GetRTCPDataAddress() != 0)
{
const RTPIPv4Address *addr = (const RTPIPv4Address *)(dat->GetRTCPDataAddress());
ip = addr->GetIP();
port = addr->GetPort() - 1;
}
else
return;
RTPIPv4Address dest(ip, port);
DeleteDestination(dest);
struct in_addr inaddr;
inaddr.s_addr = htonl(ip);
std::cout << "Deleting destination " << std::string(inet_ntoa(inaddr)) << ":" << port << std::endl;
}
void OnRemoveSource(RTPSourceData *dat)
{
if (dat->IsOwnSSRC())
return;
if (dat->ReceivedBYE())
return;
uint32_t ip;
uint16_t port;
if (dat->GetRTPDataAddress() != 0)
{
const RTPIPv4Address *addr = (const RTPIPv4Address *)(dat->GetRTPDataAddress());
ip = addr->GetIP();
port = addr->GetPort();
}
else if (dat->GetRTCPDataAddress() != 0)
{
const RTPIPv4Address *addr = (const RTPIPv4Address *)(dat->GetRTCPDataAddress());
ip = addr->GetIP();
port = addr->GetPort() - 1;
}
else
return;
RTPIPv4Address dest(ip, port);
DeleteDestination(dest);
struct in_addr inaddr;
inaddr.s_addr = htonl(ip);
std::cout << "Deleting destination " << std::string(inet_ntoa(inaddr)) << ":" << port << std::endl;
}
};
#endif //MYRTPSESSION_H
main.cpp
#include "myrtpsession.h"
#define PS_BUFF_SIZE 4096000
#define SAVE_PS_FILE 1
#define SAVE_H264_FILE 1
//
// This function checks if there was a RTP error. If so, it displays an error
// message and exists.
//
uint8_t *_frameBuff;
int _frameSize;
int _buffLen;
FILE* fp_h264;
void checkerror(int rtperr)
{
if (rtperr < 0)
{
std::cout << "ERROR: " << RTPGetErrorString(rtperr) << std::endl;
exit(-1);
}
}
void writeH264Frame()
{
printf("write frame size=%d\n", _buffLen);
if (_frameSize != _buffLen)
printf("error:frameSize=%d bufflen=%d\n", _frameSize, _buffLen);
#if SAVE_H264_FILE
fwrite(_frameBuff, 1, _buffLen, fp_h264);
#endif
memset(_frameBuff, 0, sizeof(_frameBuff));
_buffLen = 0;
_frameSize = 0;
}
void getH264Frame(uint8_t* payloadData, int payloadLength)
{
int pos = 0;
memset(_frameBuff, 0, sizeof(_frameBuff));
_buffLen = 0;
_frameSize = 0;
/****** 统一 帧 ******/
while (payloadData[pos] == 0x00 && payloadData[pos+1] == 0x00 &&
payloadData[pos+2] == 0x01 && payloadData[pos+3] == 0xe0)
{
uint16_t h264_size = payloadData[pos+4] << 8 | payloadData[pos+5];
uint8_t expand_size = payloadData[pos+8];
_frameSize = h264_size - 3 - expand_size;
pos += 9 + expand_size;
//全部写入并保存帧
if (_frameSize <= payloadLength - pos)
{
{
memcpy(_frameBuff, payloadData + pos, _frameSize);
_buffLen += _frameSize;
pos += _frameSize;
writeH264Frame();
}
if (pos >= payloadLength)
break;
}
else
{
memcpy(_frameBuff, payloadData + pos, payloadLength - pos);
_buffLen += payloadLength - pos;
printf("Frame size:%d\n", _frameSize);
break;
}
}
}
void getH264FromPacket(uint8_t* payloadData, int payloadLength)
{
int pos = 0;
uint8_t expand_size = payloadData[13] & 0x07;//扩展字节
pos += 14 + expand_size;//ps包头14
/****** i 帧 ******/
if (payloadData[pos] == 0x00 && payloadData[pos + 1] == 0x00 &&
payloadData[pos + 2] == 0x01 && payloadData[pos + 3] == 0xbb)
{//ps system header
uint16_t psh_size = payloadData[pos + 4] << 8 | payloadData[pos + 5];//psh长度
pos += 6 + psh_size;
if (payloadData[pos] == 0x00 && payloadData[pos + 1] == 0x00 &&
payloadData[pos + 2] == 0x01 && payloadData[pos + 3] == 0xbc)
{//ps system map
uint16_t psm_size = payloadData[pos + 4] << 8 | payloadData[pos + 5];
pos += 6 + psm_size;
}
else
{
printf("no system map and no video stream\n");
return;
}
}
getH264Frame(payloadData + pos, payloadLength - pos);
}
void executeProcess(int port, int secs)
{
#ifdef RTP_SOCKETTYPE_WINSOCK
WSADATA dat;
WSAStartup(MAKEWORD(2, 2), &dat);
#endif // RTP_SOCKETTYPE_WINSOCK
#if SAVE_PS_FILE
FILE* fp_ps = fopen("gb.ps", "w");
#endif // SAVE_PS_FILE
#if SAVE_H264_FILE
fp_h264 = fopen("gb.h264", "w");
#endif // SAVE_H264_FILE
MyRTPSession sess;
std::string ipstr;
int status, j;
// Now, we'll create a RTP session, set the destination
// and poll for incoming data.
RTPUDPv4TransmissionParams transparams;
RTPSessionParams sessparams;
// IMPORTANT: The local timestamp unit MUST be set, otherwise
// RTCP Sender Report info will be calculated wrong
// In this case, we'll be just use 8000 samples per second.
sessparams.SetOwnTimestampUnit(1.0 / 8000.0);
sessparams.SetAcceptOwnPackets(true);
transparams.SetPortbase(port);
status = sess.Create(sessparams, &transparams);
checkerror(status);
_frameBuff = new uint8_t[PS_BUFF_SIZE];
memset(_frameBuff, 0, sizeof(_frameBuff));
_frameSize = 0;
_buffLen = 0;
for (j = 1; j <= secs; j++)
{
sess.BeginDataAccess();
printf("secs gone %d\n", j);
// check incoming packets
if (sess.GotoFirstSourceWithData())
{
do
{
RTPPacket *pack;
while ((pack = sess.GetNextPacket()) != NULL)
{
printf("Got packet\n");
// You can examine the data here
if (pack->GetPayloadType() == 96)
{
#if SAVE_PS_FILE
fwrite(pack->GetPayloadData(), 1, pack->GetPayloadLength(), fp_ps);
#endif
//查找ps头 0x000001BA
if (pack->GetPayloadData()[0] == 0x00 && pack->GetPayloadData()[1] == 0x00 &&
pack->GetPayloadData()[2] == 0x01 && pack->GetPayloadData()[3] == 0xba)
{
getH264FromPacket(pack->GetPayloadData(), pack->GetPayloadLength());
}
else if (pack->GetPayloadData()[0] == 0x00 && pack->GetPayloadData()[1] == 0x00 &&
pack->GetPayloadData()[2] == 0x01 && pack->GetPayloadData()[3] == 0xe0)
{
getH264Frame(pack->GetPayloadData(), pack->GetPayloadLength());
}
else //当然如果开头不是0x000001BA,默认为一个帧的中间部分,我们将这部分内存顺着帧的开头向后存储
{
//排除音频和私有数据
if (pack->GetPayloadData()[0] == 0x00 && pack->GetPayloadData()[1] == 0x00 &&
pack->GetPayloadData()[2] == 0x01 && pack->GetPayloadData()[3] == 0xc0)
{
}
else if (pack->GetPayloadData()[0] == 0x00 && pack->GetPayloadData()[1] == 0x00 &&
pack->GetPayloadData()[2] == 0x01 && pack->GetPayloadData()[3] == 0xbd)
{
}
else //这是正常的帧数据,像贪吃蛇一样,将它放在帧开头的后边
{
if (pack->GetPayloadLength() + _buffLen >= _frameSize)
{
int len = _frameSize - _buffLen;
memcpy(_frameBuff + _buffLen, pack->GetPayloadData(), len);
_buffLen += len;
writeH264Frame();
if (pack->GetPayloadLength() > len)
getH264FromPacket(pack->GetPacketData() + len, pack->GetPacketLength() - len);
}
else
{
memcpy(_frameBuff + _buffLen, pack->GetPayloadData(), pack->GetPayloadLength());
_buffLen += pack->GetPayloadLength();
}
}
}
}
// we don't longer need the packet, so
// we'll delete it
sess.DeletePacket(pack);
}
} while (sess.GotoNextSourceWithData());
}
sess.EndDataAccess();
#ifndef RTP_SUPPORT_THREAD
status = sess.Poll();
checkerror(status);
#endif // RTP_SUPPORT_THREAD
RTPTime::Wait(RTPTime(1, 0));
}
sess.BYEDestroy(RTPTime(10, 0), 0, 0);
#ifdef RTP_SOCKETTYPE_WINSOCK
WSACleanup();
#endif // RTP_SOCKETTYPE_WINSOCK
#if SAVE_PS_FILE
fclose(fp_ps);
#endif
#if SAVE_H264_FILE
fclose(fp_h264);
#endif
printf("StreamReciever exits\n");
}
int main()
{
executeProcess(19444, 100);
return 0;
}
上一篇: Pytorch学习笔记(一)搭建一个CNN神经网络
下一篇: pytorch入门