本来想先搞推流的,但是这个rtmpdump只有拉流,并且这个文件也比较简单,那就暂且分析拉流吧。
2.1 使用rtmpdump
2.1.1 使用rtmpdump
不知道为啥,昨天在Debian环境下编译了几个小时,都不能把rtmpdump编译出来,openssl也使用了1.0.1f的库了,但是还是有问题,如果有编译通过的,欢迎交流,既然编不过,那没办法了,下载个windows版本把,windows版本好处就是不用自己操心,反正也是尝试一下功能而已,无所谓。librtmp下载地址
下载完成之后,开启windows小黑框,输入命令:
./rtmpdump -r "rtmp://192.168.1.177:1935/live/livestream" -o test.flv
这个就是通过在1.177服务器上拉流,服务器搭建看第一节环境搭建文章,推流用OBS,下面开始测试拉流的代码是否正常。
左边是推流,右边是拉流,现在就是拉流,我们暂停下,查看我们拉流下来的文件,这里我用vlc打开
看到没有,毫无违和感。
2.1.2 linux下编译rtmpdump
本来之前,怎么编译,都编译不过,所以才写了上面用windows版本测试,今天移植到qt环境中,修改了一些东西,然后一直移植又不成功,所以有点奔溃,然后就怀疑是不是源码有问题,windows看不懂源码,有点崩溃,所以就想到了linux版本,可惜linux版本编译不通过啊,忽然想起我不是移植到qt都编过了么,linux还比qt连接的库多么,所以就就改源码,果然就编译成功了,阳光总在风雨后。
因为这次安装是昨天安装的,尝试了很多方法,所以今天编译成功的,不知道用的是哪个了,尴尬。。。。。
但是,有一些易错点,我在这里标记一下,防止下次再次踩坑,详细安装步骤的话,等我下次有空了,在搞一个,要不下次直接搞个docker镜像得了,这样环境就不是问题了,还是下次吧,
1、 openssl一定要选择低版本的,比如这个版本openssl-1.0.1f.tar.gz,如果用了新版本就会发现接口各种不兼容,经常报DH->g 这个出错,记住就行
2、装好了openssl之后,在链接阶段,就一直报有一些函数找不到
是不是很难受,昨天一直以为是不是库没找到,尝试了各种方法,最后放弃了,但是今天突然就悟到了,添加一个不用加密的宏
就添加NO_CRYPTO就可以了,这次make就通过了,但是加了这个宏就不能使用加密功能了,不过我们测试阶段先能编译能跑先,这样进度就会快了,加密的时候以后要用的时候再。
这里贴一个可以运行的结果图片,有图有真想:
2.2 rtmpdump简单分析
讲到这个分析源码,昨晚搜索了一下,看到了雷神的博客,这里粘贴一下链接致敬雷神雷神是我们音视频的领头人,有一些工具现在还在使用,确实厉害,回到正题,这次只要分析main函数,并且移植到自己的工程中,这也是为昨天不能编译找点安慰,哈哈。
rtmpdump的main函数看着有点多,但其实主要的函数没多少(雷神的文章也介绍了)
InitSockets() //初始化socket,只有win32才执行
RTMP_Init(&rtmp); //初始化RTMP
RTMP_ParseURL() //解析URL,会把url中的主机地址,端口号等解析出来
RTMP_SetupStream() //设置一些参数,究竟设置了啥,我也不知道,今天不分析源码
fopen(flvFile, "w+b"); //打开文件,源码是把拉下来的视频文件保存到文件中
RTMP_Connect(&rtmp, NULL); //建立连接
RTMP_ConnectStream(&rtmp, dSeek); //建立流连接
Download(); //下载函数,其实这个函数里面没多少内容,就是调用了RTMP_Read读取数据,并把数据写入到文件中
RTMP_Close(&rtmp); //关闭RTMP
fclose(file); //关闭文件
CleanupSockets(); //清楚socket
所以说主要的内容没有多少,我根据这些主要的信息,自己移植了一个qt的版本,现在只是测试程序,学习就是要慢慢进步,不着急。
2.3 自己移植的拉流
2.3.1 qt环境
要想彻底搞懂rtmpdump,还是需要自己动手移植,所以我就自己动手移植了一个,这个我一直的环境是windows下的qt,新建工程这些就不说了,然后把librtmp这个库添加到工程,添加之后进行编译,发现会报错,反正就是缺少连接库,下面图片是我添加连接库的了,添加之后就可以编译成功了。
然后我去看了一下makefile,在windows下,确实需要连接这几个库:
2.3.2 我自己的源码
接下来就直接贴我自己修改后的源码,源码的主干信息,就是上面分析的哪几个函数:
int test_rtmp_pull()
{
int nStatus = RD_SUCCESS;
uint32_t dSeek = 0; // seek position in resume mode, 0 otherwise
FILE *file = 0;
int bResume = FALSE; // true in resume mode
uint32_t bufferTime = DEF_BUFTIME;
AVal hostname = { 0, 0 };
AVal playpath = { 0, 0 };
AVal subscribepath = { 0, 0 };
int port = -1;
int protocol = RTMP_PROTOCOL_UNDEFINED;
int bLiveStream = FALSE; // is it a live stream? then we can't seek/resume
long int timeout = DEF_TIMEOUT; // timeout connection after 120 seconds
RTMP rtmp = { 0 };
uint32_t dStartOffset = 0; // seek position in non-live mode
uint32_t dStopOffset = 0;
AVal swfUrl = { 0, 0 };
AVal tcUrl = { 0, 0 };
AVal pageUrl = { 0, 0 };
AVal app = { 0, 0 };
AVal auth = { 0, 0 };
AVal swfHash = { 0, 0 };
uint32_t swfSize = 0;
AVal flashVer = { 0, 0 };
AVal sockshost = { 0, 0 };
RTMP_LogPrintf("RTMPDump test rtmp pull\n");
RTMP_LogPrintf("2020-05-04\n");
char *url = "rtmp://192.168.1.177:1935/live/livestream";
char *flvFile = "test.flv";
if (!InitSockets())
{
RTMP_Log(RTMP_LOGERROR, "Couldn't load sockets support on your platform, exiting!");
return -RD_FAILED;
}
RTMP_Init(&rtmp);
{
AVal parsedHost, parsedApp, parsedPlaypath;
unsigned int parsedPort = 0;
int parsedProtocol = RTMP_PROTOCOL_UNDEFINED;
if (!RTMP_ParseURL(url, &parsedProtocol, &parsedHost, &parsedPort, &parsedPlaypath, &parsedApp))
//(const char *url, int *protocol, AVal *host, unsigned int *port, AVal *playpath, AVal *app)
{
RTMP_Log(RTMP_LOGWARNING, "Couldn't parse the specified url (%s)!", url);
}
else
{
if (!hostname.av_len)
hostname = parsedHost;
if (port == -1)
port = parsedPort;
if (playpath.av_len == 0 && parsedPlaypath.av_len)
{
playpath = parsedPlaypath;
}
if (protocol == RTMP_PROTOCOL_UNDEFINED)
protocol = parsedProtocol;
if (app.av_len == 0 && parsedApp.av_len)
{
app = parsedApp;
}
RTMP_LogPrintf("info (%s) %s %d %s %s\n", url, hostname.av_val, port, playpath.av_val, app.av_val);
}
}
{
if (!hostname.av_len)
{
RTMP_Log(RTMP_LOGERROR,
"You must specify a hostname (--host) or url (-r \"rtmp://host[:port]/playpath\") containing a hostname");
return RD_FAILED;
}
if (playpath.av_len == 0)
{
RTMP_Log(RTMP_LOGERROR,
"You must specify a playpath (--playpath) or url (-r \"rtmp://host[:port]/playpath\") containing a playpath");
return RD_FAILED;
}
if (protocol == RTMP_PROTOCOL_UNDEFINED)
{
RTMP_Log(RTMP_LOGWARNING,
"You haven't specified a protocol (--protocol) or rtmp url (-r), using default protocol RTMP");
protocol = RTMP_PROTOCOL_RTMP;
}
if (port == -1)
{
RTMP_Log(RTMP_LOGWARNING,
"You haven't specified a port (--port) or rtmp url (-r), using default port 1935");
port = 0;
}
if (port == 0)
{
if (protocol & RTMP_FEATURE_SSL)
port = 443;
else if (protocol & RTMP_FEATURE_HTTP)
port = 80;
else
port = 1935;
}
}
if (tcUrl.av_len == 0)
{
printf("tcUrl.av_len \n");
char str[512] = { 0 };
tcUrl.av_len = snprintf(str, 511, "%s://%.*s:%d/%.*s",
RTMPProtocolStringsLower[protocol], hostname.av_len,
hostname.av_val, port, app.av_len, app.av_val);
tcUrl.av_val = (char *) malloc(tcUrl.av_len + 1);
strcpy(tcUrl.av_val, str);
}
RTMP_SetupStream(&rtmp, protocol, &hostname, port, &sockshost, &playpath,
&tcUrl, &swfUrl, &pageUrl, &app, &auth, &swfHash, swfSize,
&flashVer, &subscribepath, dSeek, dStopOffset, bLiveStream, timeout);
// RTMP_SetupStream(RTMP *r,int protocol,AVal *host,unsigned int port, AVal *sockshost,AVal *playpath,AVal *tcUrl,AVal *swfUrl, AVal *pageUrl, AVal *app,
// AVal *auth, AVal *swfSHA256Hash, uint32_t swfSize,AVal *flashVer,AVal *subscribepath,int dStart, int dStop, int bLiveStream, long int timeout)
file = fopen(flvFile, "w+b");
if (file == 0)
{
RTMP_LogPrintf("Failed to open file! %s\n", flvFile);
return RD_FAILED;
}
int bufferSize = 64 * 1024;
char *buffer = (char *) malloc(bufferSize);
int nRead = 0;
off_t size = 0;
RTMP_Log(RTMP_LOGDEBUG, "Setting buffer time to: %dms", bufferTime);
RTMP_SetBufferMS(&rtmp, bufferTime);
RTMP_LogPrintf("Connecting ...\n");
if (!RTMP_Connect(&rtmp, NULL))
{
nStatus = RD_FAILED;
printf("RTMP_Connect\n");
return RD_FAILED;
}
RTMP_Log(RTMP_LOGINFO, "Connected...");
if (!RTMP_ConnectStream(&rtmp, dSeek))
{
nStatus = RD_FAILED;
printf("RTMP_ConnectStream\n");
return RD_FAILED;
}
do
{
nRead = RTMP_Read(&rtmp, buffer, bufferSize);
RTMP_LogPrintf("nRead: %d\n", nRead);
if (nRead > 0)
{
if (fwrite(buffer, sizeof(unsigned char), nRead, file) != (size_t) nRead)
{
RTMP_Log(RTMP_LOGERROR, "%s: Failed writing, exiting!", __FUNCTION__);
free(buffer);
return RD_FAILED;
}
size += nRead;
}
}
while ( nRead > -1 && RTMP_IsConnected(&rtmp));
free(buffer);
RTMP_Close(&rtmp);
if (file != 0)
fclose(file);
CleanupSockets();
return 0;
}
是不是感觉拉流就这么简单,其实不是,我这里代码只是尝试一下拉流,其他的时间戳,还有判断错误都没做,所以只能算初探rtmp,先跑起来为目的。
还有两个socket源码:
#include <librtmp/log.h>
#include <stdio.h>
#include "librtmp/rtmp_sys.h"
#define RD_SUCCESS 0
#define RD_FAILED 1
#define RD_INCOMPLETE 2
#define DEF_TIMEOUT 30 /* seconds */
#define DEF_BUFTIME (10 * 60 * 60 * 1000) /* 10 hours default */
#define DEF_SKIPFRM 0
// starts sockets
static int InitSockets()
{
#ifdef WIN32
WORD version;
WSADATA wsaData;
version = MAKEWORD(1, 1);
return (WSAStartup(version, &wsaData) == 0);
#else
return TRUE;
#endif
}
inline void CleanupSockets()
{
#ifdef WIN32
WSACleanup();
#endif
}
这样一个简单的拉流就能成功了,我比较喜欢贴下结果,这样才有成就感,哈哈(现在的推流端还是用OBS,服务器用SRS,看第一节环境搭建)