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_Hcrtmpstream.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
- Qt基于librtmp推送H.264
- 基于Flex的实时H.264流转发平台之流媒体推送端(librtmp)
- 最简单的基于librtmp的示例:发布H.264(H.264通过RTMP发布)
- 最简单的基于librtmp的示例:发布H.264(H.264通过RTMP发布)
- 最简单的基于librtmp的示例:发布H.264(H.264通过RTMP发布)
- 最简单的基于librtmp的示例:发布H.264(H.264通过RTMP发布)
- 最简单的基于librtmp的示例:发布H.264(H.264通过RTMP发布)
- 最简单的基于librtmp的示例:发布H.264(H.264通过RTMP发布)
- 【基于libRTMP的流媒体直播之 AAC、H264 推送】
- 基于libRTMP的流媒体直播之 AAC、H264 推送
- 基于libRTMP的流媒体直播之 AAC、H264 推送
- 基于libRTMP的流媒体直播之 AAC、H264 推送
- 【基于libRTMP的流媒体直播之 AAC、H264 推送】
- 【基于libRTMP的流媒体直播之 AAC、H264 推送】
- 基于libRTMP的流媒体直播之音频推送
- 基于libRTMP的流媒体直播之 AAC、H264 推送
- 【基于libRTMP的流媒体直播之 AAC、H264 推送】
- 基于libRTMP的流媒体直播之 AAC、H264 推送
- 如何向云计算迁移
- NumPy学习笔记
- 对象自己的集合
- hid的几个阶段
- int 和Integer之间的差别
- Qt基于librtmp推送H.264
- 使用spring实现一个MVC
- gcc和arm-linux-gcc默认头文件库搜索路径
- 网络五层协议-与每层对应的协议
- cpp5.17
- 动态切换App桌面icon跟text,让你的应用炫起来(Android)!!
- 新手要想学好Linux系统就必须做好这四件事情
- duplicate symbol /undefind symbol出现的原因
- ViewController的加载方式