Android NDK开发之旅34 NDK 手把手带你入门直播技术
####前言
先來了解一下視頻直播的基本架構:
我們需要有一個主播客戶端進行音視頻采集,壓縮,然后通過RTMP協議進行推流,推到流媒體服務器,然后其他客戶端統一從流媒體服務器引流,播放。關于這里的過程的一些細節我將會在后續文章中慢慢道來。
####Linux(Ubuntu系統或者虛擬機)搭建流媒體服務器
先來了解一下俄羅斯人開發的Nginx服務器,Nginx ("engine x") 是一個高性能的HTTP和反向代理服務器,也是一個IMAP/POP3/SMTP服務器。Nginx是由Igor Sysoev為俄羅斯訪問量第二的Rambler.ru站點開發的,第一個公開版本0.1.0發布于2004年10月4日。其將源代碼以類BSD許可證的形式發布,因它的穩定性、豐富的功能集、示例配置文件和低系統資源的消耗而聞名。
因為Nginx服務器支持RTMP協議,因此這里我們作為流媒體服務器。
######Tips:實際開發可能是多臺服務器,需要高并發架構支持。反向代理:外網的請求先轉發到內網,然后返回的時候再轉到外網。
Nginx是一種模塊化的服務器,開源,我們可以自由添加刪除我們自己的模塊,不同的模塊使用不同的端口號,互不沖突,如下圖所示:
一、先下載安裝 nginx 和 nginx-rtmp 編譯依賴工具。
sudo apt-get install build-essential libpcre3 libpcre3-dev libssl-dev 復制代碼二、創建一個工作目錄,并切換到工作目錄。
因為后續可能還需要進行編譯等操作,最好先給目錄遞歸賦予權限:
mkdir Live chmod +x -R Live/ 復制代碼三、下載 nginx 和 nginx-rtmp源碼(wget是一個從網絡上自動下載文件的自由工具)
wget http://nginx.org/download/nginx-1.8.1.tar.gz wget https://github.com/arut/nginx-rtmp-module/archive/master.zip 復制代碼四、解壓
如果你用的是Ubuntu系統,直接通過界面操作解壓即可。
安裝unzip工具,解壓下載的安裝包 sudo apt-get install unzip
5.解壓 nginx 和 nginx-rtmp安裝包
tar -zxvf nginx-1.8.1.tar.gz unzip master.zip 復制代碼其中:
-zxvf分別是四個參數 x : 從 tar 包中把文件提取出來 z : 表示 tar 包是被 gzip 壓縮過的,所以解壓時需要用 gunzip 解壓 v : 顯示詳細信息 f : xxx.tar.gz : 指定被處理的文件是 xxx.tar.gz 復制代碼五、添加 nginx-rtmp 模板編譯到 nginx
切換到 nginx-目錄
cd nginx-1.8.1 ./configure --with-http_ssl_module --add-module=../nginx-rtmp-module-master 復制代碼六、編譯、安裝nginx
編譯nginx源碼 make 安裝需要超級權限 sudo make install 復制代碼######Tips:make、安裝編譯默認就是調用configure腳本進行編譯安裝,因此安裝路徑可以在configure找到。編譯過程就是先生成目標文件.o,然后進行鏈接得到可執行程序。安裝的過程就是把一些文件復制到系統目錄里面去。
七、安裝nginx init 腳本
下載init腳本到/etc/init.d/nginx目錄中,其中/etc/init.d目錄放是Linux進程啟動的時候會執行的一些腳本 sudo wget https://raw.github.com/JasonGiedymin/nginx-init-ubuntu/master/nginx -O /etc/init.d/nginx 給目錄添加執行權限 sudo chmod +x /etc/init.d/nginx 刷新一下 sudo update-rc.d nginx defaults 復制代碼八、啟動和停止nginx 服務,生成配置文件
sudo service nginx start 復制代碼這時候在瀏覽器輸入http://127.0.0.1/,就可以看到頁面,證明服務器已經成功安裝好了。然后我們停止服務器,進行后續操作。
sudo service nginx stop 復制代碼九、安裝 FFmpeg
這里FFmpeg是用于做音視頻的編解碼的。同時我們測試直播功能的時候,也可以通過ffplay進行測試,ffplay支持RTMP協議。
cd ffmpeg-2.6.9編譯FFmpeg ./configure --disable-yasm 安裝FFmpeg(這個過程比較久,耐心等待) sudo make install 復制代碼輸入下面的命令測試是否安裝好:
輸出安裝信息 ffmpeg -v 復制代碼######Tips:服務器內存不足需要配置虛擬內存。
十、配置 nginx-rtmp 服務器
Nginx安裝在/usr/local/nginx中,其中:
打開 /usr/local/nginx/conf/nginx.conf
在末尾添加如下配置:
rtmp {server {listen 1935;chunk_size 4096;application live {live on;record off;exec ffmpeg -i rtmp://localhost/live/$name -threads 1 -c:v libx264 -profile:v baseline -b:v 350K -s 640x360 -f flv -c:a aac -ac 1 -strict -2 -b:a 56k rtmp://localhost/live360p/$name;}application live360p {live on;record off;}} } 復制代碼主要是配置RTMP協議,Nginx是一種模塊化的服務器,可以自由添加功能。這里主要是配置RTMP模塊的一些參數,包括端口號,視頻的編解碼參數、格式等等。
保存上面配置文件,然后重新啟動nginx服務:
sudo service nginx restart 復制代碼######Tips:Nginx的安裝目錄可以通過在configure查找“install”關鍵字找到。 ######這里也可以看到http的配置,默認是80端口,如果有沖突的話,可以修改其他端口。不同功能使用不同端口,互不沖突。 ######如果你使用了防火墻,請允許端口 tcp 1935。
####Windows平臺下流媒體服務器搭建
由于Windows平臺編譯Nginx源碼比較麻煩,因此我們用其他人編譯好的版本即可,下載地址如下:
https://github.com/illuspas/nginx-rtmp-win32 復制代碼同理,我們需要配置nginx-rtmp-win32\conf\nginx.conf文件,然后點擊根目錄下面的nginx.exe即可打開服務器。
####流媒體服務器測試
引流有兩個方法測試(將來也可以通過手機客戶端進行測試):
一、服務器配置測試播放器:將Flash播放器的所有文件復制到目錄:/usr/local/nginx/html(Windows是www目錄)/,然后修改播放地址
播放器下載地址:
鏈接:http://pan.baidu.com/s/1bo9ePRp 密碼:627r 復制代碼######方法一需要把index.html的推流IP地址改為你自己的
二、用ffplay播放RTMP直播流:
在終端輸入一下命令即可:
ffplay rtmp://172.17.120.44/live/test 復制代碼######方法二需要把命令中的推流IP地址改為你自己的,并且最好把FFmpeg添加到環境變量
推流的測試:
目前來說也是有兩種方法(將來會添加Android端引流測試):
一、使用之前創建的FFmpeg Visual項目,博客地址:
http://www.jianshu.com/p/5b7c18285667 復制代碼二、使用之前創建的FFmpeg Android Studio項目,相關博客地址:
http://www.jianshu.com/p/91e07b7dc8ca http://www.jianshu.com/p/da140cffadba 復制代碼其中,核心代碼如下:
JNIEXPORT void JNICALL Java_com_nan_ffmpeg_utils_FFmpegUtils_push(JNIEnv *env, jclass type, jstring input_,jstring output_) {const char *input = (*env)->GetStringUTFChars(env, input_, NULL);const char *output = (*env)->GetStringUTFChars(env, output_, NULL);LOGI("%s\n", input);LOGI("%s\n", output);//變量初始化AVFormatContext *inFmtCtx = NULL, *outFmtCtx = NULL;int ret;//注冊組件av_register_all();//初始化網絡avformat_network_init();//打開輸入文件if ((ret = avformat_open_input(&inFmtCtx, input, 0, 0)) < 0) {LOGE("無法打開文件");goto end;}//獲取文件信息if ((ret = avformat_find_stream_info(inFmtCtx, 0)) < 0) {LOGE("無法獲取文件信息");goto end;}//輸出的封裝格式上下文,使用RTMP協議推送flv封裝格式的流avformat_alloc_output_context2(&outFmtCtx, NULL, "flv", output); //RTMP//avformat_alloc_output_context2(&ofmt_ctx, NULL, "mpegts", output_str);//UDPint i = 0;for (; i < inFmtCtx->nb_streams; i++) {//根據輸入封裝格式中的AVStream流,來創建輸出封裝格式的AVStream流//解碼器,解碼器上下文都要一致AVStream *in_stream = inFmtCtx->streams[i];AVStream *out_stream = avformat_new_stream(outFmtCtx, in_stream->codec->codec);//復制解碼器上下文ret = avcodec_copy_context(out_stream->codec, in_stream->codec);//全局頭out_stream->codec->codec_tag = 0;if (outFmtCtx->oformat->flags == AVFMT_GLOBALHEADER) {out_stream->codec->flags = CODEC_FLAG_GLOBAL_HEADER;}}//打開輸出的AVIOContext IO流上下文AVOutputFormat *ofmt = outFmtCtx->oformat;if (!(ofmt->flags & AVFMT_NOFILE)) {ret = avio_open(&outFmtCtx->pb, output, AVIO_FLAG_WRITE);}//先寫一個頭ret = avformat_write_header(outFmtCtx, NULL);if (ret < 0) {LOGE("推流發生錯誤\n");goto end;}//獲取視頻流的索引位置int videoindex = -1;for (i = 0; i < inFmtCtx->nb_streams; i++) {if (inFmtCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {videoindex = i;break;}}int frame_index = 0;int64_t start_time = av_gettime();AVPacket pkt;while (1) {AVStream *in_stream, *out_stream;//讀取AVPacketret = av_read_frame(inFmtCtx, &pkt);if (ret < 0)break;//沒有封裝格式的裸流(例如H.264裸流)是不包含PTS、DTS這些參數的。在發送這種數據的時候,需要自己計算并寫入AVPacket的PTS,DTS,duration等參數//PTS:Presentation Time Stamp。PTS主要用于度量解碼后的視頻幀什么時候被顯示出來//DTS:Decode Time Stamp。DTS主要是標識讀入內存中的流在什么時候開始送入解碼器中進行解碼if (pkt.pts == AV_NOPTS_VALUE) {//Write PTSAVRational time_base1 = inFmtCtx->streams[videoindex]->time_base;//Duration between 2 frames (us)int64_t calc_duration =(double) AV_TIME_BASE / av_q2d(inFmtCtx->streams[videoindex]->r_frame_rate);//Parameterspkt.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);}if (pkt.stream_index == videoindex) {//FFmpeg處理數據速度很快,瞬間就能把所有的數據發送出去,流媒體服務器是接受不了//這里采用av_usleep()函數休眠的方式來延遲發送,延時時間根據幀率與時間基準計算得到AVRational time_base = inFmtCtx->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 = inFmtCtx->streams[pkt.stream_index];out_stream = outFmtCtx->streams[pkt.stream_index];/* copy packet *///Convert PTS/DTSpkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base,AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX);pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base,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;//輸出進度if (pkt.stream_index == videoindex) {LOGI("第%d幀", frame_index);frame_index++;}//推送ret = av_interleaved_write_frame(outFmtCtx, &pkt);if (ret < 0) {LOGE("Error muxing packet");break;}av_free_packet(&pkt);}//輸出結尾av_write_trailer(outFmtCtx);end://釋放資源avformat_free_context(inFmtCtx);avio_close(outFmtCtx->pb);avformat_free_context(outFmtCtx);(*env)->ReleaseStringUTFChars(env, input_, input);(*env)->ReleaseStringUTFChars(env, output_, output); } 復制代碼注意:
如果覺得我的文字對你有所幫助的話,歡迎關注我的公眾號:
我的群歡迎大家進來探討各種技術與非技術的話題,有興趣的朋友們加我私人微信huannan88,我拉你進群交(♂)流(♀)。
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的Android NDK开发之旅34 NDK 手把手带你入门直播技术的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: box unboxing(装箱 拆箱)
- 下一篇: 每日源码分析-Lodash(uniq.j