音视频学习(二、初探rtmp)

本来想先搞推流的,但是这个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,看第一节环境搭建)
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值