FLV文件推流到Nginx(C++实现)

本文将介绍如何通过librtmp将flv文件推送到Nginx。首先我们明确文件flv和rtmp flv的文件格式是不一样的,具体参考RTMP FLV和FLV文件的区别。文章结构:首先进行解析flv文件,然后从解析后的文件中,读取音频/视频文件,最后将音视频文件利用librtmp推送到Nginx服务器中。Nginx 支持RTMP就不在此赘述。该过程的实现放在下文。

解析FLV

首先我们要了解FLV文件的格式
图片来源于网络
图片来源于网络。这张图片清楚地展示了FLV的文件结构,首先是FLV Header,一般是9个字节,然后就是一系列的pre tagsize 和 tag。我们需要的是tag中的Tag Data,将它发送到服务端。FLV协议详解

获取音/视频数据

获取所谓的音视频数据就是将FLV Header,preTagSize和Body的头(tagheader)去掉,剩下的就是音视频数据了。tag header 是,1byte TT(Tag Type ),0x08音频,0x09视频,0x12 script, 2-4byte,Tag body的长度, PreTagSize - Tag Header Size = Tag body size, 5-7byte,时间戳,单位:毫秒,script它的时间戳是0, 第8个字节,扩展时间戳, 真正的时间戳格式[扩展,时间戳],一共是4字节
, 9-11,StreamId 0。
好了剩下的工作就是解析这些头了,使用fread读取二进制文件。

推流到Nginx

要推流,肯定先要连接到RTMP server的。

  1. 创建RTMP,使用RTMP_Alloc()函数。
  2. 初始化RTMP,RTMP_Init(rtmp);
  3. 设置RTMP连接地址,RTMP_SetupURL(rtmp, addr);
  4. 连接,RTMP_Connect(rtmp, NULL)
librtmp的基本步骤
  1. initialize RTMP Object
  2. configure RTMP URL
  3. connect Nginx(Media Stream Server)
  4. send data packet

源代码

//
// Created by wenfan on 2021/8/10.
//

#ifndef PLAY_FLVUPSTREAM_H
#define PLAY_FLVUPSTREAM_H


#include <cstdio>
#include <string>
#include <librtmp/rtmp.h>

using namespace std;

class FLVUpStream {

public:
    FILE* openFLV(string flvaddr);
    void publish_stream();
    RTMP* connect_rtmp_server(string rtmpaddr);
    void send_data(FILE* fp, RTMP* rtmp);
    RTMPPacket *alloc_packet();
    void free_packet(RTMPPacket* rtmpPacket);
};
#endif //PLAY_FLVUPSTREAM_H

实现

//
// Created by wenfan on 2021/8/10.
//

#include "FLVUpStream.h"
#include <librtmp/rtmp.h>
#include <thread>


/**
 * 这里涉及到大小端的问题
 * @param fp
 * @param u24
 */
static void read_u24(FILE* fp, unsigned int* u24 ){
    unsigned int tmp = 0;
    fread(&tmp, 1, 3 , fp);
    *u24 = ((tmp >> 16) & 0xFF) | ((tmp << 16) & 0xFF0000) |  ((tmp) & 0xFF00);
}


/**
 * FLV的Header一共9字节
 * 第1-3 byte 固定:'F' 'L' 'V'
 * 第4byte ,version
 * 第5字节的0~5位必须是0,进行保留
 * 第5字节的6位,是否有音频Tag
 * 第5字节的7位,保留,为0
 * 第5字节的8位,表示是否有音频Tag
 * 第6-9位是Header的大小,必须是9
 * @param flvaddr
 * @return
 */
FILE* FLVUpStream::openFLV(string flvaddr){
    FILE* fp = NULL;

    fp = fopen(flvaddr.c_str(), "r+b");
    if(!fp){
        printf("Failed to open flv file: %s", flvaddr.c_str());
        return NULL;
    }

    //jump 9 byte FLV Header
    fseek(fp, 13, SEEK_SET);
    //jump 4 byte PreTagSize
    //fseek(fp, 4, SEEK_CUR);

    return fp;
}


/**
 * 经历5个步骤,创建好RTMP Stream
 * @param rtmpaddr
 * @return
 */
RTMP* FLVUpStream::connect_rtmp_server(string rtmpaddr){
    RTMP* rtmp = nullptr;
    char* addr = new char[rtmpaddr.length()];
    strcpy(addr, rtmpaddr.c_str());

    // 1.create RTMP Object and init it;
    rtmp = RTMP_Alloc();
    if(!rtmp){
        printf("Failed to alloc RTMP Memory\n");
        goto __ERROR ;
    }
    RTMP_Init(rtmp);

    //2. set rtmp server address and set connection timeout
    rtmp->Link.timeout = 10;
    RTMP_SetupURL(rtmp, addr);

    //3.Setup connection
    if(!RTMP_Connect(rtmp, NULL)){
        printf("Failed to connect RTMP Server\n");
        goto __ERROR;
    }

    //4.The option is publish, not set the option is play
    RTMP_EnableWrite(rtmp);

    //5.create Stream
    RTMP_ConnectStream(rtmp, 0);
    return rtmp;

__ERROR:
    if(rtmp){
        RTMP_Close(rtmp);
        RTMP_Free(rtmp);
    }

    return NULL;
}

/**
 * 释放RTMPPacket内存
 * @param rtmpPacket
 */
void FLVUpStream::free_packet(RTMPPacket* rtmpPacket){
    if(rtmpPacket){
        //释放分配的Packet包内内存
        RTMPPacket_Free(rtmpPacket);
        //释放包装结构体
        free(rtmpPacket);
    }
}


/**
 *
 * @return
 */
RTMPPacket* FLVUpStream::alloc_packet(){
    RTMPPacket* packet;
    packet = (RTMPPacket*) malloc(sizeof(RTMPPacket));
    if(!packet){
        printf("Failed to alloc RTMPPacket, No Memory!\n");
        return NULL;
    }

    if(!RTMPPacket_Alloc(packet, 64*1024*1024*1024)) {
        printf("Failed to alloc RTMPPacket content, No Memory!\n");
        return NULL;
    }

    RTMPPacket_Reset(packet);
    packet->m_hasAbsTimestamp = 0;
    packet->m_nChannel = 0x4;
    return packet;
}


/**
 * tag header
 * 1byte TT(Tag Type ),0x08音频,0x09视频,0x12 script
 * 2-4byte,Tag body的长度, PreTagSize - Tag Header Size = Tag body size
 * 5-7byte,时间戳,单位:毫秒,script它的时间戳是0
 * 第8个字节,扩展时间戳, 真正的时间戳格式[扩展,时间戳],一共是4字节
 * 9-11,StreamId 0
 *
 * @param fp
 * @param pPacket
 */
static int  read_data(FILE *fp, RTMPPacket** pPacket){
    int ret = 0;
    size_t data_size;
    char tt;
    unsigned int tag_data_size;
    unsigned  int ts;
    char extend_ts;
    unsigned int streamId;
    unsigned int preTagSize ;

    /**
     * 去读body
     */
    //read_u8(fp,&tt);
    fread(&tt, 1,1, fp);
    read_u24(fp, &tag_data_size);
    read_u24(fp, &ts);
    fread(&extend_ts, 1, 1, fp);
    read_u24(fp, &streamId);


    printf("tag header :tt : %d, ts = %d, data_size = %d, streamId = %d\n", tt, ts , tag_data_size,streamId);
    //读取音视频数据
    data_size = fread((*pPacket)->m_body, 1, tag_data_size,fp);

    //读取preTagSize,4个字节
    fread(&preTagSize, 1, 4, fp);

    if(data_size != tag_data_size){
             printf("Failed to read tag body from flv ,(data_size = %zu, tag_data_size = %d)\n", data_size, tag_data_size);
        return ret;
    }

    //设置packet data
    (*pPacket)->m_headerType = RTMP_PACKET_SIZE_LARGE;
    (*pPacket)->m_nTimeStamp = ts;
    (*pPacket)->m_packetType = tt;
    (*pPacket)->m_nBodySize = tag_data_size;

    return 1;
}

/**
 * 推流
 * @param fp
 * @param rtmp
 */
void FLVUpStream::send_data(FILE *fp, RTMP *rtmp) {

    //1.create  RTMP_Packet Object
    RTMPPacket* packet = NULL;
    packet = alloc_packet();
    packet->m_nInfoField2 = rtmp->m_stream_id;

    while (1){
        //2.read data from flv
        if(!read_data(fp, &packet)){
            printf("over!\n");
            break;
        }

        //3.judge connection is nornal
        if(!RTMP_IsConnected(rtmp)){
            printf("Disconnect with the RTMP server!\n");
            return;
        }

        //4.send data
        RTMP_SendPacket(rtmp,packet, 0);
        this_thread::sleep_for(std::chrono::milliseconds(10));
    }

    // realse packet
    free_packet(packet);

    return;
}




/**
 * 进行推流
 * 进行三个步骤:
 * 1.读取flv文件
 * 2.连接RTMP服务器
 * 3.推送音频/视频数据
 * @return
 */
void FLVUpStream::publish_stream(){
    string flvaddr = "/Users/fengjianqiang/Movies/1.flv";
    string rtmpaddr = "rtmp://localhost:1935/rtmplive/room";

    //1.读取flv文件
    FILE* fp = openFLV(flvaddr);

    //2.连接RTMP服务器
    RTMP* rtmp = connect_rtmp_server(rtmpaddr);

    //3.publish audio /video data
    send_data(fp, rtmp);
}

A: 要使用nginx实现http-flv推流,需要按照以下步骤: 1.安装nginx和rtmp-module   首先需要安装nginx和rtmp-module。可以参考以下资料进行下载和安装。   nginx官网:http://nginx.org/   rtmp-module官网:https://github.com/arut/nginx-rtmp-module 2.配置nginx.conf文件   在nginx.conf文件中,需要添加以下配置信息。其中,flv、live和stream是自定义的,可以根据需要修改。 rtmp { server { listen 1935; #监听端口 chunk_size 4096; #设置chunk_size大小 application flv { live on; #启用直播模式 allow publish all; #允许推流 deny play all; #禁止播放 push_reconnect off; #关闭推流断开重连 hls off; #关闭hls flv_metadata on; #开启flv的metadata信息 wait_key on; #开启wait_key exec_pull ffmpeg -i rtmp://xxxx/$app/$name -vcodec libx264 -vprofile baseline -s 720x576 -acodec libfaac -ar 44100 -f flv rtmp://xxxx/flv/$name; #开启转码功能,实现兼容性 notify_method get; #推流成功后,请求指定地址 notify_update http://xxxx/stream/$app/$name; } } } http { server { listen 80; #监听端口 location /live { flv_live on; #开启http-flv推流 chunked_transfer_encoding on; #开启chunked传输 add_header 'Access-Control-Allow-Origin' '*'; #跨域 add_header 'Allow' 'GET, POST, OPTIONS'; #允许的http方法 add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; #允许跨域的方法 add_header 'Access-Control-Allow-Headers' 'Origin, X-Requested-With, Content-Type, Accept'; #允许跨域的请求头 } } } 3.启动nginx   将修改后的nginx.conf文件保存,使用以下命令启动nginx。 sudo /usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf 4.推流   使用推流软件(如OBS)等工具,将视频流推送到rtmp://localhost/flv/{stream_name}的地址,即可在http://localhost/live/{stream_name}.flv,通过http-flv协议播放推送的视频流。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

顾文繁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值