源碼地址
https://github.com/979451341/Rtmpnginx
1.配置RTMP服務器git
這個我很少說貼兩個博客分別是在mac和windows環境上的,你們跟着弄
MAC搭建RTMP服務器
https://www.jianshu.com/p/6fcec3b9d644
這個是在windows上的,RTMP服務器搭建(crtmpserver和nginx)github
https://www.jianshu.com/p/c71cc39f72ec
2.關於推流輸出的ip地址我好好說說windows
我這裏是手機開啓熱點,電腦鏈接手機,這個RTMP服務器的推流地址有localhost,服務器在電腦上,對於電腦這個localhost是127.0.0.1,可是對於外界好比手機,你不能用localhost,而是用這個電腦的在這個熱點也就是局域網的ip地址,不是127.0.0.1這個只表明本設備節點的ip地址,這個你須要去手機設置——》更多——》移動網絡共享——》便攜式WLAN熱點——》管理設備列表,就能夠看到電腦的局域網ip地址了服務器
3.說說代碼網絡
註冊組件,第二個若是不加的話就不能獲取網絡信息,好比相似urlapp
av_register_all(); avformat_network_init();
獲取輸入視頻的信息,和建立輸出url地址的環境ide
av_dump_format(ictx, 0, inUrl, 0); ret = avformat_alloc_output_context2(&octx, NULL, "flv", outUrl); if (ret < 0) { avError(ret); throw ret; }
將輸入視頻流放入剛纔建立的輸出流裏函數
for (i = 0; i < ictx->nb_streams; i++) { //獲取輸入視頻流 AVStream *in_stream = ictx->streams[i]; //爲輸出上下文添加音視頻流(初始化一個音視頻流容器) AVStream *out_stream = avformat_new_stream(octx, in_stream->codec->codec); if (!out_stream) { printf("未能成功添加音視頻流\n"); ret = AVERROR_UNKNOWN; } if (octx->oformat->flags & AVFMT_GLOBALHEADER) { out_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER; } ret = avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar); if (ret < 0) { printf("copy 編×××上下文失敗\n"); } out_stream->codecpar->codec_tag = 0;
// out_stream->codec->codec_tag = 0;
}url
打開輸出url,並寫入頭部數據
//打開IO ret = avio_open(&octx->pb, outUrl, AVIO_FLAG_WRITE); if (ret < 0) { avError(ret); throw ret; } logd("avio_open success!"); //寫入頭部信息 ret = avformat_write_header(octx, 0); if (ret < 0) { avError(ret); throw ret; }
而後開始循環解碼並推流數據
首先獲取一幀的數據
ret = av_read_frame(ictx, &pkt);
而後給這一幀的數據配置參數,若是原有配置沒有時間就配置時間,我在這裏再提兩個概念
DTS(解碼時間戳)和PTS(顯示時間戳)分別是×××進行解碼和顯示幀時相對於SCR(系統參考)的時間戳。SCR能夠理解爲×××應該開始從磁盤讀取數據時的時間。
if (pkt.pts == AV_NOPTS_VALUE) { //AVRational time_base:時基。經過該值能夠把PTS,DTS轉化爲真正的時間。 AVRational time_base1 = ictx->streams[videoindex]->time_base; int64_t calc_duration = (double) AV_TIME_BASE / av_q2d(ictx->streams[videoindex]->r_frame_rate); //配置參數 pkt.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); }
調節播放時間,就是當初咱們解碼視頻以前記錄了一個當前時間,而後在循環推流的時候又獲取一次當前時間,二者的差值是咱們視頻應該播放的時間,若是視頻播放太快就進程休眠 pkt.dts減去實際播放的時間的差值
if (pkt.stream_index == videoindex) { AVRational time_base = ictx->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; AVRational avr = ictx->streams[videoindex]->time_base; cout << avr.num << " " << avr.den << " " << pkt.dts << " " << pkt.pts << " " << pts_time << endl; if (pts_time > now_time) { //睡眠一段時間(目的是讓當前視頻記錄的播放時間與實際時間同步) av_usleep((unsigned int) (pts_time - now_time)); } }
若是延時了,這一幀的配置所記錄的時間就應該改變
//計算延時後,從新指定時間戳 pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, (AVRounding) (AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX)); pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, (AVRounding) (AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX)); pkt.duration = (int) av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
回調這一幀的時間參數,這裏在MainActivity裏實例化了接口,顯示播放時間
int res = FFmpegHandle.setCallback(new PushCallback() { @Override public void videoCallback(final long pts, final long dts, final long duration, final long index) { runOnUiThread(new Runnable() { @Override public void run() { if(pts == -1){ tvPushInfo.setText("播放結束"); return ; } tvPushInfo.setText("播放時間:"+dts/1000+"秒"); } }); } });
而後段代碼調用了c語言的setCallback函數,獲取了接口的實例,和接口的videoCallback函數引用,這裏還調用了一次這個函數初始化時間顯示
//轉換爲全局變量 pushCallback = env->NewGlobalRef(pushCallback1); if (pushCallback == NULL) { return -3; } cls = env->GetObjectClass(pushCallback); if (cls == NULL) { return -1; } mid = env->GetMethodID(cls, "videoCallback", "(JJJJ)V"); if (mid == NULL) { return -2; } env->CallVoidMethod(pushCallback, mid, (jlong) 0, (jlong) 0, (jlong) 0, (jlong) 0);
這個時候咱們回到循環推流一幀幀數據的時候調用videoCallback函數
env->CallVoidMethod(pushCallback, mid, (jlong) pts, (jlong) dts, (jlong) duration, (jlong) index);
而後就是向輸出url輸出數據,並釋放這一幀的數據
ret = av_interleaved_write_frame(octx, &pkt); av_packet_unref(&pkt);
釋放資源
//關閉輸出上下文,這個很關鍵。 if (octx != NULL) avio_close(octx->pb); //釋放輸出封裝上下文 if (octx != NULL) avformat_free_context(octx); //關閉輸入上下文 if (ictx != NULL) avformat_close_input(&ictx); octx = NULL; ictx = NULL; env->ReleaseStringUTFChars(path_, path); env->ReleaseStringUTFChars(outUrl_, outUrl);
最後回調時間顯示,說播放結束
callback(env, -1, -1, -1, -1);
4.關於接收推流數據
我這裏使用的是VLC,這個mac和windows都有版本,FILE——》OPEN NETWORK,輸入以前的輸出url就能夠了。這裏要注意首先在app上開啓推流再使用VLC打開url才能夠
效果以下
參考文章
https://www.jianshu.com/p/dcac5da8f1da
這個博主對於推流真的熟練,你們若是對推流還想輸入瞭解能夠看看他的博客