RTP实战-----RTP封装h264码流

1.整体目录结构

root@ubuntu:/home/sulier/work/AudioVedio/RTPZip/testRTP# tree
.
├── base.h
├── h264.cpp
├── h264.h
├── main.cpp
├── makefile
├── rtp.h
└── script
    └── makefile.build

2. 代码段

base.h

#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <iostream>
#include <string>

using namespace std;

rtp.h

#ifndef __RTP_H__
#define __RTP_H__

#define MTU_SIZE                1500
#define MAX_RTP_PACKET_LENGTH   1360

#endif

h264.h

#ifndef __H264_H__
#define __H264_H__
#include"base.h"

#define D_H264_SAMPLE_RATE  90000.0
#define I_H264_SAMPLE_RATE  90000
#define H264_PAYLOAD_TYPE   96
#define H264_FRAME_RATE     60

//定义整装NALU时NALU头结构体
typedef struct stNaluHead_t
{
    uint8_t uiForbidden:1;      //h264要求规范为0
    uint8_t uiNRI:2;            //NALU的重要性 0~3 值越大越重要
    uint8_t uiType:5;           //1~23 下图中
}stNaluHead;

/******************uiType数值含义******************
uiType            含义
0               未指定
1               非 IDR 图像的片
5               IDR 图像的片
6               SEI(辅助增强信息)
7               SPS(序列参数集)
8               PPS(图像参数集)
24              STAP-A(单一时间组合包模式 A,用于一个 RTP 包荷载多个 NALU)
25              STAP-B(单一时间组合包模式 B)
26              MTAP16(多个时间的组合包模式 A)
27              MTAP24(多个时间的组合包模式 B)
28              FU-A(分片模式 A,用于将单个 NALU 分到多个 RTP 包)
29              FU-B(分片模式 B)
***************************************************/

//定义NALU分片时FU indicator 结构体,分片时位于FU      header前
typedef struct stFuIndicator_t
{
    uint8_t uiForbidden:1;      //h264要求规范为0
    uint8_t uiNRI:2;            //NALU的重要性 0~3 值越大越重要
    uint8_t uiType:5;           //28-29 上图中
}stFuIndicator;

//定义NALU分片时FU      header结构体
typedef struct stFuHead_t
{
    uint8_t uiStart:1;          //1代表开始的第一个分片
    uint8_t uiEnd:1;            //1代表最后一个分片
    uint8_t uiReverse:1;        //保留位
    uint8_t uiType:5;           //1~23 上图中
}stFuHead;

/******************NALU包******************
***************************************************
1.startcode有两种,这是包开始
    0x00 0x00 0x00 0x01
    0x00 0x00 0x01
2.接下来就是上面的NALU头
uiForbidden规范为0,下面都不做解释
《A》可能是整装:
    头结构只有一个NALU头结构体比如0x67就是
    二进制0              1 1         0 0 1 1 1
    uiForbidden      uiNRI        uiType
    代表重要性值为3(非常重要) 类型为7(SPS序列参数集)
《B》可能是分片:
    头结构第一个FU indicator 结构体比如0x5c就是
    二进制0              1 0         1 1 1 0 0
    uiForbidden      uiNRI        uiType
    代表重要性值为2(重要) 类型为28(FU-A)
     头结构第二个就是FU      header结构体

3.最后是payload数据
***************************************************
*******************************************/
typedef struct stNalu_t{
    int     iStartCodePreLen;
    uint8_t uiForbidden;
    uint8_t uiNRI;
    uint8_t uiType;
    int     iMaxSize;
    uint8_t *pBuff;
    int     iNaluHeadLen;      //h264码流中nalu头长度,固定为1
    int     iPayloadLen;       //payload长度
}stNalu;

stNalu *AllocNalu_T(int iMaxSize);
stNalu *AllocNalu();
void FreeNalu(stNalu *pNalu);
bool isStartCode3(uint8_t *pBuff);
bool isStartCode4(uint8_t *pBuff);
int GetAnnexbNalu(FILE *F, stNalu *pNalu);


#endif

h264.cpp

#include"h264.h"

#define DEFAULT_MAX_SIZE 9600

stNalu *AllocNalu_T(int iMaxSize)
{
    stNalu *pNalu = (stNalu *)malloc(sizeof(stNalu));
    if (pNalu == NULL)
    {
        return NULL;
    }
    pNalu->pBuff = (uint8_t *)malloc(iMaxSize);
    if (pNalu->pBuff == NULL)
    {
        free(pNalu);
        pNalu = NULL;
        return NULL;
    }
    pNalu->iMaxSize = iMaxSize;
    return pNalu;
}

stNalu *AllocNalu()
{
    return AllocNalu_T(DEFAULT_MAX_SIZE);
}

void FreeNalu(stNalu *pNalu)
{
    if (pNalu != NULL)
    {
        if (pNalu->pBuff != NULL)
        {
            free(pNalu->pBuff);
            pNalu->pBuff = NULL;
        }
        free(pNalu);
        pNalu = NULL;
    }
    return;
}
bool isStartCode3(uint8_t *pBuff)
{
    if (pBuff[0] == 0x00 && pBuff[1] == 0x00 && pBuff[2] == 0x01)
    {
        return true;
    }
    return false;
}

bool isStartCode4(uint8_t *pBuff)
{
    if (pBuff[0] == 0x00 && pBuff[1] == 0x00 && pBuff[2] == 0x00 && pBuff[2] == 0x01)
    {
        return true;
    }
    return false;
}
int GetAnnexbNalu(FILE *F, stNalu *pNalu)
{
    if (F == NULL || pNalu == NULL || feof(F))
    {
        return -1;
    }
    //一次读取3个字节
    int iRead = fread(pNalu->pBuff, 1, 3, F);
    if (iRead != 3)
    {
        return -1;
    }
    if (isStartCode3(pNalu->pBuff) == true)
    {
        pNalu->iStartCodePreLen = 3;
    }
    else
    {
        iRead = fread(pNalu->pBuff + 3, 1, 1, F);
        if (iRead != 1)
        {
            return -1;
        }
        if (isStartCode4(pNalu->pBuff) == true)
        {
            pNalu->iStartCodePreLen = 4;
        }
        else
        {
            return 0;
        }
    }
    bool bNextStartCode = false;
    int pos = pNalu->iStartCodePreLen;
    int iNextStartCodeLen = 0;
    while (bNextStartCode)
    {
        if (feof(F))
        {
            break;
        }
        pNalu->pBuff[pos] = fgetc(F);
        //获取下一个startcode的位置,当然有可能是3个字节也有可能4个字节
        if (pNalu->pBuff[pos] == 0x01 && pNalu->pBuff[pos - 1] == 0x00 && pNalu->pBuff[pos -2] == 0x00)
        {
            bNextStartCode = true;
            iNextStartCodeLen = 3;
            if (pNalu->pBuff[pos -3] == 0x00)
            {
                iNextStartCodeLen = 4;
            }
        }
        pos++;
    }
    pos = pos - iNextStartCodeLen;
    //已经读取了下一个包的startcode,所以需要回退iNextStartCodeLen字节
    if (bNextStartCode == true)
    {
        if (fseek(F, -iNextStartCodeLen, SEEK_CUR) != 0)
        {
            return -1;
        }
    }
    pNalu->uiForbidden = pNalu->pBuff[pNalu->iStartCodePreLen] & 0x80;
    pNalu->uiNRI = pNalu->pBuff[pNalu->iStartCodePreLen] & 0x60;
    pNalu->uiType = pNalu->pBuff[pNalu->iStartCodePreLen] & 0x1f;
    pNalu->iNaluHeadLen = 1;        //固定1个字节的头
    pNalu->iPayloadLen = pos - pNalu->iStartCodePreLen - pNalu->iNaluHeadLen;
    return pNalu->iPayloadLen;
}

 main.cpp

#include "rtpsession.h"
#include "rtpudpv4transmitter.h"
#include "rtpipv4address.h"
#include "rtpsessionparams.h"
#include "rtperrors.h"
#include "rtplibraryversion.h"


#include "base.h"
#include "h264.h"
#include "rtp.h"

using namespace jrtplib;

void checkerror(int rtperr)
{
    if (rtperr < 0)
    {
        cout << "ERROR: " << RTPGetErrorString(rtperr) << endl;
        exit(-1);
    }
}

int main()
{
    RTPSession cSess;
    uint16_t uiPortBase,uiDestPort;
    uint32_t uiDestIp;
    string strIp, strH264File;
    int iStatus,i,num;

    cout << "Using version " << RTPLibraryVersion::GetVersion().GetVersionString() << endl;
    //本地输出端口
    cout << "Enter local portbase:" << endl;
    cin >> uiPortBase;
    cout << endl;
    //本地输入h264视频流文件
    cout << "Enter h264 file name include path:" << endl;
    cin >> strH264File;
    cout << endl;
    //输出至远程IP地址
    cout << "Enter the destination IP address" << endl;
    cin >> strIp;
    uiDestIp = inet_addr(strIp.c_str());
    if (uiDestIp == INADDR_NONE)
    {
        cerr << "Bad IP address specified" << endl;
        return -1;
    }
    uiDestIp = ntohl(uiDestIp);
    //输出至远程端口
    cout << "Enter the destination port" << endl;
    cin >> uiDestPort;

    //传输协议以及会话参数设置
    RTPUDPv4TransmissionParams transparams;
    RTPSessionParams sessparams;
    //设置自己的时间戳单元,1/sampleRate--也就是采样率
    sessparams.SetOwnTimestampUnit(1.0/D_H264_SAMPLE_RATE);

    iStatus = cSess.Create(sessparams, &transparams);
    checkerror(iStatus);

    RTPIPv4Address addr(uiDestIp,uiDestPort);
    iStatus = cSess.AddDestination(addr);
    checkerror(iStatus);

    cSess.SetDefaultPayloadType(H264_PAYLOAD_TYPE);
    cSess.SetDefaultMark(false);
    cSess.SetDefaultTimestampIncrement(I_H264_SAMPLE_RATE/H264_FRAME_RATE);

    FILE *fH264 = fopen(strH264File.c_str(), "rb");
    if (fH264 == NULL)
    {
        cerr<<"bad file"<<endl;
        return -1;
    }

    stNalu *pNalu = AllocNalu();
    uint8_t SendBuff[MTU_SIZE] = {0};
    while(true)
    {
        if(feof(fH264))
        {
            fseek(fH264, 0, SEEK_SET);
        }
        //返回的是payload的长度
        int iSize = GetAnnexbNalu(fH264, pNalu);
        if (iSize == 0)
        {
            break;
        }
        else if (iSize < 0)
        {
            FreeNalu(pNalu);
            exit(0);
        }
        else
        {
            if (iSize <= MAX_RTP_PACKET_LENGTH - 1) //预留一个字节存储stNaluHead
            {
                memset(SendBuff, 0, MTU_SIZE);
                stNaluHead *pHead = (stNaluHead *)&SendBuff[0];
                //为了确保强行赋值时位相同
                pHead->uiForbidden  = pNalu->uiForbidden & 0x1;
                pHead->uiNRI        = pNalu->uiNRI & 0x3;
                pHead->uiType       = pNalu->uiType & 0x1f;

                uint32_t uiTimeStampInc = 0;
                if(pHead->uiType == 1 || pHead->uiType == 5)// 如果是I帧,需要时间戳增加率
                {
                    uiTimeStampInc = I_H264_SAMPLE_RATE/H264_FRAME_RATE;
                }
                //payload填充
                memcpy(&SendBuff[1], pNalu->pBuff + pNalu->iStartCodePreLen + pNalu->iNaluHeadLen, pNalu->iPayloadLen);
                iStatus = cSess.SendPacket((void *)SendBuff, pNalu->iNaluHeadLen + pNalu->iPayloadLen,H264_PAYLOAD_TYPE, false, uiTimeStampInc);
            }
            else
            {
                //拆成分片时需要每个包增加stFuIndicator和stFuHead,所以应该以最大包的值减去2个字节
                int iNum = iSize/(MAX_RTP_PACKET_LENGTH - 2);
                int iLast = iSize%(MAX_RTP_PACKET_LENGTH - 2);
                if (iLast != 0) iNum++;
                int iSendSucLen = 0;
                for(int iSendNum = 0; iSendNum < iNum; iSendNum++)
                {
                    memset(SendBuff, 0, MTU_SIZE);
                    stFuIndicator *pHead0 = (stFuIndicator *)&SendBuff[0];
                    //为了确保强行赋值时位相同
                    pHead0->uiForbidden = pNalu->uiForbidden & 0x1;
                    pHead0->uiNRI       = pNalu->uiNRI & 0x3;
                    pHead0->uiType      = 0x1c;  //分片
                    
                    stFuHead *pHead1 = (stFuHead *)&SendBuff[1];
                    pHead1->uiStart     = ((iSendNum == 0) ? 1: 0); //是否是第一分片
                    pHead1->uiReverse   = 0;
                    pHead1->uiEnd       = ((iSendNum == iNum -1) ? 1: 0); //是否是最后一分片
                    pHead1->uiType      = pNalu->uiType & 0x1f;
                    uint32_t uiTimeStampInc = 0;
                    if(pHead1->uiType == 1 || pHead1->uiType == 5)// 如果是I帧,需要时间戳增长值
                    {
                        uiTimeStampInc = I_H264_SAMPLE_RATE/H264_FRAME_RATE;
                    }
                    //payload填充,最后一分片需要长度计算
                    if(pHead1->uiEnd == 1)
                    {
                        memcpy(&SendBuff[2], pNalu->pBuff + iSendSucLen, iLast);
                        iStatus = cSess.SendPacket((void *)SendBuff, iLast + 2, H264_PAYLOAD_TYPE, true, uiTimeStampInc);
                    }
                    else
                    {
                        //第一分片包含了H264码流startcode还有nalu头,payload需要剔除
                        if(pHead1->uiStart == 1)
                        {
                            //payload中只能拷贝最大值减去将要传输的 stFuIndicator 和 stFuHead,也就是2个字节
                            memcpy(&SendBuff[2], pNalu->pBuff + pNalu->iStartCodePreLen + pNalu->iNaluHeadLen, MAX_RTP_PACKET_LENGTH -2);
                        }
                        else
                        {
                            //两种拷贝方法
                            //memcpy(&SendBuff[2], pNalu->pBuff + (iSendNum + 1) * (MAX_RTP_PACKET_LENGTH - 2), MAX_RTP_PACKET_LENGTH - 2);
                            memcpy(&SendBuff[2], pNalu->pBuff + iSendSucLen, MAX_RTP_PACKET_LENGTH - 2);
                        }
                        iSendSucLen = iSendSucLen + MAX_RTP_PACKET_LENGTH - 2;
                        iStatus = cSess.SendPacket((void *)SendBuff, MAX_RTP_PACKET_LENGTH, H264_PAYLOAD_TYPE, false, uiTimeStampInc);
                    }
                }
            }
        }
    }
    FreeNalu(pNalu);
    cSess.BYEDestroy(RTPTime(10,0), 0, 0);
    return 0;
}

3.编译脚本

makefile

如下INC1、INC2、LIB1、LIB2变量都是自己安装jthread以及jrtp相对应的头文件和库(一定要对应的,特别是头文件)

安装jthread以及jrtp参考我的另外一篇

RTP实战-----RTP开源库安装

.PHONY=clean
CC=g++
RM=rm
INC1=-I/home/sulier/work/AudioVedio/RTPZip/TestProject/export/jrtplib/include/jrtplib3
INC2=-I/home/sulier/work/AudioVedio/RTPZip/TestProject/export/jthread/include
LIB1=-L/home/sulier/work/AudioVedio/RTPZip/TestProject/export/jrtplib/lib
L1=-ljrtp
LIB2=-L/home/sulier/work/AudioVedio/RTPZip/TestProject/export/jthread/lib
L2=-ljthread
CUR_PATH=$(shell pwd)
SRC_PATH=$(CUR_PATH)
MAKEFILE_PATH=$(CUR_PATH)/script
MAKEFILE_BUILD=$(MAKEFILE_PATH)/makefile.build

OUT_PATH=$(CUR_PATH)/out
OUT_PATH := $(shell mkdir -p $(OUT_PATH) && cd $(OUT_PATH) && pwd)

build:
    $(MAKE) -f $(MAKEFILE_BUILD) M=$(SRC_PATH) O=$(OUT_PATH) INC1=$(INC1) INC2=$(INC2) LIB1=$(LIB1) L1=$(L1) LIB2=$(LIB2) L2=$(L2)

clean:
    $(MAKE) -f $(MAKEFILE_BUILD) M=$(SRC_PATH) O=$(OUT_PATH) clean

script目录下的makefile.build

src-list    :=  $(filter %.cpp,$(shell ls $(M)))
obj-list    :=  $(patsubst %.cpp,%.o,$(src-list))
dep-list    :=  $(patsubst %.cpp,.%.d,$(src-list))

src-list    :=  $(addprefix $(M)/,$(src-list))
obj-list    :=  $(addprefix $(O)/,$(obj-list))
dep-list    :=  $(addprefix $(M)/,$(dep-list))

PHONY       :=
target      :=  $(O)/built-in.o

CC=g++

PHONY   += __build
__build: $(target)


$(target): $(obj-list)
    $(CC) $^ -o $@ -std=c++11 $(INC1) $(INC2) $(LIB1) $(L1) $(LIB2) $(L2)

# 生成依赖文件.d
$(dep-list):  $(M)/.%.d: $(M)/%.cpp
    $(CC) -MM $< > $@ $(INC1) $(INC2)
    @echo "$(O)/`cat $@`\n\t\$$(CC) -c -o \$$@ \$$(filter %.cpp,$$^) $(INC1) $(INC2)" > $@

-include $(dep-list)

PHONY   += clean
clean:
    @echo "$(O)"
    rm -f $(target) $(obj-list) $(dep-list)

.PHONY: $(PHONY)

4.编译

在工程目录下make build即可

root@ubuntu:/home/sulier/work/AudioVedio/RTPZip/testRTP# make build 
make -f /home/sulier/work/AudioVedio/RTPZip/testRTP/script/makefile.build M=/home/sulier/work/AudioVedio/RTPZip/testRTP O=/home/sulier/work/AudioVedio/RTPZip/testRTP/out INC1=-I/home/sulier/work/AudioVedio/RTPZip/TestProject/export/jrtplib/include/jrtplib3 INC2=-I/home/sulier/work/AudioVedio/RTPZip/TestProject/export/jthread/include LIB1=-L/home/sulier/work/AudioVedio/RTPZip/TestProject/export/jrtplib/lib L1=-ljrtp  LIB2=-L/home/sulier/work/AudioVedio/RTPZip/TestProject/export/jthread/lib L2=-ljthread
make[1]: Entering directory '/home/sulier/work/AudioVedio/RTPZip/testRTP'
g++ -MM /home/sulier/work/AudioVedio/RTPZip/testRTP/main.cpp > /home/sulier/work/AudioVedio/RTPZip/testRTP/.main.d -I/home/sulier/work/AudioVedio/RTPZip/TestProject/export/jrtplib/include/jrtplib3 -I/home/sulier/work/AudioVedio/RTPZip/TestProject/export/jthread/include
g++ -MM /home/sulier/work/AudioVedio/RTPZip/testRTP/h264.cpp > /home/sulier/work/AudioVedio/RTPZip/testRTP/.h264.d -I/home/sulier/work/AudioVedio/RTPZip/TestProject/export/jrtplib/include/jrtplib3 -I/home/sulier/work/AudioVedio/RTPZip/TestProject/export/jthread/include
g++ -c -o /home/sulier/work/AudioVedio/RTPZip/testRTP/out/h264.o /home/sulier/work/AudioVedio/RTPZip/testRTP/h264.cpp -I/home/sulier/work/AudioVedio/RTPZip/TestProject/export/jrtplib/include/jrtplib3 -I/home/sulier/work/AudioVedio/RTPZip/TestProject/export/jthread/include
g++ -c -o /home/sulier/work/AudioVedio/RTPZip/testRTP/out/main.o /home/sulier/work/AudioVedio/RTPZip/testRTP/main.cpp -I/home/sulier/work/AudioVedio/RTPZip/TestProject/export/jrtplib/include/jrtplib3 -I/home/sulier/work/AudioVedio/RTPZip/TestProject/export/jthread/include
g++ /home/sulier/work/AudioVedio/RTPZip/testRTP/out/h264.o /home/sulier/work/AudioVedio/RTPZip/testRTP/out/main.o -o /home/sulier/work/AudioVedio/RTPZip/testRTP/out/built-in.o -std=c++11 -I/home/sulier/work/AudioVedio/RTPZip/TestProject/export/jrtplib/include/jrtplib3 -I/home/sulier/work/AudioVedio/RTPZip/TestProject/export/jthread/include -L/home/sulier/work/AudioVedio/RTPZip/TestProject/export/jrtplib/lib -ljrtp -L/home/sulier/work/AudioVedio/RTPZip/TestProject/export/jthread/lib -ljthread
make[1]: Leaving directory '/home/sulier/work/AudioVedio/RTPZip/testRTP'

5.运行

编译中虽然已经添加了jrtp库和jthread库,但是运行还是需要在本运行终端添加环境(临时运行方法,如果不想这么干的可能放到系统环境中)

export LD_LIBRARY_PATH=/home/sulier/work/AudioVedio/RTPZip/TestProject/export/jrtplib/lib
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/sulier/work/AudioVedio/RTPZip/TestProject/export/jthread/lib

可以通过echo命令查看下是否已添加

root@ubuntu:/home/sulier/work/AudioVedio/RTPZip/testRTP# echo $LD_LIBRARY_PATH
/home/sulier/work/AudioVedio/RTPZip/TestProject/export/jrtplib/lib:/home/sulier/work/AudioVedio/RTPZip/TestProject/export/jthread/lib
root@ubuntu:/home/sulier/work/AudioVedio/RTPZip/testRTP# ldd out/built-in.o 
        linux-vdso.so.1 =>  (0x00007ffc62ed1000)
        libjrtp.so.3.11.2 => /home/sulier/work/AudioVedio/RTPZip/TestProject/export/jrtplib/lib/libjrtp.so.3.11.2 (0x00007f760924c000)
        libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f7608eca000)
        libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f7608cb4000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f76088ea000)
        libjthread.so.1.3.3 => /home/sulier/work/AudioVedio/RTPZip/TestProject/export/jthread/lib/libjthread.so.1.3.3 (0x00007f76086e6000)
        libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f76084c9000)
        libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f76081c0000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f7609509000)

可以看出来库路径都已找到,然后执行文件是在out目录下built-in.o

root@ubuntu:/home/sulier/work/AudioVedio/RTPZip/testRTP# ./out/built-in.o 
Using version 3.11.2
Enter local portbase:

后续添加H264视频流验证

  • 5
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值