Qt基于librtmp推送H.264

版权声明:本文为灿哥哥http://blog.csdn.net/caoshangpa原创文章,转载请标明出处。 https://blog.csdn.net/caoshangpa/article/details/53125949

rtmp打包H.264的原理可以参考:RTMP协议分析及H.264打包原理

相关文章:

【1】Win7(Windows 7)下用VS2012(Visual Studio 2012)编译librtmp

【2】libRTMP使用说明

【3】 Adobe Media Server 5(AMS)的安装及使用

【4】Adobe Media Server 5(AMS)的简单配置

【5】H.264(H264)解码SPS获取分辨率和帧率

使用的Qt版本:Qt 5.5.1 VS2012

一.源码

crtmpstream.h

#ifndef CRTMPSTREAM_H
#define CRTMPSTREAM_H

#include "rtmp.h"
#include "rtmp_sys.h"
#include "amf.h"
#include <stdio.h>

//定义包头长度,RTMP_MAX_HEADER_SIZE=18
#define RTMP_HEAD_SIZE   (sizeof(RTMPPacket)+RTMP_MAX_HEADER_SIZE)
//存储Nal单元数据的buffer大小
#define BUFFER_SIZE 32768
//搜寻Nal单元时的一些标志
#define GOT_A_NAL_CROSS_BUFFER BUFFER_SIZE+1
#define GOT_A_NAL_INCLUDE_A_BUFFER BUFFER_SIZE+2
#define NO_MORE_BUFFER_TO_READ BUFFER_SIZE+3

/**
 * _NaluUnit
 * 内部结构体。该结构体主要用于存储和传递Nal单元的类型、大小和数据
 */
typedef struct _NaluUnit
{
    int type;
    int size;
    unsigned char *data;
}NaluUnit;

/**
 * _RTMPMetadata
 * 内部结构体。该结构体主要用于存储和传递元数据信息
 */
typedef struct _RTMPMetadata
{
    // video, must be h264 type
    unsigned int    nWidth;
    unsigned int    nHeight;
    unsigned int    nFrameRate;
    unsigned int    nSpsLen;
    unsigned char   *Sps;
    unsigned int    nPpsLen;
    unsigned char   *Pps;
} RTMPMetadata,*LPRTMPMetadata;

enum
{
     VIDEO_CODECID_H264 = 7,
};

class CRTMPStream
{
public:
    CRTMPStream(void);
    ~CRTMPStream(void);
public:
    //连接到RTMP Server
    bool Connect(const char* url);
    //断开连接
    void Disconnect();
    /**
     * 发送H264数据帧
     *
     * @param data 存储数据帧内容
     * @param size 数据帧的大小
     * @param bIsKeyFrame 记录该帧是否为关键帧
     * @param nTimeStamp 当前帧的时间戳
     *
     * @成功则返回 1 , 失败则返回0
     */
    bool SendH264Packet(unsigned char *data,unsigned int size,bool bIsKeyFrame,unsigned int nTimeStamp);
    /**
     * 将内存中的一段H.264编码的视频数据利用RTMP协议发送到服务器
     *
     * @param read_buffer 回调函数,当数据不足的时候,系统会自动调用该函数获取输入数据。
     *					2个参数功能:
     *					uint8_t *buf:外部数据送至该地址
     *					int buf_size:外部数据大小
     *					返回值:成功读取的内存大小
     * @成功则返回1 , 失败则返回0
     */
    int SendH264File(int (*read_buffer)(unsigned char *buf, int buf_size));
private:
    /**
     * 发送视频的sps和pps信息
     *
     * @param pps 存储视频的pps信息
     * @param pps_len 视频的pps信息长度
     * @param sps 存储视频的sps信息
     * @param sps_len 视频的sps信息长度
     *
     * @成功则返回 1 , 失败则返回0
     */
    int SendVideoSpsPps(unsigned char *pps,int pps_len,unsigned char * sps,int sps_len,int nTimeStamp);
    /**
     * 从内存中读取出第一个Nal单元
     *
     * @param nalu 存储nalu数据
     * @param read_buffer 回调函数,当数据不足的时候,系统会自动调用该函数获取输入数据。
     *					2个参数功能:
     *					uint8_t *buf:外部数据送至该地址
     *					int buf_size:外部数据大小
     *					返回值:成功读取的内存大小
     * @成功则返回 1 , 失败则返回0
     */
    int ReadFirstNaluFromBuf(NaluUnit &nalu,int (*read_buffer)(uint8_t *buf, int buf_size));
    /**
     * 从内存中读取出一个Nal单元
     *
     * @param nalu 存储nalu数据
     * @param read_buffer 回调函数,当数据不足的时候,系统会自动调用该函数获取输入数据。
     *					2个参数功能:
     *					uint8_t *buf:外部数据送至该地址
     *					int buf_size:外部数据大小
     *					返回值:成功读取的内存大小
     * @成功则返回 1 , 失败则返回0
     */
    int ReadOneNaluFromBuf(NaluUnit &nalu,int (*read_buffer)(uint8_t *buf, int buf_size));
    /**
     * 发送RTMP数据包
     *
     * @param nPacketType 数据类型
     * @param data 存储数据内容
     * @param size 数据大小
     * @param nTimestamp 当前包的时间戳
     *
     * @成功则返回 1 , 失败则返回一个小于0的数
     */
    int SendPacket(unsigned int nPacketType,unsigned char *data,unsigned int size,unsigned int nTimestamp);

private:
    unsigned int  m_nFileBufSize;
    unsigned int  nalhead_pos;
    RTMP* m_pRtmp;
    RTMPMetadata metaData;
    unsigned char *m_pFileBuf;
    unsigned char *m_pFileBuf_tmp;
    unsigned char* m_pFileBuf_tmp_old;
};

#endif // CRTMPSTREAM_H
crtmpstream.cpp

#include "crtmpstream.h"
#include "SpsDecode.h"
#ifdef WIN32
#include <windows.h>
#endif

CRTMPStream::CRTMPStream(void)
{
#ifdef WIN32
    WSADATA wsaData;
    WSAStartup(MAKEWORD(1, 1), &wsaData);
#endif
    nalhead_pos=0;
    m_nFileBufSize=BUFFER_SIZE;
    m_pFileBuf=(unsigned char*)malloc(BUFFER_SIZE);
    m_pFileBuf_tmp=(unsigned char*)malloc(BUFFER_SIZE);

    m_pRtmp = RTMP_Alloc();
    RTMP_Init(m_pRtmp);
}

CRTMPStream::~CRTMPStream(void)
{
#ifdef WIN32
    WSACleanup();
#endif
}

bool CRTMPStream::Connect(const char* url)
{
    if(RTMP_SetupURL(m_pRtmp, (char*)url)<0)
    {
        RTMP_Free(m_pRtmp);
        return FALSE;
    }

    RTMP_EnableWrite(m_pRtmp);

    if(RTMP_Connect(m_pRtmp, NULL)<0)
    {
        RTMP_Free(m_pRtmp);
        return FALSE;
    }

    if(RTMP_ConnectStream(m_pRtmp,0)<0)
    {
        RTMP_Close(m_pRtmp);
        RTMP_Free(m_pRtmp);
        return FALSE;
    }
    return TRUE;
}

void CRTMPStream::Disconnect()
{
    if(m_pRtmp)
    {
        RTMP_Close(m_pRtmp);
        RTMP_Free(m_pRtmp);
        m_pRtmp = NULL;
    }
    if (m_pFileBuf != NULL)
    {
        free(m_pFileBuf);
    }
    if (m_pFileBuf_tmp != NULL)
    {
        free(m_pFileBuf_tmp);
    }
}

int CRTMPStream::SendPacket(unsigned int nPacketType,unsigned char *data,unsigned int size,unsigned int nTimestamp)
{
    if(m_pRtmp == NULL)
    {
        return FALSE;
    }

    RTMPPacket* packet;

    packet = (RTMPPacket *)malloc(RTMP_HEAD_SIZE+size);
    memset(packet,0,RTMP_HEAD_SIZE);

    packet->m_body = (char *)packet + RTMP_HEAD_SIZE;
    packet->m_nBodySize = size;
    memcpy(packet->m_body,data,size);
    packet->m_hasAbsTimestamp = 0;
    packet->m_packetType = nPacketType;
    packet->m_nInfoField2 = m_pRtmp->m_stream_id;
    packet->m_nChannel = 0x04;

    packet->m_headerType = RTMP_PACKET_SIZE_LARGE;
    if (RTMP_PACKET_TYPE_AUDIO ==nPacketType && size !=4)
    {
        packet->m_headerType = RTMP_PACKET_SIZE_MEDIUM;
    }
    packet->m_nTimeStamp = nTimestamp;

    int nRet =0;
    if (RTMP_IsConnected(m_pRtmp))
    {
        nRet = RTMP_SendPacket(m_pRtmp,packet,TRUE); /*TRUE为放进发送队列,FALSE是不放进发送队列,直接发送*/
    }

    free(packet);
    return nRet;
}

bool CRTMPStream::SendH264Packet(unsigned char *data,unsigned int size,bool bIsKeyFrame,unsigned int nTimeStamp)
{
    if(data == NULL && size<11)
    {
        return false;
    }

    unsigned char *body = (unsigned char*)malloc(size+9);
    memset(body,0,size+9);

    int i = 0;
    if(bIsKeyFrame)
    {
        body[i++] = 0x17;// 1:Iframe  7:AVC
        body[i++] = 0x01;// AVC NALU
        body[i++] = 0x00;
        body[i++] = 0x00;
        body[i++] = 0x00;


        // NALU size
        body[i++] = size>>24 &0xff;
        body[i++] = size>>16 &0xff;
        body[i++] = size>>8 &0xff;
        body[i++] = size&0xff;
        // NALU data
        memcpy(&body[i],data,size);
        SendVideoSpsPps(metaData.Pps,metaData.nPpsLen,metaData.Sps,metaData.nSpsLen,nTimeStamp);
    }
    else
    {
        body[i++] = 0x27;// 2:Pframe  7:AVC
        body[i++] = 0x01;// AVC NALU
        body[i++] = 0x00;
        body[i++] = 0x00;
        body[i++] = 0x00;


        // NALU size
        body[i++] = size>>24 &0xff;
        body[i++] = size>>16 &0xff;
        body[i++] = size>>8 &0xff;
        body[i++] = size&0xff;
        // NALU data
        memcpy(&body[i],data,size);
    }

    int bRet = SendPacket(RTMP_PACKET_TYPE_VIDEO,body,i+size,nTimeStamp);

    free(body);

    return bRet;
}

int CRTMPStream::SendH264File(int (*read_buffer)(unsigned char *buf, int buf_size))
{
    int ret;
    uint32_t now,last_update;

    memset(&metaData,0,sizeof(RTMPMetadata));
    memset(m_pFileBuf,0,BUFFER_SIZE);
    if((ret=read_buffer(m_pFileBuf,m_nFileBufSize))<0)
    {
        return FALSE;
    }

    NaluUnit naluUnit;
    // 读取SPS帧
    ReadFirstNaluFromBuf(naluUnit,read_buffer);
    metaData.nSpsLen = naluUnit.size;
    metaData.Sps=NULL;
    metaData.Sps=(unsigned char*)malloc(naluUnit.size);
    memcpy(metaData.Sps,naluUnit.data,naluUnit.size);

    // 读取PPS帧
    ReadOneNaluFromBuf(naluUnit,read_buffer);
    metaData.nPpsLen = naluUnit.size;
    metaData.Pps=NULL;
    metaData.Pps=(unsigned char*)malloc(naluUnit.size);
    memcpy(metaData.Pps,naluUnit.data,naluUnit.size);

    // 解码SPS,获取视频图像宽、高信息
    int width = 0,height = 0, fps=0;
    h264_decode_sps(metaData.Sps,metaData.nSpsLen,width,height,fps);
    metaData.nWidth = width;
    metaData.nHeight = height;
    printf("Width:%8d\n",width);
    printf("Height:%8d\n",height);
    printf("FPS:%8d\n",fps);
    if(fps)
        metaData.nFrameRate = fps;
    else
        metaData.nFrameRate = 25;
    unsigned int tick = 0;
    unsigned int tick_gap = 1000/metaData.nFrameRate;
    ReadOneNaluFromBuf(naluUnit,read_buffer);
    int bKeyframe  = (naluUnit.type == 0x05) ? TRUE : FALSE;
    while(SendH264Packet(naluUnit.data,naluUnit.size,bKeyframe,tick))
    {
got_sps_pps:
        printf("NALU size:%8d\n",naluUnit.size);
        last_update=RTMP_GetTime();
        if(!ReadOneNaluFromBuf(naluUnit,read_buffer))
            goto end;
        if(naluUnit.type == 0x07 || naluUnit.type == 0x08)
            goto got_sps_pps;
        bKeyframe  = (naluUnit.type == 0x05) ? TRUE : FALSE;
        tick +=tick_gap;
        now=RTMP_GetTime();
        msleep(tick_gap-now+last_update);
    }
    end:
    free(metaData.Sps);
    free(metaData.Pps);
    return TRUE;
}

int CRTMPStream::ReadFirstNaluFromBuf(NaluUnit &nalu,int (*read_buffer)(uint8_t *buf, int buf_size))
{
    int naltail_pos=nalhead_pos;
    memset(m_pFileBuf_tmp,0,BUFFER_SIZE);
    while(nalhead_pos<m_nFileBufSize)
    {
        //search for nal header
        if(m_pFileBuf[nalhead_pos++] == 0x00 &&
                m_pFileBuf[nalhead_pos++] == 0x00)
        {
            if(m_pFileBuf[nalhead_pos++] == 0x01)
                goto gotnal_head;
            else
            {
                //cuz we have done an i++ before,so we need to roll back now
                nalhead_pos--;
                if(m_pFileBuf[nalhead_pos++] == 0x00 &&
                        m_pFileBuf[nalhead_pos++] == 0x01)
                    goto gotnal_head;
                else
                    continue;
            }
        }
        else
            continue;

        //search for nal tail which is also the head of next nal
gotnal_head:
        //normal case:the whole nal is in this m_pFileBuf
        naltail_pos = nalhead_pos;
        while (naltail_pos<m_nFileBufSize)
        {
            if(m_pFileBuf[naltail_pos++] == 0x00 &&
                    m_pFileBuf[naltail_pos++] == 0x00 )
            {
                if(m_pFileBuf[naltail_pos++] == 0x01)
                {
                    nalu.size = (naltail_pos-3)-nalhead_pos;
                    break;
                }
                else
                {
                    naltail_pos--;
                    if(m_pFileBuf[naltail_pos++] == 0x00 &&
                            m_pFileBuf[naltail_pos++] == 0x01)
                    {
                        nalu.size = (naltail_pos-4)-nalhead_pos;
                        break;
                    }
                }
            }
        }

        nalu.type = m_pFileBuf[nalhead_pos]&0x1f;
        memcpy(m_pFileBuf_tmp,m_pFileBuf+nalhead_pos,nalu.size);
        nalu.data=m_pFileBuf_tmp;
        nalhead_pos=naltail_pos;
        return TRUE;
    }
}

int CRTMPStream::ReadOneNaluFromBuf(NaluUnit &nalu,int (*read_buffer)(uint8_t *buf, int buf_size))
{

    int naltail_pos=nalhead_pos;
    int ret;
    int nalustart;//nal的开始标识符是几个00
    memset(m_pFileBuf_tmp,0,BUFFER_SIZE);
    nalu.size=0;
    while(1)
    {
        if(nalhead_pos==NO_MORE_BUFFER_TO_READ)
            return FALSE;
        while(naltail_pos<m_nFileBufSize)
        {
            //search for nal tail
            if(m_pFileBuf[naltail_pos++] == 0x00 &&
                    m_pFileBuf[naltail_pos++] == 0x00)
            {
                if(m_pFileBuf[naltail_pos++] == 0x01)
                {
                    nalustart=3;
                    goto gotnal ;
                }
                else
                {
                    //cuz we have done an i++ before,so we need to roll back now
                    naltail_pos--;
                    if(m_pFileBuf[naltail_pos++] == 0x00 &&
                            m_pFileBuf[naltail_pos++] == 0x01)
                    {
                        nalustart=4;
                        goto gotnal;
                    }
                    else
                        continue;
                }
            }
            else
                continue;

gotnal:
            if(nalhead_pos==GOT_A_NAL_CROSS_BUFFER || nalhead_pos==GOT_A_NAL_INCLUDE_A_BUFFER)
            {
                nalu.size = nalu.size+naltail_pos-nalustart;
                if(nalu.size>BUFFER_SIZE)
                {
                    m_pFileBuf_tmp_old=m_pFileBuf_tmp;	//// save pointer in case realloc fails
                    if((m_pFileBuf_tmp = (unsigned char*)realloc(m_pFileBuf_tmp,nalu.size)) ==  NULL )
                    {
                        free( m_pFileBuf_tmp_old );  // free original block
                        return FALSE;
                    }
                }
                memcpy(m_pFileBuf_tmp+nalu.size+nalustart-naltail_pos,m_pFileBuf,naltail_pos-nalustart);
                nalu.data=m_pFileBuf_tmp;
                nalhead_pos=naltail_pos;
                return TRUE;
            }
            //normal case:the whole nal is in this m_pFileBuf
            else
            {
                nalu.type = m_pFileBuf[nalhead_pos]&0x1f;
                nalu.size=naltail_pos-nalhead_pos-nalustart;
                if(nalu.type==0x06)
                {
                    nalhead_pos=naltail_pos;
                    continue;
                }
                memcpy(m_pFileBuf_tmp,m_pFileBuf+nalhead_pos,nalu.size);
                nalu.data=m_pFileBuf_tmp;
                nalhead_pos=naltail_pos;
                return TRUE;
            }
        }

        if(naltail_pos>=m_nFileBufSize && nalhead_pos!=GOT_A_NAL_CROSS_BUFFER && nalhead_pos != GOT_A_NAL_INCLUDE_A_BUFFER)
        {
            nalu.size = BUFFER_SIZE-nalhead_pos;
            nalu.type = m_pFileBuf[nalhead_pos]&0x1f;
            memcpy(m_pFileBuf_tmp,m_pFileBuf+nalhead_pos,nalu.size);
            if((ret=read_buffer(m_pFileBuf,m_nFileBufSize))<BUFFER_SIZE)
            {
                memcpy(m_pFileBuf_tmp+nalu.size,m_pFileBuf,ret);
                nalu.size=nalu.size+ret;
                nalu.data=m_pFileBuf_tmp;
                nalhead_pos=NO_MORE_BUFFER_TO_READ;
                return FALSE;
            }
            naltail_pos=0;
            nalhead_pos=GOT_A_NAL_CROSS_BUFFER;
            continue;
        }
        if(nalhead_pos==GOT_A_NAL_CROSS_BUFFER || nalhead_pos == GOT_A_NAL_INCLUDE_A_BUFFER)
        {
            nalu.size = BUFFER_SIZE+nalu.size;

            m_pFileBuf_tmp_old=m_pFileBuf_tmp;	//// save pointer in case realloc fails
            if((m_pFileBuf_tmp = (unsigned char*)realloc(m_pFileBuf_tmp,nalu.size)) ==  NULL )
            {
                free( m_pFileBuf_tmp_old );  // free original block
                return FALSE;
            }

            memcpy(m_pFileBuf_tmp+nalu.size-BUFFER_SIZE,m_pFileBuf,BUFFER_SIZE);

            if((ret=read_buffer(m_pFileBuf,m_nFileBufSize))<BUFFER_SIZE)
            {
                memcpy(m_pFileBuf_tmp+nalu.size,m_pFileBuf,ret);
                nalu.size=nalu.size+ret;
                nalu.data=m_pFileBuf_tmp;
                nalhead_pos=NO_MORE_BUFFER_TO_READ;
                return FALSE;
            }
            naltail_pos=0;
            nalhead_pos=GOT_A_NAL_INCLUDE_A_BUFFER;
            continue;
        }
    }
    return FALSE;
}

int CRTMPStream::SendVideoSpsPps(unsigned char *pps,int pps_len,unsigned char * sps,int sps_len,int nTimeStamp)
{
    RTMPPacket * packet=NULL;
    unsigned char * body=NULL;
    int i;
    packet = (RTMPPacket *)malloc(RTMP_HEAD_SIZE+1024);
    //RTMPPacket_Reset(packet);//重置packet状态
    memset(packet,0,RTMP_HEAD_SIZE+1024);
    packet->m_body = (char *)packet + RTMP_HEAD_SIZE;
    body = (unsigned char *)packet->m_body;
    i = 0;
    body[i++] = 0x17;
    body[i++] = 0x00;

    body[i++] = 0x00;
    body[i++] = 0x00;
    body[i++] = 0x00;

    //AVCDecoderConfigurationRecord
    body[i++] = 0x01;
    body[i++] = sps[1];
    body[i++] = sps[2];
    body[i++] = sps[3];
    body[i++] = 0xff;

    //sps
    body[i++]   = 0xe1;
    body[i++] = (sps_len >> 8) & 0xff;
    body[i++] = sps_len & 0xff;
    memcpy(&body[i],sps,sps_len);
    i +=  sps_len;

    //pps
    body[i++]   = 0x01;
    body[i++] = (pps_len >> 8) & 0xff;
    body[i++] = (pps_len) & 0xff;
    memcpy(&body[i],pps,pps_len);
    i +=  pps_len;

    packet->m_packetType = RTMP_PACKET_TYPE_VIDEO;
    packet->m_nBodySize = i;
    packet->m_nChannel = 0x04;
    packet->m_nTimeStamp = nTimeStamp;
    packet->m_hasAbsTimestamp = 0;
    packet->m_headerType = RTMP_PACKET_SIZE_MEDIUM;
    packet->m_nInfoField2 = m_pRtmp->m_stream_id;

    int nRet = RTMP_SendPacket(m_pRtmp,packet,TRUE);
    free(packet);
    return nRet;
}
spsdecode.h在【5】中

#include "crtmpstream.h"

FILE *fp_send1;

//读文件的回调函数
int read_buffer1(unsigned char *buf, int buf_size ){
    if(!feof(fp_send1)){
        int true_size=fread(buf,1,buf_size,fp_send1);
        return true_size;
    }else{
        return -1;
    }
}
int main()
{
    fp_send1 = fopen("480320.264", "rb");

    CRTMPStream rtmpSender;
    rtmpSender.Connect("rtmp://localhost/live/livestream");
    rtmpSender.SendH264File(read_buffer1);
    rtmpSender.Disconnect();

    return 0;
}
二.测试

服务器使用Adobe Media Server 5,详见【3】和【4】。

测试效果如下所示:



源码基于最简单的基于librtmp的示例:发布H.264(H.264通过RTMP发布)修改而来,感谢前人的开源。

最简单的基于librtmp的示例:发布H.264(H.264通过RTMP发布)中发现两处小bug,一个在该文的评论中有热心人 回复了,另一个在spsdecode.h中关于帧率的计算,现在都已经修正。


源码链接:见http://blog.csdn.net/caoshangpa/article/details/53125949的评论



展开阅读全文

没有更多推荐了,返回首页