Hi3559AV100 RTSP推H265视频码流使用VLC软件本地直播

本文介绍了如何在Hi3559AV100平台上实现H264和H265视频通过RTSP协议进行本地直播,涉及编码、环形缓冲、H265分包转发和RTP封装,同时分享了VLC播放设置以确保流畅播放。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在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关键设置:否则播放几十秒后视频卡住

 

评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值