Qt基于librtmp推送H.264

来源:互联网 发布:fpga和单片机哪个好 编辑:程序博客网 时间:2024/04/30 10:11

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>#endifCRTMPStream::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 nalgotnal_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的评论



3 0
原创粉丝点击