使用ffmpeg推送視訊流至流媒體伺服器(c語言)
最近研究ffmpeg推送, 研究了ffmpeg內部函式呼叫順序。寫了一個小demo。
/*
* 學習ffmpeg使用例子.
* 如有不足之處,請指出。在此謝謝各位
* 原始碼連結:http://ffmpeg.org/doxygen/2.8/index.html
* ffmpeg使用版本 3.3.2
*/
#include <stdio.h>
#include <stdlib.h>
#include "libavformat/avformat.h"
#include "libavutil/mathematics.h"
#include "libavutil/time.h"
#include "libavcodec/avcodec.h"
//輸入對應的format context
static AVFormatContext *input_format_context = NULL;
//輸出對應的format context
static AVFormatContext *output_format_context = NULL;
//輸出流
static AVStream *input_stream = NULL;
//輸出流
static AVStream *output_stream = NULL;
//包
static AVPacket pkt;
const char *input_file = "1.flv"; //帶路徑的視訊檔案(絕對路徑或相對路徑,本例子使用相對路徑,1.flv表示與c檔案同一目錄下)
const char *output_file = "rtmp://localhost:1935/live/hello";//輸出 URL(Output URL)[RTMP], 例子使用red5作為流媒體伺服器
static int video_index = 0;
static int frame_index = 0;
void init_register_and_network_for_ffmpeg()
{
// 第一步av_register_all();
// 註冊複用器,編碼器等。註冊所有的muxers、demuxers和protocols
av_register_all();
//Network,Do global initialization of network components.
//初始化網路容器。
avformat_network_init();
}
/*
* 建立輸入流
*/
AVStream *create_input_avstream()
{
int ret; // ret儲存函式呼叫結果
// 第一步av_register_all();
// 註冊複用器,編碼器等。註冊所有的muxers、demuxers和protocols
//av_register_all();
//利用ffmpeg預設方法,初始化context。此步驟可以忽略,input_format_context可以為NULL
//第二步中avformat_open_input()函式中有判斷,若為NULL自動呼叫此函式。
input_format_context = avformat_alloc_context();
printf("-------------%s---------------\n", input_format_context->format_whitelist);
//第二步,avformat_open_input();
//開啟多媒體資料並且獲得一些相關的資訊
if ((ret = avformat_open_input(&input_format_context, input_file, 0, 0)) < 0)
{
printf("Can't open input file.");
return NULL;
}
//第三步,avformat_find_stream_info();
//讀取一部分視音訊資料並且獲得一些相關的資訊
if ((ret = avformat_find_stream_info(input_format_context, 0)) < 0) {
printf( "Failed to retrieve input stream information");
return NULL;
}
//初始化完成後,AVFormatContext中存放有AVStream的引用
//通過AVStream中存放的AVCodecContext引用獲取 (enum AVMediaType)列舉中視訊對應的值。
// ***enum AVMediaType
//AVMEDIA_TYPE_UNKNOWN,AVMEDIA_TYPE_VIDEO,AVMEDIA_TYPE_AUDIO,AVMEDIA_TYPE_DATA,AVMEDIA_TYPE_SUBTITLE,AVMEDIA_TYPE_ATTACHMENT,AVMEDIA_TYPE_NB
//例:首先通過迴圈遍歷獲取視訊
for (int i = 0; i < input_format_context->nb_streams; i++) //nb_streams為stream數量
{
//如果stream的codec為AVCodecContext變數,AVCodecContext變數中的codec為AVMediaType列舉值
if (input_format_context->streams[i]->codec->codec == AVMEDIA_TYPE_VIDEO)
{
input_stream = input_format_context->streams[i];
video_index = i;
break;
}
}
//Print detailed information about the input or output format, such as duration, bitrate, streams, container, programs, metadata, side data, codec and time base
//列印輸入輸出format的詳細資訊,該函式可選呼叫。
av_dump_format(input_format_context, 0, input_file, 0);
return input_stream;
}
/*
* 建立輸出流
*/
AVStream *create_output_avstream()
{
int ret;
// 第一步av_register_all();
// 註冊複用器,編碼器等。註冊所有的muxers、demuxers和protocols
//av_register_all();
// 第二步avformat_alloc_output_context2();
//基於FFmpeg的視音訊編碼器程式中,該函式通常是第一個呼叫的函式(除了元件註冊函式av_register_all())
// return >= 0 in case of success, 返回值大於等於0表示成功
avformat_alloc_output_context2(&output_format_context, NULL, "flv", output_file);
if (!output_format_context) {
printf( "Could not create output context\n");
ret = AVERROR_UNKNOWN;
return NULL;
}
//根據輸入流建立輸出流(Create output AVStream according to input AVStream)
AVStream *output_stream = avformat_new_stream(output_format_context, input_stream->codec->codec);
if (!output_stream) {
printf( "Failed allocating output stream\n");
ret = AVERROR_UNKNOWN;
return NULL;
}
//複製AVCodecContext的設定(Copy the settings of AVCodecContext)
ret = avcodec_copy_context(output_stream->codec, input_stream->codec);
if (ret < 0)
{
printf( "Failed to copy context from input to output stream codec context\n");
return NULL;
}
output_stream->codec->codec_tag = 0;
if (output_format_context->oformat->flags & AVFMT_GLOBALHEADER)
output_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
//Print detailed information about the input or output format, such as duration, bitrate, streams, container, programs, metadata, side data, codec and time base
//列印輸入輸出format的詳細資訊,該函式可選呼叫。
av_dump_format(output_format_context, 0, output_file, 1);
return output_stream;
}
int open_url_and_write()
{
int ret;
int64_t start_time = 0; // 開始時間
//開啟輸出URL(Open output URL)
if (!(output_format_context->oformat->flags & AVFMT_NOFILE)) {
ret = avio_open2(&output_format_context->pb, output_file, AVIO_FLAG_WRITE, NULL, NULL); //根據原始碼,呼叫avio_open同效,avio_open中呼叫avio_open2。
//ret = avio_open(&output_format_context->pb, output_file, AVIO_FLAG_WRITE);
if (ret < 0) {
printf( "Could not open output URL '%s'", output_file);
return -1;
}
}
//寫檔案頭(Write file header
//0 on success, negative AVERROR on failure.
ret = avformat_write_header(output_format_context, NULL);
if (ret < 0) {
printf( "Error occurred when opening output URL\n");
return -1;
}
start_time = av_gettime();
while (1) {
AVStream *in_stream, *out_stream;
//獲取一個AVPacket(Get an AVPacket)
ret = av_read_frame(input_format_context, &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=input_format_context->streams[video_index]->time_base;
AVRational time_base1 = input_format_context->streams[video_index]->time_base;
//Duration between 2 frames (us)
int64_t calc_duration = (double)AV_TIME_BASE/av_q2d(input_format_context->streams[video_index]->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 == video_index)
{
AVRational time_base = input_format_context->streams[video_index]->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 = input_format_context->streams[pkt.stream_index];
out_stream = output_format_context->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 == video_index){
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(output_format_context, &pkt);
if (ret < 0) {
printf( "Error muxing packet\n");
break;
}
av_free_packet(&pkt);
}
//寫檔案尾(Write file trailer)
av_write_trailer(output_format_context);
return 0;
}
int close_input_and_output()
{
int ret;
// close input
avformat_close_input(&input_format_context);
/* close output */
if (output_format_context && !(output_format_context->oformat->flags & AVFMT_NOFILE))
avio_close(output_format_context->pb);
avformat_free_context(output_format_context);
if (ret < 0 && ret != AVERROR_EOF) {
printf( "Error occurred.\n");
return -1;
}
return 0;
}
int main(int argc, char *argv[])
{
init_register_and_network_for_ffmpeg();
input_stream = create_input_avstream(); //建立輸入流、初始化輸入context
if (NULL == input_stream)
{
close_input_and_output(); //建立失敗,呼叫close關閉輸入輸出。
return -1;
}
output_stream = create_output_avstream();//建立輸出流、初始化輸出context
if (NULL == output_stream)
{
close_input_and_output(); //建立失敗,呼叫close關閉輸入輸出。
return -1;
}
if(0 != open_url_and_write())
{
close_input_and_output(); //呼叫close關閉輸入輸出。
return -1;
}
//close
close_input_and_output();
}
本人使用的所有檔案目錄如下:
通過命令編譯:
gcc -I/Users/marco/Desktop/libs/include -L/Users/marco/Desktop/libs/lib -lavcodec -lavdevice -lavfilter -lavformat -lavresample -lavutil -lpostproc -lswresample -lswscale pusher.c
如果編譯中出現以下情況:
請找到libavutil/mathematics.h下的AVRounding。將其前面加上typedef。
修改前:
修改後:
相關文章
- FFMPEG 抓RTSP流,推送RTMP至FMS伺服器伺服器
- Go語言實戰流媒體視訊網站Go網站
- nginx+ffmpeg搭建流媒體伺服器(直播流)Nginx伺服器
- FMS 流媒體視訊技術
- 關於視訊流媒體伺服器的學習記錄伺服器
- 使用PHP結合Ffmpeg快速搭建流媒體服務實踐PHP
- 流媒體開發 -- C#C#
- ffmpeg解碼音訊流音訊
- 【史上最全】Nginx+ffmpeg實現流媒體系統Nginx
- 使用Nginx搭建rtmp流媒體伺服器筆記Nginx伺服器筆記
- 什麼是流媒體伺服器?伺服器
- 用VLC做流媒體伺服器伺服器
- 流媒體加密加密
- C# 使用ffmpeg讀取監控影片流C#
- Ubuntu 中使用 Nginx+rtmp 模組搭建流媒體視訊點播服務UbuntuNginx
- 網際網路上的流媒體視訊是如何工作? - QuastorAST
- 流媒體調研:雲端視訊監控與視覺化對講視覺化
- 直播流媒體伺服器解決方案伺服器
- 1.RTMP流媒體伺服器搭建伺服器
- vlc簡單搭建流媒體伺服器伺服器
- nginx上搭建HLS流媒體伺服器Nginx伺服器
- 流媒體技術基礎-流媒體傳輸協議(二)協議
- 流媒體二次開發 -- C++C++
- Java版流媒體編解碼和影像處理(JavaCPP+FFmpeg)Java
- 使用VideoView播放rtsp視訊流IDEView
- 通過私有化部署自建一套視訊流媒體伺服器平臺如何解決視訊播放延時卡頓問題(二)之不同視訊流延時說明伺服器
- ffmpeg——TS流解析
- 基於SRS搭建RTMP直播流媒體伺服器伺服器
- AudienceProject:研究顯示流媒體電視持續激增Project
- 流媒體技術之概念
- 流媒體 Buffer 設計原理
- 流媒體學習---------序 (轉)
- FFmpeg 播放 RTSP/Webcam 流Web
- 點量流媒體伺服器系統釋出啦伺服器
- RTMP視訊直播系統(PC網頁/微信小程式/播放器/流媒體)網頁微信小程式播放器
- 解鎖視訊編碼的前世今生:流媒體產業的隱藏劇情產業
- 流媒體音視訊開發有哪些學習書籍?推薦11本
- [譯] 再看 Flask 視訊流Flask