1. 程式人生 > >最簡單的基於FFmpeg的推流器(以推送RTMP為例)

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

由於工作一部分工作是作為流媒體伺服器的程式設計師。所以自己那塊也算是處理了推流器的一塊程式碼吧。

這邊是從網上轉載的文章,原文: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流媒體伺服器,並在流媒體伺服器上建立相應的Application。有關流媒體伺服器的操作不在本文的論述範圍內,在此不再詳述。本程式執行後,即可通過RTMP客戶端(例如 Flash Player, FFplay等等)收看推送的直播流。

需要要注意的地方

封裝格式

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

延時

傳送流媒體的資料的時候需要延時。不然的話,FFmpeg處理資料速度很快,瞬間就能把所有的資料傳送出去,流媒體伺服器是接受不了的。因此需要按照視訊實際的幀率傳送資料。本文記錄的推流器在視訊幀與幀之間採用了av_usleep()函式休眠的方式來延遲傳送。這樣就可以按照視訊的幀率傳送資料了,參考程式碼如下。
  1. //…
  2. int64_t start_time=av_gettime();  
  3. while (1) {  
  4. //…
  5.     //Important:Delay
  6.     if(pkt.stream_index==videoindex){  
  7.         AVRational time_base=ifmt_ctx->streams[videoindex]->time_base;  
  8.         AVRational time_base_q={1,AV_TIME_BASE};  
  9.         int64_t pts_time = av_rescale_q(pkt.dts, time_base, time_base_q);  
  10.         int64_t now_time = av_gettime() - start_time;  
  11.         if (pts_time > now_time)  
  12.             av_usleep(pts_time - now_time);  
  13.     }  
  14. //…
  15. }  
  16. //…

PTS/DTS問題

沒有封裝格式的裸流(例如H.264裸流)是不包含PTS、DTS這些引數的。在傳送這種資料的時候,需要自己計算並寫入AVPacket的PTS,DTS,duration等引數。這裡還沒有深入研究,簡單寫了一點程式碼,如下所示。
  1. //FIX:No PTS (Example: Raw H.264)
  2. //Simple Write PTS
  3. if(pkt.pts==AV_NOPTS_VALUE){  
  4.     //Write PTS
  5.     AVRational time_base1=ifmt_ctx->streams[videoindex]->time_base;  
  6.     //Duration between 2 frames (us)
  7.     int64_t calc_duration=(double)AV_TIME_BASE/av_q2d(ifmt_ctx->streams[videoindex]->r_frame_rate);  
  8.     //Parameters
  9.     pkt.pts=(double)(frame_index*calc_duration)/(double)(av_q2d(time_base1)*AV_TIME_BASE);  
  10.     pkt.dts=pkt.pts;  
  11.     pkt.duration=(double)calc_duration/(double)(av_q2d(time_base1)*AV_TIME_BASE);  
  12. }  

程式流程圖

程式的流程圖如下圖所示。可以看出和《最簡單的基於FFMPEG的封裝格式轉換器(無編解碼)》中的封裝格式轉換器比較類似。它們之間比較明顯的區別在於:
1.Streamer輸出為URL
2.Streamer包含了延時部分

 

程式碼

程式碼如下。
  1. /** 
  2.  * 最簡單的基於FFmpeg的推流器(推送RTMP) 
  3.  * Simplest FFmpeg Streamer (Send RTMP) 
  4.  *  
  5.  * 雷霄驊 Lei Xiaohua 
  6.  * [email protected] 
  7.  * 中國傳媒大學/數字電視技術 
  8.  * Communication University of China / Digital TV Technology 
  9.  * http://blog.csdn.net/leixiaohua1020 
  10.  *  
  11.  * 本例子實現了推送本地視訊至流媒體伺服器(以RTMP為例)。 
  12.  * 是使用FFmpeg進行流媒體推送最簡單的教程。 
  13.  * 
  14.  * This example stream local media files to streaming media  
  15.  * server (Use RTMP as example).  
  16.  * It's the simplest FFmpeg streamer. 
  17.  *  
  18.  */
  19. #include <stdio.h>
  20. #define __STDC_CONSTANT_MACROS
  21. #ifdef _WIN32
  22. //Windows
  23. extern"C"
  24. {  
  25. #include "libavformat/avformat.h"
  26. #include "libavutil/mathematics.h"
  27. #include "libavutil/time.h"
  28. };  
  29. #else
  30. //Linux...
  31. #ifdef __cplusplus
  32. extern"C"
  33. {  
  34. #endif
  35. #include <libavformat/avformat.h>
  36. #include <libavutil/mathematics.h>
  37. #include <libavutil/time.h>
  38. #ifdef __cplusplus
  39. };  
  40. #endif
  41. #endif
  42. int main(int argc, char* argv[])  
  43. {  
  44.     AVOutputFormat *ofmt = NULL;  
  45.     //輸入對應一個AVFormatContext,輸出對應一個AVFormatContext
  46.     //(Input AVFormatContext and Output AVFormatContext)
  47.     AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL;  
  48.     AVPacket pkt;  
  49.     constchar *in_filename, *out_filename;  
  50.     int ret, i;  
  51.     int videoindex=-1;  
  52.     int frame_index=0;  
  53.     int64_t start_time=0;  
  54.     //in_filename  = "cuc_ieschool.mov";
  55.     //in_filename  = "cuc_ieschool.mkv";
  56.     //in_filename  = "cuc_ieschool.ts";
  57.     //in_filename  = "cuc_ieschool.mp4";
  58.     //in_filename  = "cuc_ieschool.h264";
  59.     in_filename  = "cuc_ieschool.flv";//輸入URL(Input file URL)
  60.     //in_filename  = "shanghai03_p.h264";
  61.     out_filename = "rtmp://localhost/publishlive/livestream";//輸出 URL(Output URL)[RTMP]  
  62.     //out_filename = "rtp://233.233.233.233:6666";//輸出 URL(Output URL)[UDP]
  63.     av_register_all();  
  64.     //Network
  65.     avformat_network_init();  
  66.     //輸入(Input)
  67.     if ((ret = avformat_open_input(&ifmt_ctx, in_filename, 0, 0)) < 0) {  
  68.         printf( "Could not open input file.");  
  69.         goto end;  
  70.     }  
  71.     if ((ret = avformat_find_stream_info(ifmt_ctx, 0)) < 0) {  
  72.         printf( "Failed to retrieve input stream information");  
  73.         goto end;  
  74.     }  
  75.     for(i=0; i<ifmt_ctx->nb_streams; i++)   
  76.         if(ifmt_ctx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO){  
  77.             videoindex=i;  
  78.             break;  
  79.         }  
  80.     av_dump_format(ifmt_ctx, 0, in_filename, 0);  
  81.     //輸出(Output)
  82.     avformat_alloc_output_context2(&ofmt_ctx, NULL, "flv", out_filename); //RTMP
  83.     //avformat_alloc_output_context2(&ofmt_ctx, NULL, "mpegts", out_filename);//UDP
  84.     if (!ofmt_ctx) {  
  85.         printf( "Could not create output context\n");  
  86.         ret = AVERROR_UNKNOWN;  
  87.         goto end;  
  88.     }  
  89.     ofmt = ofmt_ctx->oformat;  
  90.     for (i = 0; i < ifmt_ctx->nb_streams; i++) {  
  91.         //根據輸入流建立輸出流(Create output AVStream according to input AVStream)
  92.         AVStream *in_stream = ifmt_ctx->streams[i];  
  93.         AVStream *out_stream = avformat_new_stream(ofmt_ctx, in_stream->codec->codec);  
  94.         if (!out_stream) {  
  95.             printf( "Failed allocating output stream\n");  
  96.             ret = AVERROR_UNKNOWN;  
  97.             goto end;  
  98.         }  
  99.         //複製AVCodecContext的設定(Copy the settings of AVCodecContext)
  100.         ret = avcodec_copy_context(out_stream->codec, in_stream->codec);  
  101.         if (ret < 0) {  
  102.             printf( "Failed to copy context from input to output stream codec context\n");  
  103.             goto end;  
  104.         }  
  105.         out_stream->codec->codec_tag = 0;  
  106.         if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)  
  107.             out_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;  
  108.     }  
  109.     //Dump Format------------------
  110.     av_dump_format(ofmt_ctx, 0, out_filename, 1);  
  111.     //開啟輸出URL(Open output URL)
  112.     if (!(ofmt->flags & AVFMT_NOFILE)) {  
  113.         ret = avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE);  
  114.         if (ret < 0) {  
  115.             printf( "Could not open output URL '%s'", out_filename);  
  116.             goto end;  
  117.         }  
  118.     }  
  119.     //寫檔案頭(Write file header)
  120.     ret = avformat_write_header(ofmt_ctx, NULL);  
  121.     if (ret < 0) {  
  122.         printf( "Error occurred when opening output URL\n");  
  123.         goto end;  
  124.     }  
  125.     start_time=av_gettime();  
  126.     while (1) {  
  127.         AVStream *in_stream, *out_stream;  
  128.         //獲取一個AVPacket(Get an AVPacket)
  129.         ret = av_read_frame(ifmt_ctx, &pkt);  
  130.         if (ret < 0)  
  131.             break;  
  132.         //FIX:No PTS (Example: Raw H.264)
  133.         //Simple Write PTS
  134.         if(pkt.pts==AV_NOPTS_VALUE){  
  135.             //Write PTS
  136.             AVRational time_base1=ifmt_ctx->streams[videoindex]->time_base;  
  137.             //Duration between 2 frames (us)
  138.             int64_t calc_duration=(double)AV_TIME_BASE/av_q2d(ifmt_ctx->streams[videoindex]->r_frame_rate);  
  139.             //Parameters
  140.             pkt.pts=(double)(frame_index*calc_duration)/(double)(av_q2d(time_base1)*AV_TIME_BASE);  
  141.             pkt.dts=pkt.pts;  
  142.             pkt.duration=(double)calc_duration/(double)(av_q2d(time_base1)*AV_TIME_BASE);  
  143.         }  
  144.         //Important:Delay
  145.         if(pkt.stream_index==videoindex){  
  146.             AVRational time_base=ifmt_ctx->streams[videoindex]->time_base;  
  147.             AVRational time_base_q={1,AV_TIME_BASE};  
  148.             int64_t pts_time = av_rescale_q(pkt.dts, time_base, time_base_q);  
  149.             int64_t now_time = av_gettime() - start_time;  
  150.             if (pts_time > now_time)  
  151.                 av_usleep(pts_time - now_time);  
  152.         }  
  153.         in_stream  = ifmt_ctx->streams[pkt.stream_index];  
  154.         out_stream = ofmt_ctx->streams[pkt.stream_index];  
  155.         /* copy packet */
  156.         //轉換PTS/DTS(Convert PTS/DTS)
  157.         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));  
  158.         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));  
  159.         pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);  
  160.         pkt.pos = -1;  
  161.         //Print to Screen
  162.         if(pkt.stream_index==videoindex){  
  163.             printf("Send %8d video frames to output URL\n",frame_index);  
  164.             frame_index++;  
  165.         }  
  166.         //ret = av_write_frame(ofmt_ctx, &pkt);
  167.         ret = av_interleaved_write_frame(ofmt_ctx, &pkt);  
  168.         if (ret < 0) {  
  169.             printf( "Error muxing packet\n");  
  170.             break;  
  171.         }  
  172.         av_free_packet(&pkt);  
  173.     }  
  174.     //寫檔案尾(Write file trailer)
  175.     av_write_trailer(ofmt_ctx);  
  176. end:  
  177.     avformat_close_input(&ifmt_ctx);  
  178.     /* close output */
  179.     if (ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE))  
  180.         avio_close(ofmt_ctx->pb);  
  181.     avformat_free_context(ofmt_ctx);  
  182.     if (ret < 0 && ret != AVERROR_EOF) {  
  183.         printf( "Error occurred.\n");  
  184.         return -1;  
  185.     }  
  186.     return 0;  
  187. }  


結果

程式開始執行後。截圖如下所示。


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

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


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

下載

simplest ffmpeg streamer

專案主頁

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

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

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

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

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

MinGW:MinGW命令列下執行compile_mingw.sh即可使用MinGW的g++進行編譯。編譯命令如下。

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

GCC:Linux或者MacOS命令列下執行compile_gcc.sh即可使用GCC進行編譯。編譯命令如下。

  1. gcc simplest_ffmpeg_streamer.cpp -g -o simplest_ffmpeg_streamer.out \  
  2. -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: 將流媒體資料儲存成本地檔案。
SourceForge、Github等上面已經更新。

相關推薦

簡單基於FFmpegRTMP

由於工作一部分工作是作為流媒體伺服器的程式設計師。所以自己那塊也算是處理了推流器的一塊程式碼吧。 這邊是從網上轉載的文章,原文:http://blog.csdn.net/leixiaohua1020/article/details/46890487 =========

基於GDAL庫,讀取.grd檔案海洋地形資料C++版

技術背景   海洋地形資料主要是通過美國全球地形起伏資料(GMT)獲得,資料格式為grd(GSBG)二進位制資料,開啟軟體通過是Surfer軟體,surfer軟體可進行資料的編輯處理,以及進一步的視覺化表達等功能操作;由於Surfer軟體不支援二次開發,沒有提供相應的SDK供開發者進行使用,所以這一切只能通

基於深度學習的推薦演算法實現MovieLens 1M資料

前言 本專案使用文字卷積神經網路,並使用MovieLens資料集完成電影推薦的任務。  推薦系統在日常的網路應用中無處不在,比如網上購物、網上買書、新聞app、社交網路、音樂網站、電影網站等等等等,有人的地方就有推薦。根據個人的喜好,相同喜好人群的習慣等資訊進行個性化

六種姿勢拿下連續子序列大和問題,附虛擬碼HDU 1003 1231

問題描述:       連續子序列最大和,其實就是求一個序列中連續的子序列中元素和最大的那個。       比如例如給定序列:            { -2, 11, -4, 13, -5, -2 }         其最大連續子序列為{ 11, -4, 13 },最大和

關於端口被占用的問題61440端口

internet 其他 直接 按鍵 str es2017 查看 window 原因   先來說一下寫這篇blog的原因吧,本人今天下午回到宿舍打開電腦準備上網下載開發Python的環境,結果發現下載速度和以前相比是大大的不如。然後看了一下室友的網絡一切ok,然後我突然只能登

當你輸入一個網址/點擊一個鏈接,發生了什麽?www.baidu.com

var aid 查找 style str 在服務器 開始 傳輸 http響應 >>>點擊網址後,應用層的DNS協議會將網址解析為IP地址; DNS查找過程: 瀏覽器會檢查緩存中有沒有這個域名對應的解析過的IP地址,如果緩存中有,這個解析過程就將結束。 如果

使用Altium Designer10軟件繪制芯片引腳圖IC1114芯片

name 類型 對話 圖1 菜單欄 參數設置 計算 sch 位置 運用Altium Designer軟件繪制IC1114-F48LQ芯片的原理圖庫,詳細步驟如下: 第一步:在“文檔”裏新建一個文件夾,命名為“電子產品創新設計源文件”,用以存放每次實驗的源文件和原理圖庫文件等

Visual Studio高低版本的問題2008和2015

image 記得 標準 方法 log 版本 問題 不同 查看 第一個就是改這個裏面的配置 第二個是改這個裏面的配置 方法:通過觀察這兩個的不同,並且同時看低版本打開高版本的時候的錯誤提示來看應該改哪一些配置文件之後就可以進行對比操作,還有就是如果不小心改錯了,比如說20

Python——爬取人口遷徙數據騰訊遷徙

map car img all spa ima tps .sh compile 說明: 1.遷徙量是騰訊修改後的數值,無法確認真實性。 2.代碼運行期間,騰訊遷徙未設置IP屏蔽和瀏覽器檢測,因此下段代碼僅能保證發布近期有效。 3.代碼功能:爬取指定一天的四十個城市左右的遷徙

Qt的網絡通信一對一聊天室

lis sci idg ESS host 文字 btn stdstring nec 一、以一對一(服務器,客戶端)為例   1、服務器:      1、在目錄文件 .pro文件中   QT += core gui network 添加network      

【C語言】位元組對齊問題32位系統

  1. 什麼是對齊?   現代計算機中記憶體空間都是按照位元組(byte)劃分的,從理論上講似乎對任何型別的變數的訪問可以從任何地址開始,但實際情況是在訪問特定變數的時候經常在特定的記憶體地址訪問,這就需要各型別資料按照一定的規則在空間上排列,而不是順序地一個接一個地排放,這就是對齊

Tensorflow之多元線性迴歸問題波士頓房價預測

一、根據波士頓房價資訊進行預測,多元線性迴歸+特徵資料歸一化 #讀取資料 %matplotlib notebook import tensorflow as tf import matplotlib.pyplot as plt import numpy as np

詳解如何用爬蟲採集視訊播放量資料騰訊視訊

現代社會提到大資料大家都知道這是近幾年才形成的對於資料相關的新名詞,在1980年,著名未來學家阿爾文·托夫勒便在 《第三次浪潮》一書中,將大資料熱情地讚頌為“第三次浪潮的 華彩樂章”。在 20 世紀 80 年代我國已經有一些專家學者談到了海量資料的加工和管理,但是由於計算機技術和網路技術的限制大資料

在大資料浪潮下如何高效的獲取跨境電商銷售資料亞馬遜

隨著傳統外貿渠道的疲態,跨境電商現在發展異常火爆,跨境電商未來很有可能成為主流的外貿出口模式,並且成為推動中國外貿經濟的一個重要的突破口。跨境電商主要分跨境進口和跨境出口,主流的跨境電商平臺各有特點。 亞馬遜公司作為美國最大的一家網路電子商務公司,是網路上最早開始經營電子商務的公司之一,已成為全

3. CKeditor+ckfinder ---CKFinder原始碼修改上傳 自定義資料夾名時間年月YYYYMM

1 準備 請自行參考  第2文章  此處就不做過多解釋了 ^_^     2. CKeditor+ckfinder ---CKFinder原始碼修改自定義上傳檔名,連結如下 修改上傳檔名 2  查詢官網資料 連結如下

idea給現有web專案建立子maven web模組專案--搭建流程淘淘商城

maven  web專案主要搭建流程: 1、用任意Java開發工具ide中maven外掛(或其他外掛)建立父子web專案骨架 2、完善web專案的目錄結構(如果以下骨架已自動建好,忽略此步驟)         src/main/java       src/main/

基本資料型別和包裝類的關係int和Integer

public void test(){ int a=10; Integer b=new Integer(10); Integer d= Integer.valueOf(10); Integer e= Integer.valueOf(10); Integer c=10; In

Ubuntu下如何安裝第三方輸入法!搜狗輸入法

其實很簡單,就是: 百度”搜狗輸入法 Ubuntu“,下載,安裝,重啟。 這麼簡單,為什麼還要寫這篇文章呢? 因為你不懂的話,你就回去百度搜索這個問題,然後別人就教你如何設定預設的輸入法, 至少浪費十幾分鍾。其實重啟一下就可以了。

Docker映象製作使用操作流程bcc-explorer專案

Docker映象製作使用流程(以bcc-explorer專案為例) bcc-explorer專案是最近實習時做的一個專案,這裡只是用來舉例說明docker的用法,具體專案內容不是重點。 1 編寫dockerfile檔案 1) 進入bitcore-node

均勻分佈構造離散型隨機變數泊松分佈python

我們知道,把服從(0,1)均勻分佈的隨機變數代入任一連續性分佈函式的反函式,即可得到服從該分佈的隨機變數。那麼,若分佈是離散型的呢? 其實與構造連續性隨機變數的思想是一樣的。因為分佈函式在[0,1]內