热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

音视频学习(三、rtmp推流)

本来是想先写这一篇的,结果写完了之后,测试,竟然推不出去,尴尬,所以赶紧去补了一下FLV格式的原理࿰

本来是想先写这一篇的,结果写完了之后,测试,竟然推不出去,尴尬,所以赶紧去补了一下FLV格式的原理,因为这个rtmp推流推的就是flv格式,但是顺序还是不变,还是写推流,你们也可以先看FLV格式解析,可能看着有点乏味,但是如果我们带着问题去看的话,就觉得世界变的有趣了。

3.1 rtmp推流

3.1.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

还是把拉流的贴出来吧,没有对比就没有伤害:
在这里插入图片描述
是不是看到就个别的差别,所以推流实现起来也简单了。

3.1.2 rtmp推流实现源码

我这个推流的代码只是简单的实现功能,并没有做其他处理,也就是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(&timestamp, 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 #ifdef _WIN32
#include
#endif#define RD_SUCCESS 0
#define RD_FAILED 1
#define RD_INCOMPLETE 2enum RTMPChannel
{RTMP_NETWORK_CHANNEL &#61; 2, ///RTMP_SYSTEM_CHANNEL, ///RTMP_AUDIO_CHANNEL, ///RTMP_VIDEO_CHANNEL &#61; 6, ///RTMP_SOURCE_CHANNEL &#61; 8, ///
};#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
}


推荐阅读
  • Tag类:EVAL_BODY_INCLUDE在doStartTag中返回表示执行标签体的内容SKIP_BODY在doStartTag方法中返回表示不执行标签体EVAL ... [详细]
  • 本文讨论了在使用Git进行版本控制时,如何提供类似CVS中自动增加版本号的功能。作者介绍了Git中的其他版本表示方式,如git describe命令,并提供了使用这些表示方式来确定文件更新情况的示例。此外,文章还介绍了启用$Id:$功能的方法,并讨论了一些开发者在使用Git时的需求和使用场景。 ... [详细]
  • 如何自行分析定位SAP BSP错误
    The“BSPtag”Imentionedintheblogtitlemeansforexamplethetagchtmlb:configCelleratorbelowwhichi ... [详细]
  • STM32 IO口模拟串口通讯
    转自:http:ziye334.blog.163.comblogstatic224306191201452833850647前阵子,调项目时需要用到低波 ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • 导出功能protectedvoidbtnExport(objectsender,EventArgse){用来打开下载窗口stringfileName中 ... [详细]
  • 欢乐的票圈重构之旅——RecyclerView的头尾布局增加
    项目重构的Git地址:https:github.comrazerdpFriendCircletreemain-dev项目同步更新的文集:http:www.jianshu.comno ... [详细]
  • 如何在php文件中添加图片?
    本文详细解答了如何在php文件中添加图片的问题,包括插入图片的代码、使用PHPword在载入模板中插入图片的方法,以及使用gd库生成不同类型的图像文件的示例。同时还介绍了如何生成一个正方形文件的步骤。希望对大家有所帮助。 ... [详细]
  • 如何利用 Myflash 解析 binlog ?
    本文主要介绍了对Myflash的测试,从准备测试环境到利用Myflash解析binl ... [详细]
  • 超级简单加解密工具的方案和功能
    本文介绍了一个超级简单的加解密工具的方案和功能。该工具可以读取文件头,并根据特定长度进行加密,加密后将加密部分写入源文件。同时,该工具也支持解密操作。加密和解密过程是可逆的。本文还提到了一些相关的功能和使用方法,并给出了Python代码示例。 ... [详细]
  • 本文介绍了NetCore WebAPI开发的探索过程,包括新建项目、运行接口获取数据、跨平台部署等。同时还提供了客户端访问代码示例,包括Post函数、服务器post地址、api参数等。详细讲解了部署模式选择、框架依赖和独立部署的区别,以及在Windows和Linux平台上的部署方法。 ... [详细]
  • 本文介绍了使用C++Builder实现获取USB优盘序列号的方法,包括相关的代码和说明。通过该方法,可以获取指定盘符的USB优盘序列号,并将其存放在缓冲中。该方法可以在Windows系统中有效地获取USB优盘序列号,并且适用于C++Builder开发环境。 ... [详细]
  • tcpdump 4.5.1 crash 深入分析
    tcpdump 4.5.1 crash 深入分析 ... [详细]
  • Question该提问来源于开源项目:react-native-device-info/react-native-device-info ... [详细]
  • 译文:如何使用SocketAsyncEventArgs类(How to use the SocketAsyncEventArgs class)
    转载自:http:blog.csdn.nethulihuiarticledetails3244520原文:HowtousetheSocketAsyncE ... [详细]
author-avatar
online168
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有