java項目通過ffmpeg推流至nginx-rtmp流媒體服務

1.安裝ffmpeg

yum安裝即可,安裝後檢測版本是否安裝成功,不詳述

yum install -y ffmpeg
ffmpeg -version

 

2.安裝nginx-rtmp

nginx本身不詳述,這裏已安裝nginx的情況下增加編譯rtmp模塊,git上可下載rtmp模塊nginx-rtmp-module存放至nginx安裝目錄下
 

./configure --prefix=/usr/local/nginx  --add-module=/usr/local/nginx/nginx-rtmp-module

./configure --prefix=/你的安裝目錄  --add-module=/第三方模塊目錄

3.rtmp播放和推流監控

nginx-rtmp-module文件夾下有stat.xsl文件,如下配置 /stat可訪問監控如圖,有playing播放和publishing推流

	location /stat{
            rtmp_stat all;
            rtmp_stat_stylesheet stat.xsl;
        }
		
	location /stat.xsl{
            root nginx-rtmp-module-master/;
        }

4.推流服務配置

rtmp {
    server {
	listen 1935;
	chunk_size 4096; #默認區塊大小
        #chunk_size 1024; 
        application devlive1 {  #rtmp推流請求路徑  
            live on;    
  			on_play http://127.0.0.1:8082/ffmpeg/on_play?device_token=ocxflddu;
			on_play_done http://127.0.0.1:8082/ffmpeg/on_play_done?device_token=ocxflddu;
        }
    }
    server {
        listen 1936;
        chunk_size 4096; #默認區塊大小
        #chunk_size 1024;
        application devlive2 {  #rtmp推流請求路徑
			live on;
			on_play http://127.0.0.1:8082/ffmpeg/on_play?device_token=ocxflddu;
			on_play_done http://127.0.0.1:8082//ffmpeg/on_play_done?device_token=ocxflddu;
        }
    }

}

配置了兩個推流端口兩個應用,一個端口一個應用

on_play:有用戶播放,on_play_done:用戶關閉播放

會調用配置的接口,request.getParameter("name");可獲取到當前流名稱,還有on_publish等其他配置項

5.ffmpeg推流

個人採用的是多線程推流,一個線程管理一個外部進程(ffmpeg)

關閉推流可通過線程名匹配到並執行interrupt終止操作

推流異常時銷燬進程終止當前線程,延時10秒新啓線程來推當前流(代碼有點多就不粘出來了)

//java調第三方引用ffmpeg,RTSP轉RTMP
String[] convert = new String[]{"ffmpeg", "-i", rtspUrl, "-r", "25", "-c:v", "copy", "-f", "flv", "-y", "-an", rtmpUrl};
processBuilder.command(convert);
processBuilder.redirectErrorStream(true);
process = processBuilder.start();

6.進程阻塞問題

當網絡情況不穩定或直播流不存在或者沒有數據的情況下,ffmpeg進程容易發生阻塞,這裏就需要特殊處理,終止阻塞的進程。

進程輸出過程中持續往redis寫入更新心跳時間,另起定時任務檢測該進程心跳時間,如心跳停止2分鐘判定爲阻塞,kill進程

7.在線人數監測,無人播放時關閉推流

on_lpay和on_play_done理論可實現在線人數監測,onplay人數+1,onlpaydone人數-1,在服務不掉線始終和nginx保持網絡通暢的情況下可實現,否則。。。

上述提到過/stat可監測播放playing和推流publishing,通過解析XML格式,可獲取每個視頻playing數量,爲了方便解析,nginx-rtmp服務配置,不建議application同名

附上解析代碼

//檢測RTMP流播放人數,超過expire時長播放人數爲0,關閉推流
public void chkFfmpegPlayerZero(long expire) {
        String sendGet = HttpKit.sendGet(rtmpstat, null);
        if (ToolUtil.isNotEmpty(sendGet)){
            logger.info("RTMPSTAT:{}",sendGet);
            JSONObject xmlJSONObj = toJSONObject(sendGet);
            JSONObject rtmp = xmlJSONObj.getJSONObject("rtmp");
            JSONArray server = rtmp.getJSONArray("server");
            for (int i = 0; i < server.length(); i++) {
                JSONObject application = server.getJSONObject(i).getJSONObject("application");
                String applicationName = application.getString("name");
                JSONObject live = application.getJSONObject("live");
                long nclients = live.getLong("nclients");
                if (nclients>0){
                    //有數據
                    //此處不確定stream節點是object還是array
                    //JSONObject client = live.getJSONObject("stream").getJSONObject("client");
                    String simpleName = live.get("stream").getClass().getSimpleName();
                    if ("JSONObject".equals(simpleName)){
                        JSONObject stream = live.getJSONObject("stream");
                        chkFfmpegPlayerZeroByStream(stream,expire);
                    }else {
                        JSONArray stream = live.getJSONArray("stream");
                        for (int i1 = 0; i1 < stream.length(); i1++) {
                            JSONObject jsonObject = stream.getJSONObject(i1);
                            chkFfmpegPlayerZeroByStream(jsonObject,expire);
                        }
                    }
                }else {
                    logger.info("{}下無推流信息",applicationName);
                }
            }
        }
    }


private void chkFfmpegPlayerZeroByStream(JSONObject stream,long expire){
        String cameraId = stream.get("name").toString();
        String simpleName = stream.get("client").getClass().getSimpleName();
        int player = 0;
        if ("JSONObject".equals(simpleName)){
            JSONObject client = stream.getJSONObject("client");
            if (!client.has("publishing")){
                player++;
            }
        }else {
            JSONArray clients = stream.getJSONArray("client");
            for (int i = 0; i < clients.length(); i++) {
                if (!clients.getJSONObject(i).has("publishing")){
                    player++;
                }
            }
        }
        logger.info("推流播放人數 devId:{},人數:{}",cameraId,player);
        //持續2分鐘播放人數爲0關閉推流
        String rediskeyPlaytime = RedisKey.CAMERA_INFO + cameraId + RedisKey.CAMERA_PLAYERTIME;
        if (player==0){
            String playtimeStr = redisTemplate.opsForValue().get(rediskeyPlaytime);
            if (ToolUtil.isEmpty(playtimeStr)){
                //首次檢測到無人播放,賦值
                logger.info("首次檢測到無人播放,記錄時間戳,devId:{}",cameraId);
                redisTemplate.opsForValue().set(rediskeyPlaytime,System.currentTimeMillis()+"");
                return;
            }else {
                //非首次監測到,判斷多久無人播放
                Long playtime = Long.valueOf(playtimeStr);
                if ((System.currentTimeMillis() - playtime)>expire){
                    //關閉推流
                    logger.info("超過2分鐘無人播放,關閉推流,devId:{}",cameraId);
                    redisTemplate.delete(rediskeyPlaytime);
                    this.closeFfmpeg(cameraId);
                }else {
                    logger.info("無人播放未超時,暫不處理,devId:{}",cameraId);
                }
            }

        }else {
            //有用戶播放清空數據
            redisTemplate.delete(rediskeyPlaytime);
        }
    }

 

發佈了8 篇原創文章 · 獲贊 3 · 訪問量 2236
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章