本来是想先写这一篇的,结果写完了之后,测试,竟然推不出去,尴尬,所以赶紧去补了一下FLV格式的原理,因为这个rtmp推流推的就是flv格式,但是顺序还是不变,还是写推流,你们也可以先看FLV格式解析,可能看着有点乏味,但是如果我们带着问题去看的话,就觉得世界变的有趣了。
3.1 rtmp推流昨天写了一个简单的拉流程序,一共有几个函数,那几个函数我这里就不写,需要看的可以回到上一节看,但是我们可以根据拉流,也能猜测到推流也是几个函数,反正底层实现都交给库了,现在我们先不去研究库,先把东西做出来,下面是推流的几个重要步骤,可以看拉流对比对比:
InitSockets() //还是初始化socket
RTMP_Init(&rtmp); //初始化RTMP
RTMP_SetupURL(&rtmp, url) //设置参数,这个其实跟拉流那个解析url和设置参数是合并了
RTMP_EnableWrite(&rtmp); //这个就是区别,添加了一个写使能
RTMP_Connect(&rtmp, NULL) //建立连接
RTMP_ConnectStream(&rtmp, 0) //建立流连接
RTMPPacket_Alloc(packet, 1024 * 1024); //因为是发送,所以要准备一下发送buff
RTMP_SendPacket(&rtmp, packet, 0); //发送数据
RTMP_Close(&rtmp); //关闭RTMP
CleanupSockets(); //清除socket
还是把拉流的贴出来吧,没有对比就没有伤害:
是不是看到就个别的差别,所以推流实现起来也简单了。
我这个推流的代码只是简单的实现功能,并没有做其他处理,也就是v0版本的,纯做入门能调用库进行推流的,下一节,就会完善推拉流的代码,我们现在先看简单的,不要着急,慢慢来。
int test_rtmp_push()
{char *flv_name &#61; "test.flv";char *url &#61; "rtmp://192.168.1.177:1935/live/chen";FILE *fp &#61; NULL;RTMP rtmp &#61; { 0 };uint32_t preTagsize &#61; 0;//packet attributesuint32_t type &#61; 0;uint32_t datalength &#61; 0;uint32_t timestamp &#61; 0;uint32_t streamid &#61; 0;fp &#61; fopen(flv_name, "rb");if(!fp) {RTMP_LogPrintf("Open File LogError.\n");return -1;}/* set log level *///RTMP_LogLevel loglvl &#61; RTMP_Log; // RTMP_LOGALL;//RTMP_LogSetLevel(loglvl);//第一步if (!InitSockets()){RTMP_Log(RTMP_LOGERROR, "Couldn&#39;t load sockets support on your platform, exiting!");return -RD_FAILED;}//第二步RTMP_Init(&rtmp);//第三步&#xff0c;通过url设置参数if (!RTMP_SetupURL(&rtmp, url)){RTMP_Log(RTMP_LOGERROR, "SetupURL Err\n");CleanupSockets();return -1;}RTMP_Log(RTMP_LOGERROR, "RTMP_SetBufferMS ---------->\n");RTMP_SetBufferMS(&rtmp, 10000);//第四步// RTMP推流需要EnableWriteRTMP_Log(RTMP_LOGERROR, "RTMP_EnableWrite ---------->\n");RTMP_EnableWrite(&rtmp);RTMP_Log(RTMP_LOGERROR, "RTMP_Connect ---------->\n");if (!RTMP_Connect(&rtmp, NULL)){RTMP_Log(RTMP_LOGERROR, "Connect Err\n");CleanupSockets();return -1;}RTMP_Log(RTMP_LOGERROR, "RTMP_ConnectStream ---------->\n");if (!RTMP_ConnectStream(&rtmp, 0)){RTMP_Log(RTMP_LOGERROR, "ConnectStream Err\n");RTMP_Close(&rtmp);CleanupSockets();return -1;}RTMP_Log(RTMP_LOGERROR, "RTMP_ConnectStream ok. ---------->\n");//第五步&#xff0c;申请消息内存RTMPPacket *packet &#61; NULL;packet &#61; (RTMPPacket*)malloc(sizeof(RTMPPacket));// 为包申请了buffer, 实际在内部申请的为nSize &#43; RTMP_MAX_HEADER_SIZERTMPPacket_Alloc(packet, 1024 * 1024);RTMPPacket_Reset(packet);//第六步&#xff0c;发送//jump over FLV Headerfseek(fp, 9, SEEK_SET); // 跳过FLV header//jump over previousTagSizenfseek(fp, 4, SEEK_CUR); // 跳过 previousTagSizenpacket->m_nInfoField2 &#61; rtmp.m_stream_id;int continue_sleep_count &#61; 0;uint32_t audio_start_timestamp &#61; 0;uint32_t audio_current_timestamp &#61; 0;int h264_frame_count &#61; 0;uint32_t start_time &#61; 0;uint32_t now_time &#61; 0;start_time &#61; (uint32_t)get_current_time_msec();while(1){if ((((now_time &#61; (uint32_t)get_current_time_msec()) - start_time)<(audio_current_timestamp - audio_start_timestamp))){Sleep(30);if(continue_sleep_count&#43;&#43; > 30){
// RTMP_Log("continue_sleep_count&#61;%d timeout, time:%ums, aud:%ums\n",
// continue_sleep_count,
// (uint32_t)get_current_time_msec() - start_time,
// audio_current_timestamp - audio_start_timestamp);}continue;}continue_sleep_count &#61; 0;//not quite the same as FLV specif (!ReadU8(&type, fp)) // 读取tag类型{RTMP_Log(RTMP_LOGERROR, "%s(%d) break %d\n", __FUNCTION__, __LINE__, type);break;}datalength &#61; 0;if (!ReadU24(&datalength, fp)) // 负载数据长度{RTMP_Log(RTMP_LOGERROR, "%s(%d) break\n", __FUNCTION__, __LINE__);break;}printf("datalength &#61; %d %d\n", datalength, type);if (!ReadTime(×tamp, fp)){RTMP_Log(RTMP_LOGERROR, "%s(%d) break\n", __FUNCTION__, __LINE__);break;}if (!ReadU24(&streamid, fp)){RTMP_Log(RTMP_LOGERROR, "%s(%d) break\n", __FUNCTION__, __LINE__);break;}if (type !&#61; RTMP_PACKET_TYPE_AUDIO && type !&#61; RTMP_PACKET_TYPE_VIDEO){RTMP_Log(RTMP_LOGERROR, "unknown type:%d", type);//jump over non_audio and non_video frame&#xff0c;//jump over next previousTagSizen at the same timefseek(fp, datalength &#43; 4, SEEK_CUR);continue;}if (type &#61;&#61; RTMP_PACKET_TYPE_AUDIO){if(audio_start_timestamp &#61;&#61; 0){audio_start_timestamp &#61; timestamp;start_time &#61; (uint32_t)get_current_time_msec();}if(timestamp < audio_current_timestamp) //回绕&#xff1f;做重置{audio_start_timestamp &#61; 0;start_time &#61; (uint32_t)get_current_time_msec();RTMP_Log(RTMP_LOGWARNING, "%s(%d) timestamp rollback %u->%ums\n",__FUNCTION__, __LINE__, audio_current_timestamp, timestamp);}audio_current_timestamp &#61; timestamp;}size_t read_len &#61; 0;if ((read_len &#61; fread(packet->m_body, 1, datalength, fp)) !&#61; datalength){RTMP_Log(RTMP_LOGERROR, "fread error, read_len:%d, datalength:%d\n", datalength);break;}if(type &#61;&#61; RTMP_PACKET_TYPE_AUDIO){packet->m_nChannel &#61; RTMP_AUDIO_CHANNEL;}if(type &#61;&#61; RTMP_PACKET_TYPE_VIDEO){packet->m_nChannel &#61; RTMP_VIDEO_CHANNEL;}packet->m_headerType &#61; RTMP_PACKET_SIZE_MEDIUM;packet->m_hasAbsTimestamp &#61; 1;packet->m_nTimeStamp &#61; timestamp;packet->m_packetType &#61; type;packet->m_nBodySize &#61; datalength;//pre_frame_time &#61; timestamp;if (!RTMP_IsConnected(&rtmp)){RTMP_Log(RTMP_LOGERROR, "rtmp is not connect\n");break;}if (!RTMP_SendPacket(&rtmp, packet, 0)){RTMP_Log(RTMP_LOGERROR, "Send LogError\n");break;}if (!ReadU32(&preTagsize, fp)){RTMP_Log(RTMP_LOGERROR, "%s(%d) break\n", __FUNCTION__, __LINE__);break;}if (type &#61;&#61; RTMP_PACKET_TYPE_VIDEO){h264_frame_count&#43;&#43;;if (h264_frame_count % 50 &#61;&#61; 0){printf("t &#61; %d, frame_count &#61; %d, time:%ums, aud:%ums\n",timestamp, h264_frame_count,(uint32_t)get_current_time_msec() - start_time,audio_current_timestamp - audio_start_timestamp);}}}//第七步清场&#xff0c;退出if (fp)fclose(fp);RTMP_Close(&rtmp);CleanupSockets();return 0;
}
先去掉其他的代码&#xff0c;留下主干&#xff0c;主干是不是发现就是我刚刚说的哪几个函数&#xff0c;所以推流也是如此简单&#xff0c;既然是测试程序&#xff0c;所以我这是读取文件的方式进行推流&#xff0c;test.flv文件是昨天拉流的时候&#xff0c;拉下来的文件&#xff0c;所以今天就再次利用&#xff0c;作为推流的文件&#xff0c;再推回来。
如果还想了解一下细节&#xff0c;比如推流上去的是什么数据&#xff0c;这个就得看看后一篇文章音视频学习&#xff08;四、FLV格式解析&#xff09;&#xff0c;只有看了这个FLV格式之后&#xff0c;才能明白发送的时候去读取的数据&#xff0c;到底读出了什么&#xff0c;FLV格式第一个字节是类型&#xff0c;08是音频&#xff0c;09是视频&#xff0c;然后接下来就是有效数据的长度&#xff0c;之后那两个就是时间戳&#xff0c;我们现在不讨论时间戳&#xff0c;就把头的数据跳过之后&#xff0c;就去取有效的数据&#xff0c;然后打包到packet里面&#xff0c;进行发送&#xff0c;就是这样简单。
我之前为什么一直推流不上去的原因就是&#xff0c;我擅自把读取时间戳的函数去掉了&#xff0c;因为我觉得读取时间戳没有&#xff0c;结果一直推不上去&#xff0c;所以就火急火燎的跑去看了FLV格式&#xff0c;回来才发现&#xff0c;数据偏移出错了&#xff0c;导致每次读到的数据有错&#xff0c;那为什么读取数据会偏移&#xff0c;就是因为少读取了两个时间戳&#xff0c;导致读错&#xff0c;所以一下子就明白了&#xff0c;就改了过来&#xff0c;果不其然就推流成功了。
附其他部分代码&#xff1a;
#include
#include
#include "librtmp/rtmp_sys.h"
#include
#include
#include
#include
#endif#define RD_SUCCESS 0
#define RD_FAILED 1
#define RD_INCOMPLETE 2enum RTMPChannel
{RTMP_NETWORK_CHANNEL &#61; 2, ///
};#define HTON16(x) ((x>>8&0xff)|(x<<8&0xff00))
#define HTON24(x) ((x>>16&0xff)|(x<<16&0xff0000)|(x&0xff00))
#define HTON32(x) ((x>>24&0xff)|(x>>8&0xff00)|\(x<<8&0xff0000)|(x<<24&0xff000000))
#define HTONTIME(x) ((x>>16&0xff)|(x<<16&0xff0000)|(x&0xff00)|(x&0xff000000))static int64_t get_current_time_msec()
{
#ifdef _WIN32return (int64_t)GetTickCount();
#elsestruct timeval tv;gettimeofday(&tv, NULL);return ((int64_t)tv.tv_sec * 1000 &#43; (unsigned long long)tv.tv_usec / 1000);
#endif
}/*read 1 byte*/
int ReadU8(uint32_t *u8, FILE*fp)
{if (fread(u8, 1, 1, fp) !&#61; 1)return 0;return 1;
}
/*read 2 byte*/
int ReadU16(uint32_t *u16, FILE*fp)
{if (fread(u16, 2, 1, fp) !&#61; 1)return 0;*u16 &#61; HTON16(*u16);return 1;
}
/*read 3 byte*/
int ReadU24(uint32_t *u24, FILE*fp)
{if (fread(u24, 3, 1, fp) !&#61; 1)return 0;printf("U24 0x%x\n", *u24);*u24 &#61; HTON24(*u24);return 1;
}
/*read 4 byte*/
int ReadU32(uint32_t *u32, FILE*fp)
{if (fread(u32, 4, 1, fp) !&#61; 1)return 0;*u32 &#61; HTON32(*u32);return 1;
}
/*read 1 byte,and loopback 1 byte at once*/
int PeekU8(uint32_t *u8, FILE*fp)
{if (fread(u8, 1, 1, fp) !&#61; 1)return 0;fseek(fp, -1, SEEK_CUR);return 1;
}/*read 4 byte and convert to time format*/
int ReadTime(uint32_t *utime, FILE*fp)
{if (fread(utime, 4, 1, fp) !&#61; 1)return 0;*utime &#61; HTONTIME(*utime);return 1;
}// starts sockets
static int InitSockets()
{
#ifdef WIN32WORD version;WSADATA wsaData;version &#61; MAKEWORD(1, 1);return (WSAStartup(version, &wsaData) &#61;&#61; 0);
#elsereturn TRUE;
#endif
}static inline void CleanupSockets()
{
#ifdef WIN32WSACleanup();
#endif
}