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

Posted 顾文繁

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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);
}

以上是关于FLV文件推流到Nginx(C++实现)的主要内容,如果未能解决你的问题,请参考以下文章

ffmpeg + nginx + nginx-http-flv-module 实现转码推流相关说明 -- centos7

Nginx-http-flv-module流媒体服务器搭建+模拟推流+flv.js在前端html和Vue中播放HTTP-FLV视频流

centos使用一键lnmp+nginx-rtmp-module插件,使用obs推流到nginx

livego+obs+flv.js 搭建视频直播

ffmpeg命令行循环推流

无人机之眼 | ROS图像RTMP协议推流到地面站,CPU表示软件解码