本文記錄一個最簡單的基於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)通過訪問流媒體服務器就可以收看實時流了。
運行本程序之前需要先運行RTMP流媒體服務器,並在流媒體服務器上建立相應的Application。有關流媒體服務器的操作不在本文的論述範圍內,在此不再詳述。本程序運行後,即可通過RTMP客戶端(例如 Flash Player, FFplay等等)收看推送的直播流。
需要要注意的地方
封裝格式
RTMP採用的封裝格式是FLV。因此在指定輸出流媒體的時候需要指定其封裝格式爲“flv”。同理,其他流媒體協議也需要指定其封裝格式。例如採用UDP推送流媒體的時候,可以指定其封裝格式爲“mpegts”。
延時
發送流媒體的數據的時候需要延時。不然的話,FFmpeg處理數據速度很快,瞬間就能把所有的數據發送出去,流媒體服務器是接受不了的。因此需要按照視頻實際的幀率發送數據。本文記錄的推流器在視頻幀與幀之間採用了av_usleep()函數休眠的方式來延遲發送。這樣就可以按照視頻的幀率發送數據了,參考代碼如下。
-
-
int64_t start_time=av_gettime();
-
while (1) {
-
-
-
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等參數。這裏還沒有深入研究,簡單寫了一點代碼,如下所示。
-
-
-
if(pkt.pts==AV_NOPTS_VALUE){
-
-
AVRational time_base1=ifmt_ctx->streams[videoindex]->time_base;
-
-
int64_t calc_duration=(double)AV_TIME_BASE/av_q2d(ifmt_ctx->streams[videoindex]->r_frame_rate);
-
-
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);
-
}
程序流程圖
程序的流程圖如下圖所示。可以看出和《最簡單的基於FFMPEG的封裝格式轉換器(無編解碼)》中的封裝格式轉換器比較類似。它們之間比較明顯的區別在於:
1.
Streamer輸出爲URL
2.
Streamer包含了延時部分
代碼
代碼如下。
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
#include <stdio.h>
-
-
#define __STDC_CONSTANT_MACROS
-
-
#ifdef _WIN32
-
-
extern "C"
-
{
-
#include "libavformat/avformat.h"
-
#include "libavutil/mathematics.h"
-
#include "libavutil/time.h"
-
};
-
#else
-
-
#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 *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.flv";
-
-
-
out_filename = "rtmp://localhost/publishlive/livestream";//輸出 URL(Output URL)[RTMP]
-
-
-
av_register_all();
-
-
avformat_network_init();
-
-
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);
-
-
-
-
avformat_alloc_output_context2(&ofmt_ctx, NULL, "flv", out_filename);
-
-
-
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++) {
-
-
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;
-
}
-
-
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;
-
}
-
-
av_dump_format(ofmt_ctx, 0, out_filename, 1);
-
-
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;
-
}
-
}
-
-
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;
-
-
ret = av_read_frame(ifmt_ctx, &pkt);
-
if (ret < 0)
-
break;
-
-
-
if(pkt.pts==AV_NOPTS_VALUE){
-
-
AVRational time_base1=ifmt_ctx->streams[videoindex]->time_base;
-
-
int64_t calc_duration=(double)AV_TIME_BASE/av_q2d(ifmt_ctx->streams[videoindex]->r_frame_rate);
-
-
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);
-
}
-
-
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];
-
-
-
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;
-
-
if(pkt.stream_index==videoindex){
-
printf("Send %8d video frames to output URL\n",frame_index);
-
frame_index++;
-
}
-
-
ret = av_interleaved_write_frame(ofmt_ctx, &pkt);
-
-
if (ret < 0) {
-
printf( "Error muxing packet\n");
-
break;
-
}
-
-
av_free_packet(&pkt);
-
-
}
-
-
av_write_trailer(ofmt_ctx);
-
end:
-
avformat_close_input(&ifmt_ctx);
-
-
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;
-
}
結果
程序開始運行後。截圖如下所示。
可以通過網頁播放器播放推送的直播流。
例如下圖所示,使用Flash Media Server 的Samples文件夾下的videoPlayer播放直播流的截圖如下圖所示。(直播地址:rtmp://localhost/publishlive/livestream)
此外,也可以通過FFplay這樣的客戶端播放直播流。
下載
simplest ffmpeg streamer
項目主頁
SourceForge:https://sourceforge.net/projects/simplestffmpegstreamer/
Github:https://github.com/leixiaohua1020/simplest_ffmpeg_streamer
開源中國: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上已經更新。