現在非常流行直播,相信很多人都跟我一樣十分好奇這個技術是如何實現的,正好最近在做一個ffmpeg的專案,發現這個工具很容易就可以做直播,下面來給大家分享下技術要點:
首先你得編譯出ffmpeg執行所需的靜態庫,這個百度一下有很多內容,這裡我就不多說了,建議可以用Github上的一個開源指令碼來編譯,簡單粗暴有效率。
地址:連結描述
下載後直接用終端執行build-ffmpeg.sh指令碼就行了,大概半個小時就全部編譯好了…反正我覺得速度還行吧(PS:當初編譯Android原始碼那叫一個慢啊…),若是報錯就再來一遍,直到提示成功。
視訊直播怎麼直播呢?大概流程圖如下:
1.直播人裝置端:從攝像頭獲取視訊流,然後使用rtmp服務提交到伺服器
2.伺服器端:接收直播人提交的rtmp視訊流,併為觀看者提供rtmp源
3.觀看者:用播放器播放rtmp源的視訊.
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.(無法開啟輸入流)
");
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