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

本来是想先写这一篇的,结果写完了之后,测试,竟然推不出去,尴尬,所以赶紧去补了一下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 = "test.flv";
   char *url = "rtmp://192.168.1.177:1935/live/chen";

    FILE *fp = NULL;
    RTMP rtmp = { 0 };
    uint32_t preTagsize = 0;

    //packet attributes
    uint32_t type = 0;
    uint32_t datalength = 0;
    uint32_t timestamp = 0;
    uint32_t streamid = 0;


   fp = fopen(flv_name, "rb");
   if(!fp) {
        RTMP_LogPrintf("Open File LogError.\n");
        return -1;
    }

    /* set log level */
    //RTMP_LogLevel loglvl = RTMP_Log; // RTMP_LOGALL;
    //RTMP_LogSetLevel(loglvl);

    //第一步
    if (!InitSockets())
    {
        RTMP_Log(RTMP_LOGERROR, "Couldn't load sockets support on your platform, exiting!");
        return -RD_FAILED;
    }

    //第二步
    RTMP_Init(&rtmp);

    //第三步,通过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推流需要EnableWrite
    RTMP_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");


    //第五步,申请消息内存
    RTMPPacket *packet = NULL;
    packet = (RTMPPacket*)malloc(sizeof(RTMPPacket));
    // 为包申请了buffer, 实际在内部申请的为nSize + RTMP_MAX_HEADER_SIZE
    RTMPPacket_Alloc(packet, 1024 * 1024);
    RTMPPacket_Reset(packet);

    //第六步,发送
    //jump over FLV Header
    fseek(fp, 9, SEEK_SET);			// 跳过FLV header
    //jump over previousTagSizen
    fseek(fp, 4, SEEK_CUR);			// 跳过 previousTagSizen
    packet->m_nInfoField2 = rtmp.m_stream_id;

    int continue_sleep_count = 0;
    uint32_t audio_start_timestamp = 0;
    uint32_t audio_current_timestamp = 0;
    int h264_frame_count = 0;
    uint32_t start_time = 0;
    uint32_t now_time = 0;
    start_time = (uint32_t)get_current_time_msec();
    while(1)
    {
        if ((((now_time = (uint32_t)get_current_time_msec()) - start_time)
             <(audio_current_timestamp - audio_start_timestamp)))
        {
            Sleep(30);
            if(continue_sleep_count++ > 30)
            {
//                RTMP_Log("continue_sleep_count=%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 = 0;
        //not quite the same as FLV spec
        if (!ReadU8(&type, fp))			// 读取tag类型
        {
            RTMP_Log(RTMP_LOGERROR, "%s(%d) break %d\n", __FUNCTION__, __LINE__, type);
            break;
        }
        datalength = 0;
        if (!ReadU24(&datalength, fp))	// 负载数据长度
        {
            RTMP_Log(RTMP_LOGERROR, "%s(%d) break\n", __FUNCTION__, __LINE__);
            break;
        }
        printf("datalength = %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 != RTMP_PACKET_TYPE_AUDIO && type != RTMP_PACKET_TYPE_VIDEO)
        {
            RTMP_Log(RTMP_LOGERROR, "unknown type:%d", type);
            //jump over non_audio and non_video frame,
            //jump over next previousTagSizen at the same time
            fseek(fp, datalength + 4, SEEK_CUR);
            continue;
        }

        if (type == RTMP_PACKET_TYPE_AUDIO)
        {
            if(audio_start_timestamp == 0)
            {
                audio_start_timestamp = timestamp;
                start_time = (uint32_t)get_current_time_msec();
            }
            if(timestamp <  audio_current_timestamp)    //回绕?做重置
            {
                audio_start_timestamp = 0;
                start_time = (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 = timestamp;

        }

        size_t read_len = 0;
        if ((read_len = fread(packet->m_body, 1, datalength, fp)) != datalength)
        {
            RTMP_Log(RTMP_LOGERROR, "fread error, read_len:%d, datalength:%d\n", datalength);
            break;
        }

        if(type == RTMP_PACKET_TYPE_AUDIO)
        {
            packet->m_nChannel = RTMP_AUDIO_CHANNEL;
        }
        if(type == RTMP_PACKET_TYPE_VIDEO)
        {
            packet->m_nChannel = RTMP_VIDEO_CHANNEL;
        }

        packet->m_headerType = RTMP_PACKET_SIZE_MEDIUM;
        packet->m_hasAbsTimestamp = 1;
        packet->m_nTimeStamp = timestamp;
        packet->m_packetType = type;
        packet->m_nBodySize = datalength;
        //pre_frame_time = 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 == RTMP_PACKET_TYPE_VIDEO)
        {
            h264_frame_count++;
            if (h264_frame_count % 50 == 0)
            {
                printf("t = %d, frame_count = %d, time:%ums, aud:%ums\n",
                       timestamp, h264_frame_count,
                       (uint32_t)get_current_time_msec() - start_time,
                       audio_current_timestamp - audio_start_timestamp);
            }
        }
    }


    //第七步清场,退出
    if (fp)
        fclose(fp);

    RTMP_Close(&rtmp);

    CleanupSockets();

    return 0;
}

先去掉其他的代码,留下主干,主干是不是发现就是我刚刚说的哪几个函数,所以推流也是如此简单,既然是测试程序,所以我这是读取文件的方式进行推流,test.flv文件是昨天拉流的时候,拉下来的文件,所以今天就再次利用,作为推流的文件,再推回来。

如果还想了解一下细节,比如推流上去的是什么数据,这个就得看看后一篇文章音视频学习(四、FLV格式解析),只有看了这个FLV格式之后,才能明白发送的时候去读取的数据,到底读出了什么,FLV格式第一个字节是类型,08是音频,09是视频,然后接下来就是有效数据的长度,之后那两个就是时间戳,我们现在不讨论时间戳,就把头的数据跳过之后,就去取有效的数据,然后打包到packet里面,进行发送,就是这样简单。

我之前为什么一直推流不上去的原因就是,我擅自把读取时间戳的函数去掉了,因为我觉得读取时间戳没有,结果一直推不上去,所以就火急火燎的跑去看了FLV格式,回来才发现,数据偏移出错了,导致每次读到的数据有错,那为什么读取数据会偏移,就是因为少读取了两个时间戳,导致读错,所以一下子就明白了,就改了过来,果不其然就推流成功了。

附其他部分代码:

#include <stdio.h>
#include <librtmp/log.h>
#include "librtmp/rtmp_sys.h"
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

#ifdef _WIN32
#include <Windows.h>
#endif

#define RD_SUCCESS          0
#define RD_FAILED           1
#define RD_INCOMPLETE		2

enum RTMPChannel
{
   RTMP_NETWORK_CHANNEL = 2,   ///< channel for network-related messages (bandwidth report, ping, etc)
   RTMP_SYSTEM_CHANNEL,        ///< channel for sending server control messages
   RTMP_AUDIO_CHANNEL,         ///< channel for audio data
   RTMP_VIDEO_CHANNEL   = 6,   ///< channel for video data
   RTMP_SOURCE_CHANNEL  = 8,   ///< channel for a/v invokes
};

#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 _WIN32
    return (int64_t)GetTickCount();
#else
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return ((int64_t)tv.tv_sec * 1000 + (unsigned long long)tv.tv_usec / 1000);
#endif
}

/*read 1 byte*/
int ReadU8(uint32_t *u8, FILE*fp)
{
    if (fread(u8, 1, 1, fp) != 1)
        return 0;
    return 1;
}
/*read 2 byte*/
int ReadU16(uint32_t *u16, FILE*fp)
{
    if (fread(u16, 2, 1, fp) != 1)
        return 0;
    *u16 = HTON16(*u16);
    return 1;
}
/*read 3 byte*/
int ReadU24(uint32_t *u24, FILE*fp)
{
    if (fread(u24, 3, 1, fp) != 1)
        return 0;
    printf("U24 0x%x\n", *u24);
    *u24 = HTON24(*u24);
    return 1;
}
/*read 4 byte*/
int ReadU32(uint32_t *u32, FILE*fp)
{
    if (fread(u32, 4, 1, fp) != 1)
        return 0;
    *u32 = 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) != 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) != 1)
        return 0;
    *utime = HTONTIME(*utime);
    return 1;
}

// starts sockets
static int InitSockets()
{
#ifdef WIN32
  WORD version;
  WSADATA wsaData;

  version = MAKEWORD(1, 1);
  return (WSAStartup(version, &wsaData) == 0);
#else
  return TRUE;
#endif
}

static inline void CleanupSockets()
{
#ifdef WIN32
  WSACleanup();
#endif
}
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值