如今很是流行直播,相信不少人都跟我同樣十分好奇這個技術是如何實現的,正好最近在作一個ffmpeg的項目,發現這個工具很容易就能夠作直播,下面來給你們分享下技術要點:html
首先你得編譯出ffmpeg運行所需的靜態庫,這個百度一下有不少內容,這裏我就很少說了,建議能夠用Github上的一個開源腳原本編譯,簡單粗暴有效率。git
地址:連接描述github
下載後直接用終端運行build-ffmpeg.sh腳本就好了,大概半個小時就所有編譯好了…反正我以爲速度還行吧(PS:當初編譯Android源碼那叫一個慢啊…),如果報錯就再來一遍,直到提示成功。數組
視頻直播怎麼直播呢?大概流程圖以下:緩存
1.直播人設備端:從攝像頭獲取視頻流,而後使用rtmp服務提交到服務器服務器
2.服務器端:接收直播人提交的rtmp視頻流,併爲觀看者提供rtmp源多線程
3.觀看者:用播放器播放rtmp源的視頻.ide
PS:RTMP是Real Time Messaging Protocol(實時消息傳輸協議)的首字母縮寫。該協議基於TCP,是一個協議族,包括RTMP基本協議及RTMPT/RTMPS/RTMPE等多種變種。函數
前期準備:工具
新建一個項目,將全部須要引入的ffmpeg的靜態庫及其餘相關庫引入到工程中,配置頭文件搜索路徑,這一步網上有不少教程就不重複敘述了。
我是用上面腳本編譯的最新版,爲了後期使用,須要將這些C文件添加到項目:
cmdutils_common_opts.h cmdutils.h及cmdutils.c config.h 在scratch目錄下取個對應平臺的 ffmpeg_filter.c ffmpeg_opt.c ffmpeg_videotoolbox.c ffmpeg.h及ffmpeg.c
除了config.h文件外,別的文件均在ffmpeg-3.0源碼目錄中
注意問題:
1.編譯會報錯,由於ffmpeg.c文件中包含main函數,請將該函數重命名爲ffmpeg_main並在ffmpeg.h中添加ffmpeg_main函數的聲明.
2.ffmpeg任務完成後會結束進程,而iOS設備都是單進程多線程任務,因此須要將cmdutils.c文件中的exit_program方法中的
exit(ret);
改成結束線程,須要引入#include <pthread.h>
pthread_exit(NULL);
直播端:用ffmpeg庫抓取直播人設備的攝像頭信息,生成裸數據流stream,注意!!!這裏是裸流,裸流意味着什麼呢?就是不包含PTS(Presentation Time Stamp。PTS主要用於度量解碼後的視頻幀何時被顯示出來)、DTS(Decode Time Stamp。DTS主要是標識讀入內存中的bit流在何時開始送入解碼器中進行解碼)等信息的數據流,播放器拿到這種流是沒法進行播放的.將這個客戶端只須要將這個數據流以RTMP協議傳到服務器便可。
如何獲取攝像頭信息:
使用libavdevice庫能夠打開獲取攝像頭的輸入流,在ffmpeg中獲取攝像頭的輸入流跟打開文件輸入流很相似,示例代碼:
//打開一個文件:
AVFormatContext *pFormatCtx = avformat_alloc_context(); avformat_open_input(&pFormatCtx, "test.h264",NULL,NULL);
//獲取攝像頭輸入:
AVFormatContext *pFormatCtx = avformat_alloc_context();
//多了查找輸入設備的這一步
AVInputFormat *ifmt=av_find_input_format("vfwcap");
//選取vfwcap類型的第一個輸入設別做爲輸入流
avformat_open_input(&pFormatCtx, 0, ifmt,NULL);
如何使用RTMP上傳視頻流:
使用RTMP上傳文件的指令是:
使用ffmpeg.c中的ffmpeg_main方法直接運行該指令便可,示例代碼:
NSString *command = @"ffmpeg -re -i temp.h264 -vcodec copy -f flv rtmp://xxx/xxx/livestream"; //根據空格將指令分割爲指令數組 NSArray *argv_array=[command_str componentsSeparatedByString:(@" ")]; //將OC對象轉換爲對應的C對象
int argc=(int)argv_array.count; char** argv=(char**)malloc(sizeof(char*)*argc); for(int i=0;i<argc;i++) { argv[i]=(char*)malloc(sizeof(char)*1024); strcpy(argv[i],[[argv_array objectAtIndex:i] UTF8String]); } //傳入指令數及指令數組 ffmpeg_main(argc,argv); //線程已殺死,下方的代碼不會執行 ffmpeg -re -i temp.h264 -vcodec copy -f flv rtmp://xxx/xxx/livestream
這行代碼就是
-re參數是按照幀率發送,不然ffmpeg會按最高速率發送,那麼視頻會忽快忽慢,
-i temp.h264是須要上傳的裸h264流
-vcoder copy 這段是複製一份不改變源
-f flv rtmp://xxx/xxx/livestream 是指定格式爲flv發送到這個url
這裏看到輸入是裸流或者是文件,可是咱們從攝像頭獲取到的是直接內存流,這怎麼解決呢?
固然是有辦法的啦
1.將這串參數中temp.h264參數變爲null
2.初始化自定義的AVIOContext,指定自定義的回調函數。示例代碼以下:
//AVIOContext中的緩存
unsigned char *aviobuffer=(unsigned char*)av_malloc(32768); AVIOContext *avio=avio_alloc_context(aviobuffer, 32768,0,NULL,read_buffer,NULL,NULL); pFormatCtx->pb=avio; if(avformat_open_input(&pFormatCtx,NULL,NULL,NULL)!=0){ printf("Couldn't open inputstream.(沒法打開輸入流)\n"); return -1; }
本身寫回調函數,從輸入源中取數據。示例代碼以下:
//Callback
int read_buffer(void opaque, uint8_t buf, int buf_size){
//休眠,不然會一次性所有發送完
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); }
//fp_open替換爲攝像頭輸入流 if(!feof(fp_open)){ inttrue_size=fread(buf,1,buf_size,fp_open); return true_size; }else{ return -1; } }
服務端:原諒我一個移動開發不懂服務器端,大概應該是獲取直播端上傳的視頻流再進行廣播.因此就略過吧.
播放端:播放端實際上就是一個播放器,能夠有不少解決方案,這裏提供一種最簡單的,由於不少直播軟件播放端和客戶端都是同一個軟件,因此這裏直接使用項目中已經有的ffmpeg進行播放簡單粗暴又省事.
在Github上有個基於ffmpeg的第三方播放器kxmovie,直接用這個就好.
地址: 連接描述
當你把kxmovie的播放器部分添加到以前作好的上傳部分,你會發現報錯了......
查找的結果是kxmovie所使用的avpicture_deinterlace方法不存在,我第一個想法就是想辦法屏蔽到這個方法,讓程序能正常使用,結果......固然不能正常播放視頻了,一百度才發現這個方法竟然是去交錯,雖然我視頻只是不夠豐富,可是也知道這個方法確定是不能少的.
沒事,只有改源碼了.從ffmpeg官方源碼庫中能夠找到這個方法.
地址: 連接描述
發現這個方法在以前的實現中是在avcodec.h中聲明是AVPicture的方法,而後在avpicture.c中再調用libavcodec/imgconvert.c這個文件中,也就是說這個方法自己就是屬於imgconvert.c的,avpicture.c只是間接調用,查找ffmpeg3.0的imgconvert.c文件,竟然沒這個方法,可是官方代碼庫中是有這個方法的,難道是已經移除了?移除不移除關我毛事,我只想能用,因此簡單點直接改avpicture.c
首先添加這幾個宏定義
#define deinterlace_line_inplace deinterlace_line_inplace_c #define deinterlace_line deinterlace_line_c #define ff_cropTbl ((uint8_t *)NULL) 而後從網頁上覆制這幾個方法到avpicture.c文件中 static void deinterlace_line_c static void deinterlace_line_inplace_c static void deinterlace_bottom_field static void deinterlace_bottom_field_inplace int avpicture_deinterlace 再在avcodec.h頭文件中, avpicture_alloc方法下面添加聲明: attribute_deprecated int avpicture_deinterlace(AVPicture *dst, const AVPicture *src, enum AVPixelFormat pix_fmt, int width, int height); 保存後再用終端執行build-ffmpeg.sh腳本編譯一次就好了…再次導入項目中kxmovie就不會報錯了,播放視頻的代碼以下: KxMovieViewController *vc = [KxMovieViewController movieViewControllerWithContentPath:path parameters:nil]; [self presentViewController:vc animated:YES completion:nil];
注:其中path能夠是以http/rtmp/trsp開始的url