11-流媒体-LibRtmp推H264流

2023-12-30 22:28
文章标签 流媒体 h264 librtmp

本文主要是介绍11-流媒体-LibRtmp推H264流,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

本节内容主要描述如何将一个h264文件通过RTMP推到流媒体服务器上
工程目录如下

$ tree 
.
|-- Makefile
|-- inc
|   |-- librtmp_send264.h
|   `-- sps_decode.h
|-- input.h264
|-- obj
|   |-- librtmp_send264.o
|   `-- test.o
|-- src
|   |-- librtmp_send264.cpp
|   `-- test.cpp
|-- third_lib
|   |-- openssl
|   |   |-- include
|   |   |   `-- openssl
|   |   |       |-- aes.h
|   |   |       |-- asn1.h
|   |   |       |-- asn1_mac.h
|   |   |       |-- asn1t.h
|   |   |       |-- bio.h
|   |   |       |-- blowfish.h
|   |   |       |-- bn.h
|   |   |       |-- buffer.h
|   |   |       |-- camellia.h
|   |   |       |-- cast.h
|   |   |       |-- cmac.h
|   |   |       |-- cms.h
|   |   |       |-- comp.h
|   |   |       |-- conf.h
|   |   |       |-- conf_api.h
|   |   |       |-- crypto.h
|   |   |       |-- des.h
|   |   |       |-- des_old.h
|   |   |       |-- dh.h
|   |   |       |-- dsa.h
|   |   |       |-- dso.h
|   |   |       |-- dtls1.h
|   |   |       |-- e_os2.h
|   |   |       |-- ebcdic.h
|   |   |       |-- ec.h
|   |   |       |-- ecdh.h
|   |   |       |-- ecdsa.h
|   |   |       |-- engine.h
|   |   |       |-- err.h
|   |   |       |-- evp.h
|   |   |       |-- hmac.h
|   |   |       |-- idea.h
|   |   |       |-- krb5_asn.h
|   |   |       |-- kssl.h
|   |   |       |-- lhash.h
|   |   |       |-- md4.h
|   |   |       |-- md5.h
|   |   |       |-- mdc2.h
|   |   |       |-- modes.h
|   |   |       |-- obj_mac.h
|   |   |       |-- objects.h
|   |   |       |-- ocsp.h
|   |   |       |-- opensslconf.h
|   |   |       |-- opensslv.h
|   |   |       |-- ossl_typ.h
|   |   |       |-- pem.h
|   |   |       |-- pem2.h
|   |   |       |-- pkcs12.h
|   |   |       |-- pkcs7.h
|   |   |       |-- pqueue.h
|   |   |       |-- rand.h
|   |   |       |-- rc2.h
|   |   |       |-- rc4.h
|   |   |       |-- ripemd.h
|   |   |       |-- rsa.h
|   |   |       |-- safestack.h
|   |   |       |-- seed.h
|   |   |       |-- sha.h
|   |   |       |-- srp.h
|   |   |       |-- srtp.h
|   |   |       |-- ssl.h
|   |   |       |-- ssl2.h
|   |   |       |-- ssl23.h
|   |   |       |-- ssl3.h
|   |   |       |-- stack.h
|   |   |       |-- symhacks.h
|   |   |       |-- tls1.h
|   |   |       |-- ts.h
|   |   |       |-- txt_db.h
|   |   |       |-- ui.h
|   |   |       |-- ui_compat.h
|   |   |       |-- whrlpool.h
|   |   |       |-- x509.h
|   |   |       |-- x509_vfy.h
|   |   |       `-- x509v3.h
|   |   `-- lib
|   |       |-- libcrypto.so -> libcrypto.so.1.0.0
|   |       |-- libcrypto.so.1.0.0
|   |       |-- libssl.so -> libssl.so.1.0.0
|   |       `-- libssl.so.1.0.0
|   |-- rtmpdump
|   |   |-- include
|   |   |   `-- librtmp
|   |   |       |-- amf.h
|   |   |       |-- http.h
|   |   |       |-- log.h
|   |   |       `-- rtmp.h
|   |   `-- lib
|   |       |-- librtmp.so -> librtmp.so.0
|   |       `-- librtmp.so.0
|   `-- zlib
|       |-- include
|       |   |-- zconf.h
|       |   `-- zlib.h
|       `-- lib
|           |-- libz.so -> libz.so.1.2.8
|           |-- libz.so.1 -> libz.so.1.2.8
|           `-- libz.so.1.2.8
`-- video_prj

工程中的内容包括第三方如下文件
test.cpp
librtmp_send264.cpp
librtmp_send264.h
sps_decode.h
Makefile

下面分别介绍
test.cpp

#include <stdio.h>
#include "librtmp_send264.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(int argc, char* argv[])
{fp_send1 = fopen("input.h264", "rb");//初始化并连接到服务器RTMP264_Connect("rtmp://127.0.0.1:1935/live/xx");//发送RTMP264_Send(read_buffer1);//断开连接并释放相关资源RTMP264_Close();return 0;
}

librtmp_send264.cpp

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>  
#include "librtmp_send264.h"
#include "librtmp/rtmp.h"   
//#include "librtmp\rtmp_sys.h"   
#include "librtmp/amf.h"  
#include "sps_decode.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,  
};  /*** 初始化winsock*					* @成功则返回1 , 失败则返回相应错误代码*/ 
int InitSockets()    
{    #ifdef WIN32     WORD version;    WSADATA wsaData;    version = MAKEWORD(1, 1);    return (WSAStartup(version, &wsaData) == 0);    #else     return TRUE;    #endif     
}/*** 释放winsock*					* @成功则返回0 , 失败则返回相应错误代码*/ 
inline void CleanupSockets()    
{    #ifdef WIN32     WSACleanup();    #endif     
}    //网络字节序转换
char * put_byte( char *output, uint8_t nVal )    
{    output[0] = nVal;    return output+1;    
}   char * put_be16(char *output, uint16_t nVal )    
{    output[1] = nVal & 0xff;    output[0] = nVal >> 8;    return output+2;    
}  char * put_be24(char *output,uint32_t nVal )    
{    output[2] = nVal & 0xff;    output[1] = nVal >> 8;    output[0] = nVal >> 16;    return output+3;    
}    
char * put_be32(char *output, uint32_t nVal )    
{    output[3] = nVal & 0xff;    output[2] = nVal >> 8;    output[1] = nVal >> 16;    output[0] = nVal >> 24;    return output+4;    
}    
char *  put_be64( char *output, uint64_t nVal )    
{    output=put_be32( output, nVal >> 32 );    output=put_be32( output, nVal );    return output;    
}  char * put_amf_string( char *c, const char *str )    
{    uint16_t len = strlen( str );    c=put_be16( c, len );    memcpy(c,str,len);    return c+len;    
}    
char * put_amf_double( char *c, double d )    
{    *c++ = AMF_NUMBER;  /* type: Number */    {    unsigned char *ci, *co;    ci = (unsigned char *)&d;    co = (unsigned char *)c;    co[0] = ci[7];    co[1] = ci[6];    co[2] = ci[5];    co[3] = ci[4];    co[4] = ci[3];    co[5] = ci[2];    co[6] = ci[1];    co[7] = ci[0];    }    return c+8;    
}  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;	//used for realloc/*** 初始化并连接到服务器** @param url 服务器上对应webapp的地址*					* @成功则返回1 , 失败则返回0*/ 
int RTMP264_Connect(const char* url)  
{  nalhead_pos=0;m_nFileBufSize=BUFFER_SIZE;m_pFileBuf=(unsigned char*)malloc(BUFFER_SIZE);m_pFileBuf_tmp=(unsigned char*)malloc(BUFFER_SIZE);InitSockets();  m_pRtmp = RTMP_Alloc();RTMP_Init(m_pRtmp);/*设置URL*/if (RTMP_SetupURL(m_pRtmp,(char*)url) == FALSE){RTMP_Free(m_pRtmp);return false;}/*设置可写,即发布流,这个函数必须在连接前使用,否则无效*/RTMP_EnableWrite(m_pRtmp);/*连接服务器*/if (RTMP_Connect(m_pRtmp, NULL) == FALSE) {RTMP_Free(m_pRtmp);return false;} /*连接流*/if (RTMP_ConnectStream(m_pRtmp,0) == FALSE){RTMP_Close(m_pRtmp);RTMP_Free(m_pRtmp);return false;}return true;  
}  /*** 断开连接,释放相关的资源。**/    
void RTMP264_Close()  
{  if(m_pRtmp)  {  RTMP_Close(m_pRtmp);  RTMP_Free(m_pRtmp);  m_pRtmp = NULL;  }  CleanupSockets();   if (m_pFileBuf != NULL){  free(m_pFileBuf);}  if (m_pFileBuf_tmp != NULL){  free(m_pFileBuf_tmp);}
} /*** 发送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)  
{  RTMPPacket* packet;/*分配包内存和初始化,len为包体长度*/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;  
}  /*** 发送视频的sps和pps信息** @param pps 存储视频的pps信息* @param pps_len 视频的pps信息长度* @param sps 存储视频的pps信息* @param sps_len 视频的sps信息长度** @成功则返回 1 , 失败则返回0*/
int SendVideoSpsPps(unsigned char *pps,int pps_len,unsigned char * sps,int sps_len)
{RTMPPacket * packet=NULL;//rtmp包结构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 = 0;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;
}/*** 发送H264数据帧** @param data 存储数据帧内容* @param size 数据帧的大小* @param bIsKeyFrame 记录该帧是否为关键帧* @param nTimeStamp 当前帧的时间戳** @成功则返回 1 , 失败则返回0*/
int SendH264Packet(unsigned char *data,unsigned int size,int 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);}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;  
} /*** 从内存中读取出第一个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)) 
{int naltail_pos=nalhead_pos;memset(m_pFileBuf_tmp,0,BUFFER_SIZE);while(nalhead_pos<m_nFileBufSize)  {  //search for nal headerif(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 nownalhead_pos--;		if(m_pFileBuf[nalhead_pos++] == 0x00 && m_pFileBuf[nalhead_pos++] == 0x01)goto gotnal_head;elsecontinue;}}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_pFileBufnaltail_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;   		}
}/*** 从内存中读取出一个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))  
{    int naltail_pos=nalhead_pos;int ret;int nalustart;//nal的开始标识符是几个00memset(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 tailif(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 nownaltail_pos--;		if(m_pFileBuf[naltail_pos++] == 0x00 && m_pFileBuf[naltail_pos++] == 0x01){nalustart=4;goto gotnal;}elsecontinue;}}else continue;gotnal:	/***special case1:parts of the nal lies in a m_pFileBuf and we have to read from buffer *again to get the rest part of this nal*/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 failsif((m_pFileBuf_tmp = (unsigned char*)realloc(m_pFileBuf_tmp,nalu.size)) ==  NULL ){free( m_pFileBuf_tmp_old );  // free original blockreturn 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_pFileBufelse {  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 failsif((m_pFileBuf_tmp = (unsigned char*)realloc(m_pFileBuf_tmp,nalu.size)) ==  NULL ){free( m_pFileBuf_tmp_old );  // free original blockreturn 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;  
} /*** 将内存中的一段H.264编码的视频数据利用RTMP协议发送到服务器** @param read_buffer 回调函数,当数据不足的时候,系统会自动调用该函数获取输入数据。*					2个参数功能:*					uint8_t *buf:外部数据送至该地址*					int buf_size:外部数据大小*					返回值:成功读取的内存大小* @成功则返回1 , 失败则返回0*/ 
int RTMP264_Send(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;  if(fps)metaData.nFrameRate = fps; elsemetaData.nFrameRate = 25;//发送PPS,SPS//ret=SendVideoSpsPps(metaData.Pps,metaData.nPpsLen,metaData.Sps,metaData.nSpsLen);//if(ret!=1)//	return FALSE;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://if(naluUnit.size==8581)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();usleep((tick_gap-now+last_update)*1000);  //msleep(40);}  end:free(metaData.Sps);free(metaData.Pps);return TRUE;  
}  

librtmp_send264.h


/*** 初始化并连接到服务器** @param url 服务器上对应webapp的地址*					* @成功则返回1 , 失败则返回0*/ 
int RTMP264_Connect(const char* url);    /*** 将内存中的一段H.264编码的视频数据利用RTMP协议发送到服务器** @param read_buffer 回调函数,当数据不足的时候,系统会自动调用该函数获取输入数据。*					2个参数功能:*					uint8_t *buf:外部数据送至该地址*					int buf_size:外部数据大小*					返回值:成功读取的内存大小* @成功则返回1 , 失败则返回0*/ 
int RTMP264_Send(int (*read_buffer)(unsigned char *buf, int buf_size));/*** 断开连接,释放相关的资源。**/    
void RTMP264_Close(); 

sps_decode.h

#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <math.h>typedef  unsigned int UINT;
typedef  unsigned char BYTE;
typedef  unsigned long DWORD;UINT Ue(BYTE *pBuff, UINT nLen, UINT &nStartBit)
{//计算0bit的个数UINT nZeroNum = 0;while (nStartBit < nLen * 8){if (pBuff[nStartBit / 8] & (0x80 >> (nStartBit % 8))) //&:按位与,%取余{break;}nZeroNum++;nStartBit++;}nStartBit ++;//计算结果DWORD dwRet = 0;for (UINT i=0; i<nZeroNum; i++){dwRet <<= 1;if (pBuff[nStartBit / 8] & (0x80 >> (nStartBit % 8))){dwRet += 1;}nStartBit++;}return (1 << nZeroNum) - 1 + dwRet;
}int Se(BYTE *pBuff, UINT nLen, UINT &nStartBit)
{int UeVal=Ue(pBuff,nLen,nStartBit);double k=UeVal;int nValue=ceil(k/2);//ceil函数:ceil函数的作用是求不小于给定实数的最小整数。ceil(2)=ceil(1.2)=cei(1.5)=2.00if (UeVal % 2==0)nValue=-nValue;return nValue;
}DWORD u(UINT BitCount,BYTE * buf,UINT &nStartBit)
{DWORD dwRet = 0;for (UINT i=0; i<BitCount; i++){dwRet <<= 1;if (buf[nStartBit / 8] & (0x80 >> (nStartBit % 8))){dwRet += 1;}nStartBit++;}return dwRet;
}/*** H264的NAL起始码防竞争机制 ** @param buf SPS数据内容** @无返回值*/ 
void de_emulation_prevention(BYTE* buf,unsigned int* buf_size)
{int i=0,j=0;BYTE* tmp_ptr=NULL;unsigned int tmp_buf_size=0;int val=0;tmp_ptr=buf;tmp_buf_size=*buf_size;for(i=0;i<(tmp_buf_size-2);i++){//check for 0x000003val=(tmp_ptr[i]^0x00) +(tmp_ptr[i+1]^0x00)+(tmp_ptr[i+2]^0x03);if(val==0){//kick out 0x03for(j=i+2;j<tmp_buf_size-1;j++)tmp_ptr[j]=tmp_ptr[j+1];//and so we should devrease bufsize(*buf_size)--;}}return;
}/*** 解码SPS,获取视频图像宽、高信息 ** @param buf SPS数据内容* @param nLen SPS数据的长度* @param width 图像宽度* @param height 图像高度* @成功则返回1 , 失败则返回0*/ 
int h264_decode_sps(BYTE * buf,unsigned int nLen,int &width,int &height,int &fps)
{UINT StartBit=0; fps=0;de_emulation_prevention(buf,&nLen);int forbidden_zero_bit=u(1,buf,StartBit);int nal_ref_idc=u(2,buf,StartBit);int nal_unit_type=u(5,buf,StartBit);if(nal_unit_type==7){int profile_idc=u(8,buf,StartBit);int constraint_set0_flag=u(1,buf,StartBit);//(buf[1] & 0x80)>>7;int constraint_set1_flag=u(1,buf,StartBit);//(buf[1] & 0x40)>>6;int constraint_set2_flag=u(1,buf,StartBit);//(buf[1] & 0x20)>>5;int constraint_set3_flag=u(1,buf,StartBit);//(buf[1] & 0x10)>>4;int reserved_zero_4bits=u(4,buf,StartBit);int level_idc=u(8,buf,StartBit);int seq_parameter_set_id=Ue(buf,nLen,StartBit);if( profile_idc == 100 || profile_idc == 110 ||profile_idc == 122 || profile_idc == 144 ){int chroma_format_idc=Ue(buf,nLen,StartBit);if( chroma_format_idc == 3 )int residual_colour_transform_flag=u(1,buf,StartBit);int bit_depth_luma_minus8=Ue(buf,nLen,StartBit);int bit_depth_chroma_minus8=Ue(buf,nLen,StartBit);int qpprime_y_zero_transform_bypass_flag=u(1,buf,StartBit);int seq_scaling_matrix_present_flag=u(1,buf,StartBit);int seq_scaling_list_present_flag[8];if( seq_scaling_matrix_present_flag ){for( int i = 0; i < 8; i++ ) {seq_scaling_list_present_flag[i]=u(1,buf,StartBit);}}}int log2_max_frame_num_minus4=Ue(buf,nLen,StartBit);int pic_order_cnt_type=Ue(buf,nLen,StartBit);if( pic_order_cnt_type == 0 )int log2_max_pic_order_cnt_lsb_minus4=Ue(buf,nLen,StartBit);else if( pic_order_cnt_type == 1 ){int delta_pic_order_always_zero_flag=u(1,buf,StartBit);int offset_for_non_ref_pic=Se(buf,nLen,StartBit);int offset_for_top_to_bottom_field=Se(buf,nLen,StartBit);int num_ref_frames_in_pic_order_cnt_cycle=Ue(buf,nLen,StartBit);int *offset_for_ref_frame=new int[num_ref_frames_in_pic_order_cnt_cycle];for( int i = 0; i < num_ref_frames_in_pic_order_cnt_cycle; i++ )offset_for_ref_frame[i]=Se(buf,nLen,StartBit);delete [] offset_for_ref_frame;}int num_ref_frames=Ue(buf,nLen,StartBit);int gaps_in_frame_num_value_allowed_flag=u(1,buf,StartBit);int pic_width_in_mbs_minus1=Ue(buf,nLen,StartBit);int pic_height_in_map_units_minus1=Ue(buf,nLen,StartBit);width=(pic_width_in_mbs_minus1+1)*16;height=(pic_height_in_map_units_minus1+1)*16;int frame_mbs_only_flag=u(1,buf,StartBit);if(!frame_mbs_only_flag)int mb_adaptive_frame_field_flag=u(1,buf,StartBit);int direct_8x8_inference_flag=u(1,buf,StartBit);int frame_cropping_flag=u(1,buf,StartBit);if(frame_cropping_flag){int frame_crop_left_offset=Ue(buf,nLen,StartBit);int frame_crop_right_offset=Ue(buf,nLen,StartBit);int frame_crop_top_offset=Ue(buf,nLen,StartBit);int frame_crop_bottom_offset=Ue(buf,nLen,StartBit);}int vui_parameter_present_flag=u(1,buf,StartBit);if(vui_parameter_present_flag){int aspect_ratio_info_present_flag=u(1,buf,StartBit);              if(aspect_ratio_info_present_flag){int aspect_ratio_idc=u(8,buf,StartBit);   if(aspect_ratio_idc==255){int sar_width=u(16,buf,StartBit);                                  int sar_height=u(16,buf,StartBit);                                      }}int overscan_info_present_flag=u(1,buf,StartBit); if(overscan_info_present_flag)int overscan_appropriate_flagu=u(1,buf,StartBit);                   int video_signal_type_present_flag=u(1,buf,StartBit); if(video_signal_type_present_flag){int video_format=u(3,buf,StartBit);                         int video_full_range_flag=u(1,buf,StartBit);                       int colour_description_present_flag=u(1,buf,StartBit);if(colour_description_present_flag){int colour_primaries=u(8,buf,StartBit);              int transfer_characteristics=u(8,buf,StartBit);                     int matrix_coefficients=u(8,buf,StartBit);                  		}}int chroma_loc_info_present_flag=u(1,buf,StartBit);  if(chroma_loc_info_present_flag){int chroma_sample_loc_type_top_field=Ue(buf,nLen,StartBit);             int chroma_sample_loc_type_bottom_field=Ue(buf,nLen,StartBit);       }int timing_info_present_flag=u(1,buf,StartBit);        if(timing_info_present_flag){int num_units_in_tick=u(32,buf,StartBit);                              int time_scale=u(32,buf,StartBit);    fps=time_scale/(2*num_units_in_tick);}}return true;}elsereturn false;
}

Makefile

PRJ_PATH	= .
TARGETS = video_prj
DIR_INC = ./inc
DIR_SRC = ./src
DIR_OBJ = ./obj
CXX = g++SRC := $(wildcard  ${DIR_SRC}/*.cpp)
OBJ := $(patsubst ${DIR_SRC}/%.cpp,$(DIR_OBJ)/%.o,$(SRC))ZLIB_LIB_DIR	= $(PRJ_PATH)/third_lib/zlib/lib
ZLIB_INC_DIR	= $(PRJ_PATH)/third_lib/zlib/includeOPENSSL_LIB_DIR	= $(PRJ_PATH)/third_lib/openssl/lib
OPENSSL_INC_DIR	= $(PRJ_PATH)/third_lib/openssl/includeRTMP_LIB_DIR	= $(PRJ_PATH)/third_lib/rtmpdump/lib
RTMP_INC_DIR	= $(PRJ_PATH)/third_lib/rtmpdump/includeINCLUDES	= -I$(DIR_INC)
INCLUDES	+= -I$(ZLIB_INC_DIR)
INCLUDES	+= -I$(OPENSSL_INC_DIR)
INCLUDES	+= -I$(RTMP_INC_DIR)LDFLAGS =  -lpthread 
LDFLAGS += -L$(RTMP_LIB_DIR) -lrtmp
LDFLAGS += -L$(ZLIB_LIB_DIR) -lz
LDFLAGS += -L$(OPENSSL_LIB_DIR) -lcrypto -lsslCXXFLAGS = -fpermissive
CXXFLAGS += -fPIC CFLAGS += $(INCLUDES)$(TARGETS):$(OBJ)$(CXX) $^ -o $@ $(LDFLAGS) $(CXXFLAGS)$(DIR_OBJ)/%.o:$(DIR_SRC)/%.cpp$(CXX) $(CFLAGS) -c $< -o $@ $(CXXFLAGS)
clean:rm -f $(TARGETS)rm -f $(DIR_OBJ)/*.o

所有代码编写完后,编译代码,然后执行推流进程,进程就会往服务器推流。关于服务器的启动可参考
https://blog.csdn.net/sishen4199/article/details/133850730
另外本章内容,拉流的客户端作者使用的是vlc软件作为验证,关于vlc如何拉RTMP,本文不具体描述。

这篇关于11-流媒体-LibRtmp推H264流的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/554184

相关文章

流媒体平台/视频监控/安防视频汇聚EasyCVR播放暂停后视频画面黑屏是什么原因?

视频智能分析/视频监控/安防监控综合管理系统EasyCVR视频汇聚融合平台,是TSINGSEE青犀视频垂直深耕音视频流媒体技术、AI智能技术领域的杰出成果。该平台以其强大的视频处理、汇聚与融合能力,在构建全栈视频监控系统中展现出了独特的优势。视频监控管理系统EasyCVR平台内置了强大的视频解码、转码、压缩等技术,能够处理多种视频流格式,并以多种格式(RTMP、RTSP、HTTP-FLV、WebS

rtmp流媒体编程相关整理2013(crtmpserver,rtmpdump,x264,faac)

转自:http://blog.163.com/zhujiatc@126/blog/static/1834638201392335213119/ 相关资料在线版(不定时更新,其实也不会很多,也许一两个月也不会改) http://www.zhujiatc.esy.es/crtmpserver/index.htm 去年在这进行rtmp相关整理,其实内容早有了,只是整理一下看着方

RTMP流媒体服务器 crtmpserver

http://www.oschina.net/p/crtmpserver crtmpserver又称rtmpd是Evostream Media Server(www.evostream.com)的社区版本采用GPLV3授权 其主要作用为一个高性能的RTMP流媒体服务器,可以实现直播与点播功能多终端支持功能,在特定情况下是FMS的良好替代品。 支持RTMP的一堆协议(RT

流媒体与直播的基础理论(其一)

欢迎诸位来阅读在下的博文~ 在这里,在下会不定期发表一些浅薄的知识和经验,望诸位能与在下多多交流,共同努力 文章目录 一、流媒体简介二、流媒体协议常见的流媒体协议 三、视频直播原理与流程通用的视频直播模型视频直播链路 一、流媒体简介 流媒体是指将一连串的媒体数据压缩后,经过网上分段发送数据,在网上及时传输影音以供观赏的一种技术与过程,此技术使数据得以像流水一样发送;如果不

LiveQing视频点播流媒体RTMP推流服务功能-支持大疆等无人机RTMP推流支持OBS推流一步一步搭建RTMP视频流媒体服务示例

LiveQing支持大疆等无人机RTMP推流支持OBS推流一步一步搭建RTMP视频流媒体服务示例 1、流媒体服务搭建2、推流工具准备3、创建鉴权直播间4、获取推流地址5、配置OBS推流6、推流及播放7、获取播放地址7.1 页面查看视频源地址7.2 接口查询 8、相关问题8.1、大疆无人机推流花屏 9、RTMP推流视频直播和点播流媒体服务 1、流媒体服务搭建 Windows/Lin

LiveGBS流媒体平台GB/T28181功能-支持RTSP流分发支持GB28181国标流转RTSP流如何配置RTSP视频直播流输出

LiveGBS支持RTSP流分发支持GB28181国标流转RTSP流如何配置RTSP视频直播流输出 1、开启RTSP1.1、页面配置1.2、ini文件配置 2、配置RTSP流用户密码3、RTSP流地址(接口调用)4、RTSP流地址(静态拼接获取)5 、相关问题6、搭建GB28181视频直播平台 1、开启RTSP 1.1、页面配置 在基础配置 -> 流媒体服务配置中配置,RTSP

物联网直播流媒体技术学习总结

因工作涉及到直播流媒体以下文章记录了流媒体播放内容,内容来源于同事大力支持和网络。 目录 第一部分 流媒体  一 技术流线图 二 流媒体内部结构图 三 流媒体解析时序图 ​四 直播协议​ 第二部分 流媒体服务器(SRS) 一、安装 二、启动/停止 三、推流测试 四、拉流测试 参考文献 第一部分 流媒体  物联网流媒体技术路线图 一 技术流线图 技术路线图分C/C

【音视频】播放音视频时发生了什么? 视频的编解码 H264是什么? MP4是什么?

目录 ✨播放一个视频的流程✨为什么要编码(压缩)视频数据?✨如何编码(压缩)数据🎄简单的例子🎄音视频编码方式🎄视频编码格式H264编码是什么?发展历程?H.264基本单元H.264的I帧,P帧,B帧 🎄音频编码格式 ✨视频文件封装格式✨解封装格式✨视频解码✨H264分层结构🎄网络上传输H264视频数据的流程VCL的结构关系 🎄NAL单元 ✨ 参考文章 ✨播放一个视频的流

【Android 多媒体应用】使用MediaCodec将摄像头采集的视频编码为h264

转载自:http://www.cnblogs.com/CoderTian/p/6224605.html MainActivity.java import android.app.Activity;import android.graphics.ImageFormat;import android.hardware.Camera;import android.hardware.Camera

H264的句法和语义(二)

1.2 句法元素的分层结构 1.2.1 句法元素与变量 编码器将数据编码为句法元素然后依次发送。在解码器端,通常要将句法元素作求值计算,得出一些中间数据,这些中间数据就是H.264定义的变量。 图1 从句法元素解出变量 pic_width_in_mbs_minus1 是解码器直接从码流中提取的句法元素,这个句法元素表征图像的宽度以宏块为单位。我们看到,为了