由於工作一部分工作是作爲流媒體服務器的程序員。所以自己那塊也算是處理了推流器的一塊代碼吧。
這邊是從網上轉載的文章,原文:http://blog.csdn.net/leixiaohua1020/article/details/46890487
=====================================================
最簡單的基於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)通過訪問流媒體服務器就可以收看實時流了。
需要要注意的地方
封裝格式
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);
- }
程序流程圖
程序的流程圖如下圖所示。可以看出和《最簡單的基於FFMPEG的封裝格式轉換器(無編解碼)》中的封裝格式轉換器比較類似。它們之間比較明顯的區別在於:1. Streamer輸出爲URL
2. Streamer包含了延時部分
代碼
代碼如下。- /**
- * 最簡單的基於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;
- }
結果
程序開始運行後。截圖如下所示。
可以通過網頁播放器播放推送的直播流。
例如下圖所示,使用Flash Media Server 的Samples文件夾下的videoPlayer播放直播流的截圖如下圖所示。(直播地址:rtmp://localhost/publishlive/livestream)
下載
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
CSDN下載地址:http://download.csdn.net/detail/leixiaohua1020/8445455
SourceForge上已經更新。
simplest_ffmpeg_receiver: 將流媒體數據保存成本地文件。