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的。
- 创建RTMP,使用RTMP_Alloc()函数。
- 初始化RTMP,RTMP_Init(rtmp);
- 设置RTMP连接地址,RTMP_SetupURL(rtmp, addr);
- 连接,RTMP_Connect(rtmp, NULL)
librtmp的基本步骤
- initialize RTMP Object
- configure RTMP URL
- connect Nginx(Media Stream Server)
- 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视频流