1、推流介紹
推流是將輸入視頻數(shù)據(jù)推送至流媒體服務(wù)器, 輸入視頻數(shù)據(jù)可以是本地視頻文件(avi,mp4,flv......),也可以是內(nèi)存視頻數(shù)據(jù),或者攝像頭等系統(tǒng)設(shè)備,也可以是網(wǎng)絡(luò)流URL。本篇介紹將本地視頻文件通過(guò)FFmpeg編程以RTMP直播流的形式推送至RTMP流媒體服務(wù)器的方法。
推流的網(wǎng)絡(luò)拓?fù)浣Y(jié)構(gòu)如下:

RTMP流媒體服務(wù)器: 采用nginx+rtmp module實(shí)現(xiàn)
RTMP拉流器:
RTMP推流器:采用ffmpeg實(shí)現(xiàn)
需要注意的是,RTMP采用的封裝格式是FLV。在指定輸出流媒體格式的時(shí)候需要指定其封裝格式為“flv”。同理,其他流媒體協(xié)議也需要指定其封裝格式。例如采用UDP推送流媒體的時(shí)候,可以指定其封裝格式為“mpegts”。
2、FFmpeg推流
FFMpeg處理RTMP流有兩種方式:
一個(gè)是使用自帶的RTMP代碼功能;
一個(gè)是使用第三方庫(kù)librtmp;
這兩種方式是有些區(qū)別的
1. FFmpeg自帶的RTMP代碼功能
FFmpeg自帶的RTMP代碼只支持RTMP協(xié)議,不支持rtmpt,rtmpe,rtmpte和rtmps協(xié)議;
命令行設(shè)置如下:
\1. 將RTMP流原樣保存成文件
# ./ffmpeg -i rtmp://192.168.1.11:1935/live/teststream -acodec copy -vcodec copy -f flv -y test.flv
\2. 將RTMP流轉(zhuǎn)碼保存成文件
# ./ffmpeg -i rtmp://192.168.1.11:1935/live/teststream -acodec ... -vcodec ... -f mp4 -y test.mp4
\3. 將RTMP流轉(zhuǎn)碼后再以RTMP流的方式推送到RTMP流服務(wù)器
# ./ffmpeg -i rtmp://192.168.1.11:1935/live/teststream -acodec ... -vcodec ... -f flv rtmp://10.2.11.111/live/newstream
2. 第三方庫(kù)librtmp
如何讓FFMpeg鏈接該庫(kù)(后續(xù)更新)
FFMpeg可以支持rtmp://, rtmpt://, rtmpe://, rtmpte://,以及 rtmps://協(xié)議。
鏈接了librtmp的FFMpeg接受一個(gè)字符串的輸入方式,
如:"rtmp://server:port/app/playpath/stream_name live=1 playpath=xxx ..."
NOTE:引號(hào)是必須的;
\1. 保存RTMP直播流原樣保存成文件:
# ./ffmpeg -i "rtmp://http://pub1.guoshi.com/live/newcetv1 live=1" -vcodec copy -acodec copy -y cetv1.flv
\2. 將RTMP流轉(zhuǎn)碼后再以RTMP流的方式推送到RTMP流服務(wù)器
# ./ffmpeg -i "rtmp://192.168.1.11:1935/live/app/teststream live=1" -acodec ... -vcodec ... -f flv rtmp://10.2.11.111/live/newstream
\3. 用ffplay播放RTMP直播流:
ffplay "rtmp://http://pub1.guoshi.com/live/newcetv1 live=1"
\4. 在使用FFMPEG類庫(kù)進(jìn)行編程的時(shí)候,也是一樣的,
只需要將字符串傳遞給avformat_open_input()就行了,形如:
ffplay "rtmp://http://pub1.guoshi.com/live/newcetv1 live=1"
char url[]="rtmp://http://live.hkstv.hk.lxdns.com/live/hks live=1";
avformat_open_input(&pFormatCtx,url,NULL,&avdic)
3、推流器函數(shù)流程圖

4、代碼
int main(int argc, char * argv[])
{
AVFormatContext *pInFmtContext = NULL;
AVStream *in_stream;
AVCodecContext *pInCodecCtx;
AVCodec *pInCodec;
AVPacket *in_packet;
AVFormatContext * pOutFmtContext;
AVOutputFormat *outputFmt;
AVStream * out_stream;
//AVCodecContext * pOutCodecCtx;
//AVCodec *pOutCodec;
//AVPacket *out_packet;
//AVFrame *pOutFrame;
AVRational frame_rate;
double duration;
//int picture_size = 0;
//FILE *fp;
int ret;
const char * default_url = "rtmp://localhost:1935/live/tuiliu1";
char in_file[128] = {0};
char out_file[256] = {0};
int videoindex = -1;
int audioindex = -1;
int video_frame_count = 0;
int audio_frame_count = 0;
int video_frame_size = 0;
int audio_frame_size = 0;
int i;
int got_picture;
if(argc < 2){
printf("Usage: a.out <in_filename> <url>\n");
return -1;
}
memcpy(in_file, argv[1], strlen(argv[1]));
if( argc == 2){
memcpy(out_file, default_url, strlen(default_url));
}else{
memcpy(out_file, argv[2], strlen(argv[2]));
}
//av_register_all();
//avformat_network_init();
// Open an input stream and read the header,
if (avformat_open_input ( &pInFmtContext, in_file, NULL, NULL) < 0){
printf("avformat_open_input failed\n");
return -1;
}
//查詢輸入流中的所有流信息
if( avformat_find_stream_info(pInFmtContext, NULL) < 0){
printf("avformat_find_stream_info failed\n");
return -1;
}
av_dump_format(pInFmtContext, 0, in_file, 0);
ret = avformat_alloc_output_context2(&pOutFmtContext, NULL, "flv", out_file);
if(ret < 0){
printf("avformat_alloc_output_context2 failed\n");
return -1;
}
//outputFmt = pOutFmtContext->oformat;
for(i=0; i < pInFmtContext->nb_streams; i++){
in_stream = pInFmtContext->streams[i];
if( in_stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO){
audioindex = i;
}
if( in_stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){
videoindex = i;
frame_rate = av_guess_frame_rate(pInFmtContext, in_stream, NULL);
printf("video: frame_rate:%d/%d\n", frame_rate.num, frame_rate.den);
printf("video: frame_rate:%d/%d\n", frame_rate.den, frame_rate.num);
duration = av_q2d((AVRational){frame_rate.den, frame_rate.num});
}
pInCodec = avcodec_find_decoder(in_stream->codecpar->codec_id);
printf("%x, %d\n", pInCodec, in_stream->codecpar->codec_id);
//printf("-----%s,%s\n", pInCodec->name, in_stream->codec->codec->name);
out_stream = avformat_new_stream(pOutFmtContext, pInCodec);//in_stream->codec->codec);
if( out_stream == NULL){
printf("avformat_new_stream failed:%d\n",i);
}
ret = avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar);
if( ret < 0){
printf("avcodec_parameters_copy failed:%d\n", i);
}
out_stream->codecpar->codec_tag = 0;
if( pOutFmtContext->oformat->flags & AVFMT_GLOBALHEADER){//AVFMT_GLOBALHEADER代表封裝格式包含“全局頭”(即整個(gè)文件的文件頭),大部分封裝格式是這樣的。一些封裝格式?jīng)]有“全局頭”,比如MPEG2TS
out_stream->codec->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
}
}
av_dump_format(pOutFmtContext, 0, out_file, 1);
ret = avio_open(&pOutFmtContext->pb, out_file, AVIO_FLAG_WRITE);
if(ret < 0){
printf("avio_open failed:%d\n", ret);
return -1;
}
int64_t start_time = av_gettime();
ret = avformat_write_header(pOutFmtContext, NULL);
in_packet = av_packet_alloc();
while(1){
ret = av_read_frame(pInFmtContext, in_packet);
if(ret < 0){
printf("read frame end\n");
break;
}
in_stream = pInFmtContext->streams[in_packet->stream_index];
if(in_packet->stream_index == videoindex){
video_frame_size += in_packet->size;
printf("recv %5d video frame %5d-%5d\n", ++video_frame_count, in_packet->size, video_frame_size);
}
if(in_packet->stream_index == audioindex){
audio_frame_size += in_packet->size;
printf("recv %5d audio frame %5d-%5d\n", ++audio_frame_count, in_packet->size, audio_frame_size);
}
int codec_type = in_stream->codecpar->codec_type;
if( codec_type == AVMEDIA_TYPE_VIDEO){
#if 0
//延時(shí)方案1: 根據(jù) 1/幀率 來(lái)計(jì)算延時(shí)時(shí)間
av_usleep((int64_t)(duration * AV_TIME_BASE));
//av_usleep(10);
printf("%d\n", (int)(duration * AV_TIME_BASE));
#else
// 延時(shí)方案2: 根據(jù)pts時(shí)間與系統(tǒng)時(shí)間的關(guān)系來(lái)計(jì)算延時(shí)時(shí)間, 該方案更優(yōu)
AVRational dst_time_base = {1, AV_TIME_BASE};
int64_t pts_time = av_rescale_q(in_packet->pts, in_stream->time_base, dst_time_base);
int64_t now_time = av_gettime() - start_time;
if( pts_time > now_time)
av_usleep(pts_time - now_time);
//printf("%d\n", pts_time - now_time);
#endif
}
out_stream = pOutFmtContext->streams[in_packet->stream_index];
av_packet_rescale_ts(in_packet,in_stream->time_base, out_stream->time_base);
in_packet->pos = -1;
ret = av_interleaved_write_frame(pOutFmtContext, in_packet);
if( ret < 0){
printf("av_interleaved_write_frame failed\n");
break;
}
av_packet_unref(in_packet);
}
//
av_write_trailer(pOutFmtContext);
av_packet_free(&in_packet);
avformat_close_input(&pInFmtContext);
avio_close( pOutFmtContext->pb);
avformat_free_context(pOutFmtContext);
return 0;
}
有兩點(diǎn)需要注意的地方
\1. 推流的速度
不能一下子將數(shù)據(jù)全推到服務(wù)器,這樣流媒體服務(wù)器承受不住,實(shí)際中音頻流的數(shù)據(jù)量相比視頻要小很多,可以不必管它, 只按視頻播放速度(幀率)來(lái)推流即可滿足需要。因此每推送一個(gè)視頻幀,要延時(shí)一個(gè)視頻幀的時(shí)長(zhǎng)。視頻幀的時(shí)長(zhǎng)可根據(jù)幀率計(jì)算得出,即 1/幀率。
上述代碼中采用的是av_usleep()直接延時(shí)等待的方式, , 等待時(shí)間為‘1/幀率’, 由于存在程序處理的時(shí)間,系統(tǒng)延時(shí)等, 這種方式控制時(shí)間是不準(zhǔn)確的,但是上述代碼卻很直觀的表現(xiàn)了推流延時(shí)的實(shí)現(xiàn)。 實(shí)際中,我們需要考慮系統(tǒng)執(zhí)行的時(shí)間以及延時(shí), 可以結(jié)合系統(tǒng)當(dāng)前時(shí)間與視頻幀pts時(shí)間之間的差距,來(lái)決定延時(shí)的時(shí)間,這樣計(jì)算的延時(shí)時(shí)間相對(duì)更準(zhǔn)確, 計(jì)算公式如下:
delay_time = pts_time - now_time
= av_rescale_q(pkt.dts, ifmt_ctx->streams[videoindex]->time_base, (AVRational){1,AV_TIME_BASE}) - (av_gettime() - start_time)
\2. 推流的類型
上述代碼采用的推流協(xié)議是rtmp, rtmp推流必須推送flv封裝格式,而其他的協(xié)議也有相應(yīng)的格式要求(udp推流必須推送mpegts封裝格式)。 如果要將上述代碼修改為適配多種推流協(xié)議,則可根據(jù)推流協(xié)議自動(dòng)選擇相應(yīng)的封裝格式。
編譯
gcc tuiliu1.c -lavformat -lavcodec -lavutil
驗(yàn)證
要驗(yàn)證推流程序是否正確,我們需要搭建一個(gè)nginx+rtmp流媒體服務(wù)器(搭建nginx+rtmp服務(wù)器),而拉流端可以使用ffplay,參考以下過(guò)程:
1. 啟動(dòng)nginx服務(wù)器
nginx
2. 啟動(dòng)拉流
ffplay rtmp://localhost:1935/live/tuiliu1
3. 啟動(dòng)推流:
./a.out test.flv
?
接下來(lái),就能看視頻了
遺留問(wèn)題
\1. 無(wú)論使用ffplay命令播放視頻還是使用SDL編程播放視頻,都會(huì)導(dǎo)致compiz占用cpu過(guò)高

也嘗試過(guò)網(wǎng)上的多種解決方式,均無(wú)法解決,懷疑是compiz的bug
\2. 無(wú)論使用ffmpeg命令推流還是使用以上代碼推流,都會(huì)在推流結(jié)束調(diào)用av_write_trailer時(shí)打印
[flv @ 0x858c440] Failed to update header with correct duration.
[flv @ 0x858c440] Failed to update header with correct filesize.
使用以下推流命令依然會(huì)打印以上信息。
ffmpeg -re -i test.mp4 -c copy -f flv rtmp://localhost:1935/live/tuiliu1
原文https://zhuanlan.zhihu.com/p/436334751
★文末名片可以免費(fèi)領(lǐng)取音視頻開(kāi)發(fā)學(xué)習(xí)資料,內(nèi)容包括(FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)以及音視頻學(xué)習(xí)路線圖等等。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-597306.html
見(jiàn)下方!↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-597306.html
到了這里,關(guān)于音視頻開(kāi)發(fā)---ffmpeg rtmp推流的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!