天天看點

最簡單的基于FFmpeg的推流器(以推送RTMP為例)

作者:音視訊流媒體技術

最簡單的基于FFmpeg的推流器系列文章清單:

《最簡單的基于FFmpeg的推流器(以推送RTMP為例)》

《最簡單的基于FFMPEG的推流器附件:收流器》

本文記錄一個最簡單的基于FFmpeg的推流器(simplest ffmpeg streamer)。推流器的作用就是将本地的視訊資料推送至流媒體伺服器。本文記錄的推流器,可以将本地的 MOV / AVI / MKV / MP4 / FLV 等格式的媒體檔案,通過流媒體協定(例如RTMP,HTTP,UDP,TCP,RTP等等)以直播流的形式推送出去。由于流媒體協定種類繁多,不一一記錄。在這裡記錄将本地檔案以RTMP直播流的形式推送至RTMP流媒體伺服器(例如 Flash Media Server,Red5,Wowza等等)的方法。

在這個推流器的基礎上可以進行多種方式的修改,實作各式各樣的推流器。例如:

* 将輸入檔案改為網絡流URL,可以實作轉流器。

* 将輸入的檔案改為回調函數(記憶體讀取)的形式,可以推送記憶體中的視訊資料。

* 将輸入檔案改為系統裝置(通過libavdevice),同時加上編碼的功能,可以實作實時推流器(現場直播)。

PS:本程式并不包含視訊轉碼的功能。

簡介

RTMP推流器(Streamer)的在流媒體系統中的作用可以用下圖表示。首先将視訊資料以RTMP的形式發送到流媒體伺服器端(Server,比如FMS,Red5,Wowza等),然後用戶端(一般為Flash Player)通過通路流媒體伺服器就可以收看實時流了。

最簡單的基于FFmpeg的推流器(以推送RTMP為例)

運作本程式之前需要先運作RTMP流媒體伺服器,并在流媒體伺服器上建立相應的Application。有關流媒體伺服器的操作不在本文的論述範圍内,在此不再詳述。本程式運作後,即可通過RTMP用戶端(例如 Flash Player, FFplay等等)收看推送的直播流。

需要要注意的地方

封裝格式

RTMP采用的封裝格式是FLV。是以在指定輸出流媒體的時候需要指定其封裝格式為“flv”。同理,其他流媒體協定也需要指定其封裝格式。例如采用UDP推送流媒體的時候,可以指定其封裝格式為“mpegts”。

延時

發送流媒體的資料的時候需要延時。不然的話,FFmpeg處理資料速度很快,瞬間就能把所有的資料發送出去,流媒體伺服器是接受不了的。是以需要按照視訊實際的幀率發送資料。本文記錄的推流器在視訊幀與幀之間采用了av_usleep()函數休眠的方式來延遲發送。這樣就可以按照視訊的幀率發送資料了,參考代碼如下。

//…
int64_t start_time=av_gettime();
while (1) {
//…
	//Important:Delay
	if(pkt.stream_index==videoindex){
		AVRational time_base=ifmt_ctx->streams[videoindex]->time_base;
		AVRational time_base_q={1,AV_TIME_BASE};
		int64_t pts_time = av_rescale_q(pkt.dts, time_base, time_base_q);
		int64_t now_time = av_gettime() - start_time;
		if (pts_time > now_time)
			av_usleep(pts_time - now_time);
	}
//…
}
//…           

PTS/DTS問題

沒有封裝格式的裸流(例如H.264裸流)是不包含PTS、DTS這些參數的。在發送這種資料的時候,需要自己計算并寫入AVPacket的PTS,DTS,duration等參數。這裡還沒有深入研究,簡單寫了一點代碼,如下所示。

//FIX:No PTS (Example: Raw H.264)
//Simple Write PTS
if(pkt.pts==AV_NOPTS_VALUE){
	//Write PTS
	AVRational time_base1=ifmt_ctx->streams[videoindex]->time_base;
	//Duration between 2 frames (us)
	int64_t calc_duration=(double)AV_TIME_BASE/av_q2d(ifmt_ctx->streams[videoindex]->r_frame_rate);
	//Parameters
	pkt.pts=(double)(frame_index*calc_duration)/(double)(av_q2d(time_base1)*AV_TIME_BASE);
	pkt.dts=pkt.pts;
	pkt.duration=(double)calc_duration/(double)(av_q2d(time_base1)*AV_TIME_BASE);
}           

相關學習資料推薦,點選下方連結免費報名,先碼住不迷路~】

【免費分享】音視訊學習資料包、大廠面試題、技術視訊和學習路線圖,資料包括(C/C++,Linux,FFmpeg webRTC rtmp hls rtsp ffplay srs 等等)有需要的可以點選加群免費領取~

最簡單的基于FFmpeg的推流器(以推送RTMP為例)

程式流程圖

程式的流程圖如下圖所示。可以看出和《最簡單的基于FFMPEG的封裝格式轉換器(無編解碼)》中的封裝格式轉換器比較類似。它們之間比較明顯的差別在于:

1.Streamer輸出為URL

2.Streamer包含了延時部分

最簡單的基于FFmpeg的推流器(以推送RTMP為例)

代碼

代碼如下。

/**
 * 最簡單的基于FFmpeg的推流器(推送RTMP)
 * Simplest FFmpeg Streamer (Send RTMP)
 * 
 * 雷霄骅 Lei Xiaohua
 * [email protected]
 * 中國傳媒大學/數字電視技術
 * Communication University of China / Digital TV Technology
 * http://blog.csdn.net/leixiaohua1020
 * 
 * 本例子實作了推送本地視訊至流媒體伺服器(以RTMP為例)。
 * 是使用FFmpeg進行流媒體推送最簡單的教程。
 *
 * This example stream local media files to streaming media 
 * server (Use RTMP as example). 
 * It's the simplest FFmpeg streamer.
 * 
 */
 
#include <stdio.h>
 
#define __STDC_CONSTANT_MACROS
 
#ifdef _WIN32
//Windows
extern "C"
{
#include "libavformat/avformat.h"
#include "libavutil/mathematics.h"
#include "libavutil/time.h"
};
#else
//Linux...
#ifdef __cplusplus
extern "C"
{
#endif
#include <libavformat/avformat.h>
#include <libavutil/mathematics.h>
#include <libavutil/time.h>
#ifdef __cplusplus
};
#endif
#endif
 
int main(int argc, char* argv[])
{
	AVOutputFormat *ofmt = NULL;
	//輸入對應一個AVFormatContext,輸出對應一個AVFormatContext
	//(Input AVFormatContext and Output AVFormatContext)
	AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL;
	AVPacket pkt;
	const char *in_filename, *out_filename;
	int ret, i;
	int videoindex=-1;
	int frame_index=0;
	int64_t start_time=0;
	//in_filename  = "cuc_ieschool.mov";
	//in_filename  = "cuc_ieschool.mkv";
	//in_filename  = "cuc_ieschool.ts";
	//in_filename  = "cuc_ieschool.mp4";
	//in_filename  = "cuc_ieschool.h264";
	in_filename  = "cuc_ieschool.flv";//輸入URL(Input file URL)
	//in_filename  = "shanghai03_p.h264";
	
	out_filename = "rtmp://localhost/publishlive/livestream";//輸出 URL(Output URL)[RTMP]
	//out_filename = "rtp://233.233.233.233:6666";//輸出 URL(Output URL)[UDP]
 
	av_register_all();
	//Network
	avformat_network_init();
	//輸入(Input)
	if ((ret = avformat_open_input(&ifmt_ctx, in_filename, 0, 0)) < 0) {
		printf( "Could not open input file.");
		goto end;
	}
	if ((ret = avformat_find_stream_info(ifmt_ctx, 0)) < 0) {
		printf( "Failed to retrieve input stream information");
		goto end;
	}
 
	for(i=0; i<ifmt_ctx->nb_streams; i++) 
		if(ifmt_ctx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO){
			videoindex=i;
			break;
		}
 
	av_dump_format(ifmt_ctx, 0, in_filename, 0);
 
	//輸出(Output)
	
	avformat_alloc_output_context2(&ofmt_ctx, NULL, "flv", out_filename); //RTMP
	//avformat_alloc_output_context2(&ofmt_ctx, NULL, "mpegts", out_filename);//UDP
 
	if (!ofmt_ctx) {
		printf( "Could not create output context\n");
		ret = AVERROR_UNKNOWN;
		goto end;
	}
	ofmt = ofmt_ctx->oformat;
	for (i = 0; i < ifmt_ctx->nb_streams; i++) {
		//根據輸入流建立輸出流(Create output AVStream according to input AVStream)
		AVStream *in_stream = ifmt_ctx->streams[i];
		AVStream *out_stream = avformat_new_stream(ofmt_ctx, in_stream->codec->codec);
		if (!out_stream) {
			printf( "Failed allocating output stream\n");
			ret = AVERROR_UNKNOWN;
			goto end;
		}
		//複制AVCodecContext的設定(Copy the settings of AVCodecContext)
		ret = avcodec_copy_context(out_stream->codec, in_stream->codec);
		if (ret < 0) {
			printf( "Failed to copy context from input to output stream codec context\n");
			goto end;
		}
		out_stream->codec->codec_tag = 0;
		if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
			out_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
	}
	//Dump Format------------------
	av_dump_format(ofmt_ctx, 0, out_filename, 1);
	//打開輸出URL(Open output URL)
	if (!(ofmt->flags & AVFMT_NOFILE)) {
		ret = avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE);
		if (ret < 0) {
			printf( "Could not open output URL '%s'", out_filename);
			goto end;
		}
	}
	//寫檔案頭(Write file header)
	ret = avformat_write_header(ofmt_ctx, NULL);
	if (ret < 0) {
		printf( "Error occurred when opening output URL\n");
		goto end;
	}
 
	start_time=av_gettime();
	while (1) {
		AVStream *in_stream, *out_stream;
		//擷取一個AVPacket(Get an AVPacket)
		ret = av_read_frame(ifmt_ctx, &pkt);
		if (ret < 0)
			break;
		//FIX:No PTS (Example: Raw H.264)
		//Simple Write PTS
		if(pkt.pts==AV_NOPTS_VALUE){
			//Write PTS
			AVRational time_base1=ifmt_ctx->streams[videoindex]->time_base;
			//Duration between 2 frames (us)
			int64_t calc_duration=(double)AV_TIME_BASE/av_q2d(ifmt_ctx->streams[videoindex]->r_frame_rate);
			//Parameters
			pkt.pts=(double)(frame_index*calc_duration)/(double)(av_q2d(time_base1)*AV_TIME_BASE);
			pkt.dts=pkt.pts;
			pkt.duration=(double)calc_duration/(double)(av_q2d(time_base1)*AV_TIME_BASE);
		}
		//Important:Delay
		if(pkt.stream_index==videoindex){
			AVRational time_base=ifmt_ctx->streams[videoindex]->time_base;
			AVRational time_base_q={1,AV_TIME_BASE};
			int64_t pts_time = av_rescale_q(pkt.dts, time_base, time_base_q);
			int64_t now_time = av_gettime() - start_time;
			if (pts_time > now_time)
				av_usleep(pts_time - now_time);
 
		}
 
		in_stream  = ifmt_ctx->streams[pkt.stream_index];
		out_stream = ofmt_ctx->streams[pkt.stream_index];
		/* copy packet */
		//轉換PTS/DTS(Convert PTS/DTS)
		pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
		pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
		pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
		pkt.pos = -1;
		//Print to Screen
		if(pkt.stream_index==videoindex){
			printf("Send %8d video frames to output URL\n",frame_index);
			frame_index++;
		}
		//ret = av_write_frame(ofmt_ctx, &pkt);
		ret = av_interleaved_write_frame(ofmt_ctx, &pkt);
 
		if (ret < 0) {
			printf( "Error muxing packet\n");
			break;
		}
		
		av_free_packet(&pkt);
		
	}
	//寫檔案尾(Write file trailer)
	av_write_trailer(ofmt_ctx);
end:
	avformat_close_input(&ifmt_ctx);
	/* close output */
	if (ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE))
		avio_close(ofmt_ctx->pb);
	avformat_free_context(ofmt_ctx);
	if (ret < 0 && ret != AVERROR_EOF) {
		printf( "Error occurred.\n");
		return -1;
	}
	return 0;
}
 
            

結果

程式開始運作後。截圖如下所示。

最簡單的基于FFmpeg的推流器(以推送RTMP為例)

可以通過網頁播放器播放推送的直播流。

例如下圖所示,使用Flash Media Server 的Samples檔案夾下的videoPlayer播放直播流的截圖如下圖所示。(直播位址:rtmp://localhost/publishlive/livestream)

最簡單的基于FFmpeg的推流器(以推送RTMP為例)

此外,也可以通過FFplay這樣的用戶端播放直播流。

下載下傳

simplest ffmpeg streamer

項目首頁

SourceForge:simplest ffmpeg streamer

Github:GitHub - leixiaohua1020/simplest_ffmpeg_streamer: Simplest streamer based on FFmpeg

開源中國:http://git.oschina.net/leixiaohua1020/simplest_ffmpeg_streamer

CSDN下載下傳位址:

http://download.csdn.net/detail/leixiaohua1020/8005311

更新-1.1 (2015.2.13)=========================================

這次考慮到了跨平台的要求,調整了源代碼。經過這次調整之後,源代碼可以在以下平台編譯通過:

VC++:打開sln檔案即可編譯,無需配置。

cl.exe:打開compile_cl.bat即可指令行下使用cl.exe進行編譯,注意可能需要按照VC的安裝路徑調整腳本裡面的參數。編譯指令如下。

::VS2010 Environment
call "D:\Program Files\Microsoft Visual Studio 10.0\VC\vcvarsall.bat"
::include
@set INCLUDE=include;%INCLUDE%;
::lib
@set LIB=lib;%LIB%;
::compile and link
cl simplest_ffmpeg_streamer.cpp /link avcodec.lib avformat.lib avutil.lib ^
avdevice.lib avfilter.lib postproc.lib swresample.lib swscale.lib /OPT:NOREF           

MinGW:MinGW指令行下運作compile_mingw.sh即可使用MinGW的g++進行編譯。編譯指令如下。

g++ simplest_ffmpeg_streamer.cpp -g -o simplest_ffmpeg_streamer.exe \
-I /usr/local/include -L /usr/local/lib -lavformat -lavcodec -lavutil           

GCC:Linux或者MacOS指令行下運作compile_gcc.sh即可使用GCC進行編譯。編譯指令如下。

gcc simplest_ffmpeg_streamer.cpp -g -o simplest_ffmpeg_streamer.out \
-I /usr/local/include -L /usr/local/lib -lavformat -lavcodec -lavutil           

PS:相關的編譯指令已經儲存到了工程檔案夾中

CSDN下載下傳位址:http://download.csdn.net/detail/leixiaohua1020/8445455

SourceForge上已經更新。

更新-1.2(2015.7.17)==============================

增加了下列工程:

simplest_ffmpeg_receiver: 将流媒體資料儲存成本地檔案。

CSDN項目下載下傳位址: http://download.csdn.net/detail/leixiaohua1020/8924345

SourceForge、Github等上面已經更新。

原文連結:最簡單的基于FFmpeg的推流器(以推送RTMP為例)_ffmpeg推流rtmp_雷霄骅的部落格-CSDN部落格