最簡單的基於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採用的封裝格式是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;
- constchar *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
專案主頁
更新-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: 將流媒體資料儲存成本地檔案。SourceForge、Github等上面已經更新。
相關推薦
最簡單的基於FFmpeg的推流器(以推送RTMP為例)
由於工作一部分工作是作為流媒體伺服器的程式設計師。所以自己那塊也算是處理了推流器的一塊程式碼吧。 這邊是從網上轉載的文章,原文: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]內