海康視頻 rtnp轉 flv


1.海康視頻實時播放視頻

1.1 了解海康視頻sdk
1.1.1 獲取海康視頻sdk和SDK文檔

可以去官網下載也可以去百度網盤下載
鏈接:https://pan.baidu.com/s/1a5tmB1lwmb0ON_BRGB4XxQ
提取碼:1111

1.1.2 查看sdk播放視頻全流程
  • 初始化SDK
static HCNetSDK hCNetSDK = HCNetSDK.INSTANCE;
  • 注冊設備,獲取返回的IUser
lUserID = hCNetSDK.NET_DVR_Login_V30("182.104.192.XX",
(short) 8002, "admin", "密碼", m_strDeviceInfo);
  • 根據獲取到的lUserID 調用實時預覽的接口
//初始化客戶端信息數據
m_strClientInfo = new HCNetSDK.NET_DVR_CLIENTINFO();
//獲取視頻通道
m_strClientInfo.lChannel = new NativeLong(iChannelNum);
//根據通道編號以及注冊設備返回的UserId調用視頻實時預覽的API,返回值是窗口的句柄號
lPreviewHandle = hCNetSDK.NET_DVR_RealPlay_V30(lUserID,
        m_strClientInfo, null, null, true);
  • 通過回調參數獲取流數據
NET_DVR_RealPlay_V30的第三個參數可以自定義實時預覽的回調方法,回調方法的調用如下

代碼例子如下

class FRealDataCallBack implements HCNetSDK.FRealDataCallBack_V30 {
    //預覽回調
    public void invoke(NativeLong lRealHandle, int dwDataType, ByteByReference pBuffer, int dwBufSize, Pointer pUser) {
        HWND hwnd = new HWND(Native.getComponentPointer(panelRealplay));
        switch (dwDataType) {
            case HCNetSDK.NET_DVR_SYSHEAD: //系統頭

                if (!playControl.PlayM4_GetPort(m_lPort)) //獲取播放庫未使用的通道號
                {
                    break;
                }

                if (dwBufSize > 0) {
                    if (!playControl.PlayM4_SetStreamOpenMode(m_lPort.getValue(), PlayCtrl.STREAME_REALTIME))  //設置實時流播放模式
                    {
                        break;
                    }

                    if (!playControl.PlayM4_OpenStream(m_lPort.getValue(), pBuffer, dwBufSize, 1024 * 1024)) //打開流接口
                    {
                        break;
                    }

                    if (!playControl.PlayM4_Play(m_lPort.getValue(), hwnd)) //播放開始
                    {
                        break;
                    }
                }
            case HCNetSDK.NET_DVR_STREAMDATA:   //碼流數據
                if ((dwBufSize > 0) && (m_lPort.getValue().intValue() != -1)) {
                    if (!playControl.PlayM4_InputData(m_lPort.getValue(), pBuffer, dwBufSize))  //輸入流數據
                    {
                        break;
                    }
                }
        }
    }
}

pBuffer.getValue()就是流數據

1.2 海康視頻轉碼

1.2.1 思路1 將byte[]轉成視頻文件(.flv),再在前端進行播放
/** 
* 將字節流轉換成文件 
* @param filename 
* @param data 
* @throws Exception 
*/  
public static void saveFile(String filename,byte [] data)throws Exception{   
    if(data != null){   
      String filepath ="D:\\" + filename;   
      File file  = new File(filepath);   
      if(file.exists()){   
         file.delete();   
      }   
      FileOutputStream fos = new FileOutputStream(file);   
      fos.write(data,0,data.length);   
      fos.flush();   
      fos.close();   
    }   
} 

優點:不依賴其他中間件,純代碼實現,部署簡單,代碼編寫也簡單
缺點:
這樣雖然能播放視頻,但是直播流的視頻都是實時的,而不是一個完整的flv視頻文件,如果要實現直播流,要分割成很多個文件,然后一次播放,而且視頻延時很大,前端要以此播放轉好的視頻,前端代碼實現麻煩

1.2.2 思路2:byte[] —> PipedOutputStream —> PipedInputStream —>FFmpegFrameGrabber

byte{} 轉為 PipedOutputStream

//byte{} 轉為 PipedOutputStream
PipedInputStream inputStream = new PipedInputStream(byte);
PipedOutputStream outputStream = new PipedOutputStream();
inputStream.connect(outputStream);

// PipedInputStream —> FFmpegFrameGrabber
FFmpegFrameGrabber ff = new MyFFmpegFrameGrabber(InputStream inputStream)

參考博客:https://blog.csdn.net/weixin_40777510/article/details/105840823
缺點:播放3路視頻以上cpu占用率過高,過於占用服務器資源,而且長時間播放 JavaCV 經常出現 OOM ,暫無法解決

1.2.3 思路3 獲取海康視頻的rtnp地址,通過JavaCV推送到視頻流

步驟:拉取rtnp視頻流—>FFmpeg將視頻流轉成FLV流視頻—>推送到nginx服務器
——>返回播放地址給前端

具體流程圖如下
image

前端就可以用Flv.js進行播放了

grabber = new MyFFmpegFrameGrabber(pojo.getRtsp());
grabber.setOption("rtsp_transport", "tcp");

// 設置采集器構造超時時間
grabber.setOption("stimeout", "2000000");
if ("sub".equals(pojo.getStream())) {
   grabber.start(videoPushConfig.getSub_code());
} else if ("main".equals(pojo.getStream())) {
   grabber.start(videoPushConfig.getMain_code());
} else {
   grabber.start(videoPushConfig.getMain_code());
}

// 部分監控設備流信息里攜帶的幀率為9000,如出現此問題,會導致dts、pts時間戳計算失敗,播放器無法播放,故出現錯誤的幀率時,默認為25幀
if (grabber.getFrameRate() > 0 && grabber.getFrameRate() < 100) {
   framerate = grabber.getFrameRate();
} else {
   framerate = 25.0;
}
int width = grabber.getImageWidth();
int height = grabber.getImageHeight();
// 若視頻像素值為0,說明拉流異常,程序結束
if (width == 0 && height == 0) {
   log.error(pojo.getRtsp() + "  拉流異常!");
   grabber.stop();
   grabber.close();
   release();
   return;
}
recorder = new FFmpegFrameRecorder(pojo.getRtmp(), grabber.getImageWidth(), grabber.getImageHeight());
recorder.setInterleaved(true);
// 關鍵幀間隔,一般與幀率相同或者是視頻幀率的兩倍
recorder.setGopSize((int) framerate * 2);
// 視頻幀率(保證視頻質量的情況下最低25,低於25會出現閃屏)
recorder.setFrameRate(framerate);
// 設置比特率
recorder.setVideoBitrate(grabber.getVideoBitrate());
// 封裝flv格式
recorder.setFormat("flv");
// h264編/解碼器
recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
recorder.setPixelFormat(avutil.AV_PIX_FMT_YUV420P);
Map<String, String> videoOption = new HashMap<>();

// 該參數用於降低延遲
videoOption.put("tune", "zerolatency");
/**
 ** 權衡quality(視頻質量)和encode speed(編碼速度) values(值): *
 * ultrafast(終極快),superfast(超級快), veryfast(非常快), faster(很快), fast(快), *
 * medium(中等), slow(慢), slower(很慢), veryslow(非常慢) *
 * ultrafast(終極快)提供最少的壓縮(低編碼器CPU)和最大的視頻流大小;而veryslow(非常慢)提供最佳的壓縮(高編碼器CPU)的同時降低視頻流的大小
 */
videoOption.put("preset", "ultrafast");
// 畫面質量參數,0~51;18~28是一個合理范圍
videoOption.put("crf", "28");
recorder.setOptions(videoOption);
AVFormatContext fc = grabber.getFormatContext();
recorder.start(fc);
log.debug("開始推流 設備信息:[ip:" + pojo.getIp() + " channel:" + pojo.getChannel() + " stream:"
      + pojo.getStream() + " starttime:" + pojo.getStarttime() + " endtime:" + pojo.getEndtime()
      + " rtsp:" + pojo.getRtsp() + " url:" + pojo.getUrl() + "]");
// 清空探測時留下的緩存
grabber.flush();

AVPacket pkt = null;
long dts = 0;
long pts = 0;
int timebase = 0;
for (int no_frame_index = 0; no_frame_index < 10 && err_index < 5;) {
   long time1 = System.currentTimeMillis();
   if (exitcode == 1) {
      break;
   }
   pkt = grabber.grabPacket();
   if (pkt == null || pkt.size() == 0 || pkt.data() == null) {
      // 空包記錄次數跳過
      log.warn("JavaCV 出現空包 設備信息:[ip:" + pojo.getIp() + " channel:" + pojo.getChannel() + " stream:"
            + pojo.getStream() + " starttime:" + pojo.getStarttime() + " endtime:" + " rtsp:"
            + pojo.getRtsp() + pojo.getEndtime() + " url:" + pojo.getUrl() + "]");
      no_frame_index++;
      continue;
   }
   // 過濾音頻
   if (pkt.stream_index() == 1) {
      av_packet_unref(pkt);
      continue;
   }

   // 矯正sdk回調數據的dts,pts每次不從0開始累加所導致的播放器無法續播問題
   pkt.pts(pts);
   pkt.dts(dts);
   err_index += (recorder.recordPacket(pkt) ? 0 : 1);
   // pts,dts累加
   timebase = grabber.getFormatContext().streams(pkt.stream_index()).time_base().den();
   pts += timebase / (int) framerate;
   dts += timebase / (int) framerate;
   // 將緩存空間的引用計數-1,並將Packet中的其他字段設為初始值。如果引用計數為0,自動的釋放緩存空間。
   av_packet_unref(pkt);

   long endtime = System.currentTimeMillis();
   if ((long) (1000 /framerate) - (endtime - time1) > 0) {
      Thread.sleep((long) (1000 / framerate) - (endtime - time1));
   }

參考這篇博客
https://blog.csdn.net/weixin_40777510/article/details/103764198

利用Nginx搭建視頻流服務器

1.安裝nginx
此此過程可以看我之前的博客
https://www.cnblogs.com/xiaodou00/p/13470548.html
2.安裝rtmp視頻流插件以及flv視頻插件
鏈接:https://pan.baidu.com/s/1a5tmB1lwmb0ON_BRGB4XxQ
提取碼:1111
3,把現在好的插件上傳的linux服務器,執行動態添加模塊的指令./configure
configure是nginx下載下來以后自帶的一個腳本文件,如果發現nginx目錄下沒有configure這個腳本,重新下載一個nginx,添加該腳本
eg:

./configure --add-module=../nginx-rtmp-module-master

4,編譯nginx

make

5.備份sbin中的nginx主文件,復制新編譯的nginx文件到sbin中

cp /opt/nginx-1.9.5/sbin/nginx /opt/nginx-1.9.5/sbin/nginx.bak
# cp ./objs/nginx /opt/nginx-1.9.5/sbin/

6.重新運行nginx

nginx -s stop
nginx

7.查看nginx中的插件

nginx -v

看看有沒有之前上面添加的兩個模塊
8.修改nginx配置文件
添加rtmp應用

rtmp {

    server {
        listen 1935; # 監聽端口

        chunk_size 4000;
        application live33 {
            live on;
			gop_cache on;
        }		
    }
}

9.添加flv的http監聽路徑

server {
        listen       8090;
        server_name  test72.qtopay.cn  127.0.0.1;
        location /stat {     #第二處添加的location字段。
		rtmp_stat all;
		rtmp_stat_stylesheet stat.xsl;
	}
	location /stat.xsl { #第二處添加的location字段。
		root /usr/local/nginx/nginx-rtmp-module-master/;
	}
	
	location /rtmpLive {
		flv_live on;
		chunked_transfer_encoding  on; #open 'Transfer-Encoding: chunked' response
		add_header 'Access-Control-Allow-Credentials' 'true'; #add additional HTTP header
		add_header 'Access-Control-Allow-Origin' '*'; #add additional HTTP header
		add_header Access-Control-Allow-Headers X-Requested-With;
		add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
		add_header 'Cache-Control' 'no-cache';
	}
 }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM