Android NDK开发之旅34 NDK 手把手带你入门直播技术
####前言
先來(lái)了解一下視頻直播的基本架構(gòu):
我們需要有一個(gè)主播客戶端進(jìn)行音視頻采集,壓縮,然后通過(guò)RTMP協(xié)議進(jìn)行推流,推到流媒體服務(wù)器,然后其他客戶端統(tǒng)一從流媒體服務(wù)器引流,播放。關(guān)于這里的過(guò)程的一些細(xì)節(jié)我將會(huì)在后續(xù)文章中慢慢道來(lái)。
####Linux(Ubuntu系統(tǒng)或者虛擬機(jī))搭建流媒體服務(wù)器
先來(lái)了解一下俄羅斯人開發(fā)的Nginx服務(wù)器,Nginx ("engine x") 是一個(gè)高性能的HTTP和反向代理服務(wù)器,也是一個(gè)IMAP/POP3/SMTP服務(wù)器。Nginx是由Igor Sysoev為俄羅斯訪問量第二的Rambler.ru站點(diǎn)開發(fā)的,第一個(gè)公開版本0.1.0發(fā)布于2004年10月4日。其將源代碼以類BSD許可證的形式發(fā)布,因它的穩(wěn)定性、豐富的功能集、示例配置文件和低系統(tǒng)資源的消耗而聞名。
因?yàn)镹ginx服務(wù)器支持RTMP協(xié)議,因此這里我們作為流媒體服務(wù)器。
######Tips:實(shí)際開發(fā)可能是多臺(tái)服務(wù)器,需要高并發(fā)架構(gòu)支持。反向代理:外網(wǎng)的請(qǐng)求先轉(zhuǎn)發(fā)到內(nèi)網(wǎng),然后返回的時(shí)候再轉(zhuǎn)到外網(wǎng)。
Nginx是一種模塊化的服務(wù)器,開源,我們可以自由添加刪除我們自己的模塊,不同的模塊使用不同的端口號(hào),互不沖突,如下圖所示:
一、先下載安裝 nginx 和 nginx-rtmp 編譯依賴工具。
sudo apt-get install build-essential libpcre3 libpcre3-dev libssl-dev 復(fù)制代碼二、創(chuàng)建一個(gè)工作目錄,并切換到工作目錄。
因?yàn)楹罄m(xù)可能還需要進(jìn)行編譯等操作,最好先給目錄遞歸賦予權(quán)限:
mkdir Live chmod +x -R Live/ 復(fù)制代碼三、下載 nginx 和 nginx-rtmp源碼(wget是一個(gè)從網(wǎng)絡(luò)上自動(dòng)下載文件的自由工具)
wget http://nginx.org/download/nginx-1.8.1.tar.gz wget https://github.com/arut/nginx-rtmp-module/archive/master.zip 復(fù)制代碼四、解壓
如果你用的是Ubuntu系統(tǒng),直接通過(guò)界面操作解壓即可。
安裝unzip工具,解壓下載的安裝包 sudo apt-get install unzip
5.解壓 nginx 和 nginx-rtmp安裝包
tar -zxvf nginx-1.8.1.tar.gz unzip master.zip 復(fù)制代碼其中:
-zxvf分別是四個(gè)參數(shù) x : 從 tar 包中把文件提取出來(lái) z : 表示 tar 包是被 gzip 壓縮過(guò)的,所以解壓時(shí)需要用 gunzip 解壓 v : 顯示詳細(xì)信息 f : xxx.tar.gz : 指定被處理的文件是 xxx.tar.gz 復(fù)制代碼五、添加 nginx-rtmp 模板編譯到 nginx
切換到 nginx-目錄
cd nginx-1.8.1 ./configure --with-http_ssl_module --add-module=../nginx-rtmp-module-master 復(fù)制代碼六、編譯、安裝nginx
編譯nginx源碼 make 安裝需要超級(jí)權(quán)限 sudo make install 復(fù)制代碼######Tips:make、安裝編譯默認(rèn)就是調(diào)用configure腳本進(jìn)行編譯安裝,因此安裝路徑可以在configure找到。編譯過(guò)程就是先生成目標(biāo)文件.o,然后進(jìn)行鏈接得到可執(zhí)行程序。安裝的過(guò)程就是把一些文件復(fù)制到系統(tǒng)目錄里面去。
七、安裝nginx init 腳本
下載init腳本到/etc/init.d/nginx目錄中,其中/etc/init.d目錄放是Linux進(jìn)程啟動(dòng)的時(shí)候會(huì)執(zhí)行的一些腳本 sudo wget https://raw.github.com/JasonGiedymin/nginx-init-ubuntu/master/nginx -O /etc/init.d/nginx 給目錄添加執(zhí)行權(quán)限 sudo chmod +x /etc/init.d/nginx 刷新一下 sudo update-rc.d nginx defaults 復(fù)制代碼八、啟動(dòng)和停止nginx 服務(wù),生成配置文件
sudo service nginx start 復(fù)制代碼這時(shí)候在瀏覽器輸入http://127.0.0.1/,就可以看到頁(yè)面,證明服務(wù)器已經(jīng)成功安裝好了。然后我們停止服務(wù)器,進(jìn)行后續(xù)操作。
sudo service nginx stop 復(fù)制代碼九、安裝 FFmpeg
這里FFmpeg是用于做音視頻的編解碼的。同時(shí)我們測(cè)試直播功能的時(shí)候,也可以通過(guò)ffplay進(jìn)行測(cè)試,ffplay支持RTMP協(xié)議。
cd ffmpeg-2.6.9編譯FFmpeg ./configure --disable-yasm 安裝FFmpeg(這個(gè)過(guò)程比較久,耐心等待) sudo make install 復(fù)制代碼輸入下面的命令測(cè)試是否安裝好:
輸出安裝信息 ffmpeg -v 復(fù)制代碼######Tips:服務(wù)器內(nèi)存不足需要配置虛擬內(nèi)存。
十、配置 nginx-rtmp 服務(wù)器
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;}} } 復(fù)制代碼主要是配置RTMP協(xié)議,Nginx是一種模塊化的服務(wù)器,可以自由添加功能。這里主要是配置RTMP模塊的一些參數(shù),包括端口號(hào),視頻的編解碼參數(shù)、格式等等。
保存上面配置文件,然后重新啟動(dòng)nginx服務(wù):
sudo service nginx restart 復(fù)制代碼######Tips:Nginx的安裝目錄可以通過(guò)在configure查找“install”關(guān)鍵字找到。 ######這里也可以看到http的配置,默認(rèn)是80端口,如果有沖突的話,可以修改其他端口。不同功能使用不同端口,互不沖突。 ######如果你使用了防火墻,請(qǐng)?jiān)试S端口 tcp 1935。
####Windows平臺(tái)下流媒體服務(wù)器搭建
由于Windows平臺(tái)編譯Nginx源碼比較麻煩,因此我們用其他人編譯好的版本即可,下載地址如下:
https://github.com/illuspas/nginx-rtmp-win32 復(fù)制代碼同理,我們需要配置nginx-rtmp-win32\conf\nginx.conf文件,然后點(diǎn)擊根目錄下面的nginx.exe即可打開服務(wù)器。
####流媒體服務(wù)器測(cè)試
引流有兩個(gè)方法測(cè)試(將來(lái)也可以通過(guò)手機(jī)客戶端進(jìn)行測(cè)試):
一、服務(wù)器配置測(cè)試播放器:將Flash播放器的所有文件復(fù)制到目錄:/usr/local/nginx/html(Windows是www目錄)/,然后修改播放地址
播放器下載地址:
鏈接:http://pan.baidu.com/s/1bo9ePRp 密碼:627r 復(fù)制代碼######方法一需要把index.html的推流IP地址改為你自己的
二、用ffplay播放RTMP直播流:
在終端輸入一下命令即可:
ffplay rtmp://172.17.120.44/live/test 復(fù)制代碼######方法二需要把命令中的推流IP地址改為你自己的,并且最好把FFmpeg添加到環(huán)境變量
推流的測(cè)試:
目前來(lái)說(shuō)也是有兩種方法(將來(lái)會(huì)添加Android端引流測(cè)試):
一、使用之前創(chuàng)建的FFmpeg Visual項(xiàng)目,博客地址:
http://www.jianshu.com/p/5b7c18285667 復(fù)制代碼二、使用之前創(chuàng)建的FFmpeg Android Studio項(xiàng)目,相關(guān)博客地址:
http://www.jianshu.com/p/91e07b7dc8ca http://www.jianshu.com/p/da140cffadba 復(fù)制代碼其中,核心代碼如下:
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;//注冊(cè)組件av_register_all();//初始化網(wǎng)絡(luò)avformat_network_init();//打開輸入文件if ((ret = avformat_open_input(&inFmtCtx, input, 0, 0)) < 0) {LOGE("無(wú)法打開文件");goto end;}//獲取文件信息if ((ret = avformat_find_stream_info(inFmtCtx, 0)) < 0) {LOGE("無(wú)法獲取文件信息");goto end;}//輸出的封裝格式上下文,使用RTMP協(xié)議推送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++) {//根據(jù)輸入封裝格式中的AVStream流,來(lái)創(chuàng)建輸出封裝格式的AVStream流//解碼器,解碼器上下文都要一致AVStream *in_stream = inFmtCtx->streams[i];AVStream *out_stream = avformat_new_stream(outFmtCtx, in_stream->codec->codec);//復(fù)制解碼器上下文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);}//先寫一個(gè)頭ret = avformat_write_header(outFmtCtx, NULL);if (ret < 0) {LOGE("推流發(fā)生錯(cuò)誤\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這些參數(shù)的。在發(fā)送這種數(shù)據(jù)的時(shí)候,需要自己計(jì)算并寫入AVPacket的PTS,DTS,duration等參數(shù)//PTS:Presentation Time Stamp。PTS主要用于度量解碼后的視頻幀什么時(shí)候被顯示出來(lái)//DTS:Decode Time Stamp。DTS主要是標(biāo)識(shí)讀入內(nèi)存中的流在什么時(shí)候開始送入解碼器中進(jìn)行解碼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處理數(shù)據(jù)速度很快,瞬間就能把所有的數(shù)據(jù)發(fā)送出去,流媒體服務(wù)器是接受不了//這里采用av_usleep()函數(shù)休眠的方式來(lái)延遲發(fā)送,延時(shí)時(shí)間根據(jù)幀率與時(shí)間基準(zhǔn)計(jì)算得到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;//輸出進(jìn)度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);}//輸出結(jié)尾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); } 復(fù)制代碼注意:
如果覺得我的文字對(duì)你有所幫助的話,歡迎關(guān)注我的公眾號(hào):
我的群歡迎大家進(jìn)來(lái)探討各種技術(shù)與非技術(shù)的話題,有興趣的朋友們加我私人微信huannan88,我拉你進(jìn)群交(♂)流(♀)。
《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的Android NDK开发之旅34 NDK 手把手带你入门直播技术的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: box unboxing(装箱 拆箱)
- 下一篇: 每日源码分析-Lodash(uniq.j