视频RTMP推流实践

对应RTMP推流,业界有很多开源方案。如使用FFMPEG推流,librtmp(rtmp-dump),gstream推流。由于ffmpeg和gstreamer比较庞大,仅仅用来推流,有大炮打蚊子之嫌。针对客户端特别是瘦客户端,使用librtmp(rtmp-dump)方案更加精简,更加高效。

本方案基本思路:

  1. 下载并编译librtmp。

       下载地址:http://rtmpdump.mplayerhq.hu/download/

        编译成功后产生一个librtmp.so 库

 2.调用librtmp,封装一个视频层Wrapper_RtmpLib.cpp,该类定义如下:

class Wrapper_RtmpLib

{

public:

       Wrapper_RtmpLib(char * url);

       ~Wrapper_RtmpLib();

      int Open();

      int SendData(char * data,int dataLength, unsigned int timeStamp,int debug = -1);

      int IsConnect();

      int Close();

private:

     int InitSockets();

     void CleanupSockets();

     int pushSPSPPS(char *sps, int spsLen, char *pps, int ppsLen, int m_stream_id,unsigned int timeStamp);

     int pushVideoData(char *data, int dataLen, bool keyFrame, int m_stream_id,unsigned int timeStamp);

     int GetStartPrixLen(char *Pack, int offest);

     char * rtmpUrl = NULL;

     RTMP * m_pRtmp = NULL;

     NALU * CopyNALU(NALU * src);

     void FreeNALU(NALU * nalu);

};

Wrapper_RtmpLib对外提供RTMP推流接口。

基本使用步骤:

  1. 定义一个Wrapper_RtmpLib对象test
  2. Test.open(),与服务器建立rtmp信令相关连接
  3. int SendData(char * data,int dataLength, unsigned int timeStamp,int debug = -1);发送RTMP数据

注意data,必须是一个完整的NAL单元。所以应用程序调该接口前必须解析出NAL单元。

下面是一个h264裸文件推送RTMP过程。

#include <cstdio>
#include"Wrapper_RtmpLib.hpp"
#include <unistd.h>
#include<string.h>
#include <signal.h>
#include<time.h>
#define LEN_R 1400


//检测启动码,并获取启动码的长度
int GetStartCode(char *Pack, int offest)
{
    int iStartPrexLen = 0;
    if (Pack[offest] == '\x00' && Pack[offest + 1] == '\x00'&&Pack[offest + 2] == '\x00'&&Pack[offest + 3] == '\x01')
    {
        iStartPrexLen = 4;
        return iStartPrexLen;
    }
    else if (Pack[offest] == '\x00' && Pack[offest + 1] == '\x00'&&Pack[offest + 2] == '\x01')
    {
        iStartPrexLen = 3;
        return iStartPrexLen;
    }
    else
    {
        return iStartPrexLen;
    }
}
#include <time.h>
void delaytime(int ms)
{
//    return;
    struct timespec tvUec;
    clock_gettime(CLOCK_MONOTONIC, &tvUec);
    long long pretime = tvUec.tv_nsec / 1000000 + tvUec.tv_sec * 1000;
    long long nowtime = tvUec.tv_nsec / 1000000 + tvUec.tv_sec * 1000;
    while (1)
    {
        clock_gettime(CLOCK_MONOTONIC, &tvUec);
        nowtime = tvUec.tv_nsec / 1000000 + tvUec.tv_sec * 1000;
        if (nowtime - pretime > ms-10) //程序自身耗时,预估耗时10ms。实际网络流不要延时,仅供测试
        {
            return;
        }
    }

}
void help(char *p)
{
    printf("Use:");
    printf("%s h264_File RTMP_URL FRate  1111\n",p);
}


char NALBuff[1080 * 1920 * 8]={0};
int NALLen = 0;
int lastPos = 0;
int pretime = 0;
int NALCount = 0;
int main(int argc,char*argv[])
{
    if(argc<4)
    {
        help(argv[0]);
        return 1;
    }
    signal(SIGPIPE, SIG_IGN);
    Wrapper_RtmpLib test(argv[2]);
    if (test.Open() < 0)
    {
        printf("open is failed\n");
        return 0;
    }
    if (test.IsConnect() < 0)
    {
        printf("connect is failed\n");
        return 0;
    }
    else
        printf("connect is ok\n");



    char Pack[1500] = { 0 };
    int ret = 0;
    char *pStart = NULL;
    char *pEnd = NULL;
    char *pNALbuff = NALBuff;
    unsigned int timestamp = 0;
    int ioffset = 1000 /atoi(argv[3]);
    char *pPack = Pack;
    int iCurrentStartLen = 0;
    int iPreStartLen = 0;
    FILE *fp = NULL;
    fp = fopen(argv[1], "rb+");
    if (fp == NULL)
    {
        printf("open file is failed\n");
        return -1;
    }
    while (1)
    {
        pStart = NULL;
        pEnd = NULL;
        ret = fread(Pack, LEN_R, 1, fp);
        if (ret == 1)
        {
            //如果头4个字节恰好为 00 00 00 01  或者00 00 01
            iCurrentStartLen = GetStartCode(Pack, 0);
            if(iCurrentStartLen>0)
            {
                iPreStartLen = iCurrentStartLen;
                pStart=&Pack[0];
                pEnd = &Pack[0];
                for (int i = 2; i < LEN_R; i++)
                {
                    iCurrentStartLen = GetStartCode(Pack, i);
                    if (iCurrentStartLen > 0)
                    {
                        printf("##find nal start1\n");
                        pEnd = &Pack[i];
                        memmove(NALBuff+ NALLen, pStart, pEnd - pStart);//分离NAL拷贝到buffer
                        NALLen += pEnd - pStart;
                        NALCount++;
                    }
                    if (NALLen != 0)
                    {
                            int StartCodeLen = GetStartCode(NALBuff, 0);
                            if (StartCodeLen <= 0)
                            {
                                printf("NAL buffer data error\n");
                            }
                            if ((NALBuff[StartCodeLen] & 0x1F) == 7) //sps需要等pps一起发
                            {
                                if (NALCount == 1)
                                {
                                    iPreStartLen = iCurrentStartLen;
                                    i++;
                                    pStart = pEnd;
                                    continue;
                                }
                            }
                            else if (( NALBuff[StartCodeLen] & 0x1F)== 5|| (NALBuff[StartCodeLen] & 0x1F) == 1)
                            {
                                timestamp = timestamp + ioffset;
                                i++;
                            }
                            else
                            {
                                //不是我所关注的NAL类型,可以不往下送
                                NALLen = 0;
                                NALCount = 0;
                                memset(NALBuff, 0, sizeof(NALBuff));
                                iPreStartLen = iCurrentStartLen;
                                i++;
                                pStart = pEnd;
                                continue;
                            }
                            ret =test.SendData(NALBuff, NALLen, timestamp, -1);
                            if (ret < 0)
                                printf("send is failed\n");
                            NALLen = 0;
                            NALCount = 0;
                            memset(NALBuff, 0, sizeof(NALBuff));
                            iPreStartLen = iCurrentStartLen;
                            delaytime(ioffset);
                    } 

                    pStart = pEnd;

                }
                //一个包中遗留半个NAL单元,找不到下一个头
                //剩余的不完整NAL单元拷贝到临时buffer,后面凑齐一个NAL单元再发
                    memmove(NALBuff + NALLen, pStart, (&Pack[LEN_R - 1] - pStart) + 1);//sps pps idr non-idr,拷贝到buffer
                    NALLen += (&Pack[LEN_R - 1] - pStart + 1);

            }
            else  //如果头4个字节不是启动码
            {
                for (int i = 1; i < LEN_R; i++) //必须从2开始,因为可能存在00 00 01或00 00 00 01相邻出现
                {
                    //  pStart = &Pack[0];
                    iCurrentStartLen = GetStartCode(Pack, i);
                    if (iCurrentStartLen > 0)
                    {
                        printf("##find nal start2\n");
                        pEnd = &Pack[i];
                        if (pStart == NULL)
                            pStart = &Pack[0];
                        memmove(NALBuff + NALLen, pStart, pEnd - pStart);//sps pps idr non-idr,拷贝到buffer
                        NALLen += pEnd - pStart;
                        NALCount++;
                        if (NALLen != 0)
                        {

                            int StartCodeLen = GetStartCode(NALBuff, 0);
                            if (StartCodeLen <= 0)
                            {
                                printf("NAL buffer data error\n");
                            }
                            if ((NALBuff[StartCodeLen] & 0x1F) == 7) //sps需要等pps一起发
                            {
                                if (NALCount == 1)
                                {
                                    iPreStartLen = iCurrentStartLen;
                                    i++;
                                    pStart = pEnd;
                                    continue;
                                }
                            }
                            else if ((NALBuff[StartCodeLen] & 0x1F) == 5 || (NALBuff[StartCodeLen] & 0x1F) == 1)
                            {
                                timestamp = timestamp + ioffset;
                            }
                            else
                            {
                                //不是我所关注的NAL类型,可以不往下送
                                NALLen = 0;
                                NALCount = 0;
                                memset(NALBuff, 0, sizeof(NALBuff));
                                iPreStartLen = iCurrentStartLen;
                                i++;
                                pStart = pEnd;
                                continue;
                            }

                                ret = test.SendData(NALBuff, NALLen, timestamp, -1);
                                if (ret < 0)
                                    printf("send data is failed\n");
                                NALLen = 0;
                                NALCount = 0;
                                memset(NALBuff, 0, sizeof(NALBuff));
                                delaytime(ioffset);

                        }
                        i++;
                        iPreStartLen = iCurrentStartLen;
                  }
                    pStart = pEnd;
                }
                //整个包都不足一个NAL单元
                if (pStart == pEnd)
                {
                    if (pStart == NULL)
                    {
                        pStart = &Pack[0];
                    }
                    pEnd = &Pack[LEN_R - 1];
                    memmove(NALBuff + NALLen, pStart, pEnd - pStart + 1); //
                        //lastPos = NALLen;
                    NALLen += (pEnd - pStart + 1);
                }

            }

        }
        else
        {
            printf("read is failed endof stream\n");
            break;
        }
    }
    getchar();
    printf("hello from rtmp!\n");
    return 0;
}

基本思路如下:

读文件----解析NAL单元---利用 SendData发送一个完成的NAL单元完成推流

编译main.cpp Wrapper_RtmpLib.cpp 并链接librtmp.so生成可执行文件h2642rtmp.

运行可执行程序推流

./h264tortmp  avc.h264 rtmp://192.168.1.226:8085/live/1830562240700540100 25

使用该方案注意:

  1. SendData 必须是一个完整的NAL单元。如果是文件需要解析或网络流必须解析出NAL单元。
  2. 时间戳采用间隔时间。即时间戳按每帧时间间隔递增,可能因为网络抖动或者1000/帧率不是帧率会存在累计误差。该demo因为不存在音视频同步,时间戳影响不大。

3.如果是云主机,在云主机内不能推公网IP,而要推内网IP 192.168.1.226,客户端访问需要外网IP。

客户端播放效果如下:。

更多更详细资源请关注公众号:AV_Chat

猜你喜欢

转载自blog.csdn.net/fengliang191/article/details/106453915