在Hi3559AV100平台实现H264和H265通过RTSP推流本地直播,参考了天录的H264代码,然后自己实现了H265的分包代码。
H264 RTSP推流
参考天录的H264代码:海思平台rtsp移植笔记
H265 RTSP推流
在H264代码基础上,增加H265的编码和分包后通过RTSP转发,电脑通过VLC软件实现软硬件解码H265码流。
1、编码后的H265帧数据,需要每个包存储在环形buffer中,不可像H264一样多包存储在一个buffer中,因为需要按照流程发送每一包数据。
HI_S32 HisiPutH265DataToBuffer(VENC_STREAM_S *pstStream)
{
HI_S32 i;
for (i = 0; i < pstStream->u32PackCount; i++){
if(n<NMAX){
memcpy(ringfifo[iput].buffer, pstStream->pstPack[i].pu8Addr, pstStream->pstPack[i].u32Len);
ringfifo[iput].size= pstStream->pstPack[i].u32Len;
iput = addring(iput);
n++;
}
}
return HI_SUCCESS;
}
2、参考博客:h265、h264的RTP包封装区别,实现H265的分包转发,size小于1400直接去掉起始码00 00 00 01进行转发,size大于1400的需要分包转发,提取NAL头2个字节数据到FU头3个字节数据里面,然后去掉NAL头2个字节进行数据转发。
typedef struct
{
//byte 0
unsigned char u1Lid:1;
unsigned char u6Type:6;
unsigned char u1F:1;
} StFuIndicator_h265_1; /**/ /* 1 BYTES */
typedef struct
{
//byte 0
unsigned char u3Tid:3;
unsigned char u5Lid:5;
} StFuIndicator_h265_2; /**/ /* 1 BYTES */
typedef struct
{
//byte 0
unsigned char u6Type:6;
unsigned char u1E:1;
unsigned char u1S:1;
} StFuHdr_h265; /**/ /* 1 BYTES */
typedef struct _tagStRtpHandle
{
int s32Sock;
struct sockaddr_in stServAddr;
unsigned short u16SeqNum;
unsigned long long u32TimeStampInc;
unsigned long long u32TimeStampCurr;
unsigned long long u32CurrTime;
unsigned long long u32PrevTime;
unsigned int u32SSrc;
StRtpFixedHdr *pRtpFixedHdr;
StNaluHdr *pNaluHdr;
StFuIndicator *pFuInd;
StFuIndicator_h265_1 *pFuInd_1;
StFuIndicator_h265_2 *pFuInd_2;
StFuHdr *pFuHdr;
StFuHdr_h265 *pFuHdr_h265;
EmRtpPayload emPayload;
#ifdef SAVE_NALU
FILE *pNaluFile;
#endif
} StRtpObj, *HndRtp;
static int SendNalu265(HndRtp hRtp, char *pNalBuf, int s32NalBufSize)
{
char *pNaluPayload;
char *pSendBuf;
int s32Bytes = 0;
int s32Ret = 0;
struct timeval stTimeval;
char *pNaluCurr;
int s32NaluRemain;
unsigned char u8NaluBytes_1;
unsigned char u8NaluBytes_2;
char *buf = NULL;
#if 0 //for debug
int idx = 0;
static int g_num = 0;
if(g_num < 20){
while(idx<(s32NalBufSize < 64 ? s32NalBufSize : 64)){
printf("%02x ",pNalBuf[idx]);
idx++;
if(idx%16 == 0){
printf("\n");
}
}
g_num++;
printf("size=%d\n\n",s32NalBufSize);
}
#endif
pSendBuf = (char *)calloc(MAX_RTP_PKT_LENGTH + 100, sizeof(char));
if(NULL == pSendBuf)
{
s32Ret = -1;
goto cleanup;
}
hRtp->pRtpFixedHdr = (StRtpFixedHdr *)pSendBuf;
hRtp->pRtpFixedHdr->u7Payload = H264;
hRtp->pRtpFixedHdr->u2Version = 2;
hRtp->pRtpFixedHdr->u1Marker = 0;
hRtp->pRtpFixedHdr->u32SSrc = hRtp->u32SSrc;
//计算时间戳
hRtp->pRtpFixedHdr->u32TimeStamp = htonl(hRtp->u32TimeStampCurr * (90000 / 1000));
//printf("sendnalu264 timestamp:%lld\n",hRtp->u32TimeStampCurr);
if(gettimeofday(&stTimeval, NULL) == -1)
{
printf("Failed to get os time\n");
s32Ret = -1;
goto cleanup;
}
//保存nalu首2 byte
u8NaluBytes_1 = *(pNalBuf+4);
u8NaluBytes_2 = *(pNalBuf+5);
//设置未发送的Nalu数据指针位置
pNaluCurr = pNalBuf + 4;
//设置剩余的Nalu数据数量
s32NaluRemain = s32NalBufSize - 4;
//NALU包小于等于最大包长度,直接发送
if(s32NaluRemain <= MAX_RTP_PKT_LENGTH)
{
hRtp->pRtpFixedHdr->u1Marker = 1;
hRtp->pRtpFixedHdr->u16SeqNum = htons(hRtp->u16SeqNum ++);
pNaluPayload = (pSendBuf + 12);
memcpy(pNaluPayload, pNaluCurr, s32NaluRemain);
s32Bytes = s32NaluRemain + 12;
if(sendto(hRtp->s32Sock, pSendBuf, s32Bytes, 0, (struct sockaddr *)&hRtp->stServAddr, sizeof(hRtp->stServAddr)) < 0)
{
s32Ret = -1;
goto cleanup;
}
#ifdef SAVE_NALU
fwrite(pSendBuf, s32Bytes, 1, hRtp->pNaluFile);
#endif
}
//NALU包大于最大包长度,分批发送
else
{
//去掉nalu首2 byte
pNaluCurr = pNaluCurr + 2;
s32NaluRemain = s32NaluRemain - 2;
//指定fu indicator位置
hRtp->pFuInd_1 = (StFuIndicator *)(pSendBuf + 12);
hRtp->pFuInd_1->u1F = (u8NaluBytes_1 & 0x80) >> 7;
hRtp->pFuInd_1->u1Lid = u8NaluBytes_1 & 0x01;
hRtp->pFuInd_1->u6Type = 49;
hRtp->pFuInd_2 = (StFuIndicator *)(pSendBuf + 13);
hRtp->pFuInd_2->u3Tid = u8NaluBytes_2 & 0x07;
hRtp->pFuInd_2->u5Lid = (u8NaluBytes_2 & 0xf0) >> 4;
//指定fu header位置
hRtp->pFuHdr_h265 = (StFuHdr *)(pSendBuf + 14);
hRtp->pFuHdr_h265->u6Type = (u8NaluBytes_1 >> 1) & 0x3F;
//指定payload位置
pNaluPayload = (pSendBuf + 15);
//当剩余Nalu数据多于0时分批发送nalu数据
while(s32NaluRemain > 0)
{
/*配置fixed header*/
//每个包序号增1
hRtp->pRtpFixedHdr->u16SeqNum = htons(hRtp->u16SeqNum ++);
hRtp->pRtpFixedHdr->u1Marker = (s32NaluRemain <= MAX_RTP_PKT_LENGTH) ? 1 : 0;
/*配置fu header*/
//最后一批数据则置1
hRtp->pFuHdr_h265->u1E = (s32NaluRemain <= MAX_RTP_PKT_LENGTH) ? 1 : 0;
//第一批数据则置1
hRtp->pFuHdr_h265->u1S = (s32NaluRemain == (s32NalBufSize - 6)) ? 1 : 0;
s32Bytes = (s32NaluRemain < MAX_RTP_PKT_LENGTH) ? s32NaluRemain : MAX_RTP_PKT_LENGTH;
memcpy(pNaluPayload, pNaluCurr, s32Bytes);
//发送本批次
s32Bytes = s32Bytes + 15;
if(sendto(hRtp->s32Sock, pSendBuf, s32Bytes, 0, (struct sockaddr *)&hRtp->stServAddr, sizeof(hRtp->stServAddr)) < 0)
{
s32Ret = -1;
goto cleanup;
}
#ifdef SAVE_NALU
fwrite(pSendBuf, s32Bytes, 1, hRtp->pNaluFile);
#endif
//指向下批数据
pNaluCurr += MAX_RTP_PKT_LENGTH;
//计算剩余的nalu数据长度
s32NaluRemain -= MAX_RTP_PKT_LENGTH;
}
}
cleanup:
if(pSendBuf)
{
free((void *)pSendBuf);
}
return s32Ret;
}
3、修改SDP H265编码信息。
rtspservice.c +638
strcat(pDescr,"H264/90000");//H265/90000
sample_rtsp.c +437
PAYLOAD_TYPE_E enPayLoad[2] = {PT_H264};//PT_H265
rtspservice.c +1022
rtp_s->hndRtp = (struct _tagStRtpHandle*)RtpCreate((unsigned int)(((struct sockaddr_in *)(&pRtsp->stClientAddr))->sin_addr.s_addr), Transport.u.udp.cli_ports.RTP, _h264nalu);//_h265
v=0
o=-2209000192 2209000192 IN IP4 192.168.10.1
s=Unnamed
i=N/A
c=IN IP4 192.168.10.88
t=0 0
a=recvonly
m=video 5004 RTP/AVP 96
b=RR:0
a=rtpmap:96 H265/90000
a=fmtp:96 packetization-mode=1;profile-level-id=;sprop-parameter-sets=,;
a=control:trackID=0
H265原始帧
00 00 00 01 40 01 0c 01 ff ff 01 60 00 00 03 00
b0 00 00 03 00 00 03 00 7b ac 09
size=27
00 00 00 01 42 01 01 01 60 00 00 03 00 b0 00 00
03 00 00 03 00 7b a0 03 c0 80 10 e5 8d ae 49 14
bd 37 01 01 01 00 80
size=39
00 00 00 01 44 01 c0 f2 f0 3b 34
size=11
00 00 00 01 4e 01 e5 04 28 a2 00 00 80
size=13
00 00 00 01 4e 01 89 18 3a 98 75 30 1d 4c 0b b8
7d 00 40 74 3d 13 40 42 00 00 03 00 00 03 00 00
03 00 00 80
size=36
00 00 00 01 26 01 af 77 40 10 e2 59 73 3d bc 06
86 15 ec 43 6f 29 70 ed 68 04 9a 9b 9e 62 85 86
8b 72 ab 5d 5e 28 ce 83 e4 01 b2 12 4a 4c 77 13
81 b8 98 d1 ca a6 55 2e 41 e4 0a da e3 e5 f2 f0
size=102441
00 00 00 01 02 01 d0 00 09 7f 08 ae 80 a0 90 b7
6f 81 c4 4f 3c 53 7e 6c d5 0e af db 79 b1 c9 f1
3b 29 4b 11 03 cf 7f fd 15 a3 84 a9 89 64 6a 67
f9 62 af 05 9b 89 bb 35 a7 c8 d0 f7 35 2f f8 c3
size=17007
H265抓包
第一包VPS数据:VPS_NUT
第一帧IDR数据分包数据:Start:IDR_W_RADL
最后一帧IDR数据分包数据:FU End
非I帧各分片数据:TRAIL_R标识
VLC播放
VLC关键设置:否则播放几十秒后视频卡住