最近在玩视频相关的,也算是一步一步的深入吧。

第一版:

用海康SDK进行历史数据下载:

https://blog.csdn.net/qq_16504067/article/details/114538622?spm=1001.2014.3001.5502

https://blog.csdn.net/qq_16504067/article/details/114577693?spm=1001.2014.3001.5502

用ffmgeg转rtsp格式为rtmp格式存储到http-flv的直播流媒体服务器,然后前端直接拉取rtmp流进行播放

https://blog.csdn.net/qq_16504067/article/details/115503882?spm=1001.2014.3001.5502

https://github.com/eguid/FFCH4J

第二版:

问了海康的技术支持说他们是PS流,所以只需要解析PS流成前端可以播放的就行,现在在网上找到了一个wfs.js(https://github.com/MarkRepo/wfs.js)可以播放裸流H264,所以我就想在PS流中解析出H264的裸流,然后给wfs.js进行播放

https://blog.csdn.net/qq_16504067/article/details/117781017?spm=1001.2014.3001.5502

https://blog.yasking.org/a/hikvision-rtp-ps-stream-parser.html

https://blog.csdn.net/weixin_44517656/article/details/108412988

看以上的参考文章和c++的这个项目https://github.com/kevinfromcn/PsToH264后自己进行了一次的demo的预览回调解析,我先通过

public static String byteToHex(final byte[] bytes) {
		String strHex = "";
		StringBuilder sb = new StringBuilder("");
		for (int n = 0; n < bytes.length; n++) {
			strHex = Integer.toHexString(bytes[n] & 0xFF);
			sb.append((strHex.length() == 1) ? "0" + strHex : strHex); // 每个字节由两个字符表示,位数不够,高位补0
			// sb.append(" ");
		}
		return sb.toString().trim();
	}

将海康的PS流转成16进制进行答应查看,果然是PS流的格式

然后再按照上面的参考进行解析(因为海康是一段段给的,所以我的解析也进行了简单处理)


	/**
	 * 开启/关闭预览
	 * 
	 * @return
	 */
	public JSONObject startOrStopPreview() {
		final JSONObject result = new JSONObject();
		if (lUserID.intValue() == -1) {
			logger.error("请先登陆....");

			result.put("status", false);
			result.put("msg", "请先登陆....");
			return result;
		}

		// 如果预览窗口没打开,不在预览
		if (bRealPlay == false) {
			// 要开启预览的通道号
			int iChannelNum = 1;// 通道号

			m_strClientInfo = new HCNetSDK.NET_DVR_CLIENTINFO();
			m_strClientInfo.lChannel = new NativeLong(iChannelNum);
			// 回调预览
			m_strClientInfo.hPlayWnd = null;
			lPreviewHandle = hCNetSDK.NET_DVR_RealPlay_V30(lUserID, m_strClientInfo, fRealDataCallBack, null, true);
			long previewSucValue = lPreviewHandle.longValue();
			// 预览失败时:
			if (previewSucValue == -1) {
				logger.error("开启预览失败......");
				result.put("status", false);
				result.put("msg", "开启预览失败......");
				return result;
			}
			// 预览成功的操作
			bRealPlay = true;

			result.put("status", true);
			result.put("msg", "开始预览成功....");
		} else {// 如果在预览,停止预览,关闭窗口
			hCNetSDK.NET_DVR_StopRealPlay(lPreviewHandle);
			bRealPlay = false;

			result.put("status", true);
			result.put("msg", "停止预览成功");
		}

		return result;
	}

	/******************************************************************************
	 * 内部类: FRealDataCallBack 实现预览回调数据
	 ******************************************************************************/
	class FRealDataCallBack implements HCNetSDK.FRealDataCallBack_V30 {
		// 预览回调
		@Override
		public void invoke(final NativeLong lRealHandle, final int dwDataType, final ByteByReference pBuffer,
				final int dwBufSize, final Pointer pUser) {
			switch (dwDataType) {
			case HCNetSDK.NET_DVR_SYSHEAD: // 系统头
			case HCNetSDK.NET_DVR_STREAMDATA: // 码流数据
				if (dwBufSize > 0) {
					byte[] outputData = pBuffer.getPointer().getByteArray(0, dwBufSize);
					try {
						writeESH264(outputData);
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			}
		}
	}

	byte[] allEsBytes = null;

	/**
	 * 提取H264的裸流写入文件
	 * 
	 * @param outputData
	 * @throws IOException
	 */
	public void writeESH264(final byte[] outputData) throws IOException {
		if (outputData.length <= 0) {
			return;
		}
		if ((outputData[0] & 0xff) == 0x00//
				&& (outputData[1] & 0xff) == 0x00//
				&& (outputData[2] & 0xff) == 0x01//
				&& (outputData[3] & 0xff) == 0xBA) {// RTP包开头
			try {
				// 一个完整的帧解析完成后将解析的数据放入BlockingQueue,websocket获取后发生给前端
				if (allEsBytes != null && allEsBytes.length > 0) {
					MyBlockingQueue.bq.put(allEsBytes);
				}
				allEsBytes = null;
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}

		// 是00 00 01 eo开头的就是视频的pes包
		if ((outputData[0] & 0xff) == 0x00//
				&& (outputData[1] & 0xff) == 0x00//
				&& (outputData[2] & 0xff) == 0x01//
				&& (outputData[3] & 0xff) == 0xE0) {//
			// 去掉包头后的起始位置
			int from = 9 + outputData[8] & 0xff;
			int len = outputData.length - 9 - (outputData[8] & 0xff);
			// 获取es裸流
			byte[] esBytes = new byte[len];
			System.arraycopy(outputData, from, esBytes, 0, len);

			if (allEsBytes == null) {
				allEsBytes = esBytes;
			} else {
				byte[] newEsBytes = new byte[allEsBytes.length + esBytes.length];
				System.arraycopy(allEsBytes, 0, newEsBytes, 0, allEsBytes.length);
				System.arraycopy(esBytes, 0, newEsBytes, allEsBytes.length, esBytes.length);
				allEsBytes = newEsBytes;
			}
		}
	}

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicInteger;

import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;

import org.springframework.stereotype.Component;

import com.example.demo.domain.MyBlockingQueue;

import lombok.extern.slf4j.Slf4j;

/**
 * 前后端交互的类实现消息的接收推送(自己发送给自己)
 * 
 * @ServerEndpoint(value = "/wstest") 前端通过此URI和后端交互,建立连接
 */
@Slf4j
@ServerEndpoint(value = "/wstest")
@Component
public class OneWebSocket {

	/** 记录当前在线连接数 */
	private static AtomicInteger onlineCount = new AtomicInteger(0);

	/**
	 * 连接建立成功调用的方法
	 */
	@OnOpen
	public void onOpen(final Session session) {
		onlineCount.incrementAndGet(); // 在线数加1
		log.info("有新连接加入:{},当前在线人数为:{}", session.getId(), onlineCount.get());

		while (true) {
			try {
				byte[] esBytes = (byte[]) MyBlockingQueue.bq.take();

				ByteBuffer data = ByteBuffer.wrap(esBytes);
				session.getBasicRemote().sendBinary(data);
			} catch (InterruptedException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}

		}
	}

	public static String byteToHex(final byte[] bytes) {
		String strHex = "";
		StringBuilder sb = new StringBuilder("");
		for (int n = 0; n < bytes.length; n++) {
			strHex = Integer.toHexString(bytes[n] & 0xFF);
			sb.append((strHex.length() == 1) ? "0" + strHex : strHex); // 每个字节由两个字符表示,位数不够,高位补0
			// sb.append(" ");
		}
		return sb.toString().trim();
	}

	/**
	 * 连接关闭调用的方法
	 */
	@OnClose
	public void onClose(final Session session) {
		onlineCount.decrementAndGet(); // 在线数减1
		log.info("有一连接关闭:{},当前在线人数为:{}", session.getId(), onlineCount.get());
	}

	/**
	 * 收到客户端消息后调用的方法
	 *
	 * @param message
	 *            客户端发送过来的消息
	 */
	@OnMessage
	public void onMessage(final String message, final Session session) {
		log.info("服务端收到客户端[{}]的消息:{}", session.getId(), message);
	}

	@OnError
	public void onError(final Session session, final Throwable error) {
		log.error("发生错误");
		error.printStackTrace();
	}

	/**
	 * 服务端发送消息给客户端
	 */
	private void sendMessage(final String message, final Session toSession) {
		try {
			log.info("服务端给客户端[{}]发送消息{}", toSession.getId(), message);
			toSession.getBasicRemote().sendText(message);
		} catch (Exception e) {
			log.error("服务端发送消息给客户端失败:{}", e);
		}
	}
}

前端:

<!DOCTYPE html>
<html>
	<head>

		<title>h.264 To fmp4</title>
		<script src="js/jquery/jquery-1.12.3.js"> </script>
		<script src="js/wfs.js"></script>
		<link href="js/jquery/jquery-ui.css" rel="stylesheet" type="text/css" />
		
		<style type="text/css" media="screen">
			video.rotate180 {
				width: 100%;
				height: 100%;
				transform: rotateX(180deg);
				-moz-transform: rotateX(180deg);
				-webkit-transform: rotateX(180deg);
				-o-transform: rotateX(180deg);
				-ms-transform: rotateX(180deg);
			}
		</style>
	</head>
	<body>
		<h2>h.264 To fmp4</h2>
		<div class="wfsjs">
			<video id="video1" muted="muted" controls="controls" style="width: 100%;height: 100%;"
				autoplay="autoplay" muted></video>
			<div class="ratio"></div>
		</div>

		<script>
			window.onload = function() {
				if (Wfs.isSupported()) {
					var video1 = document.getElementById("video1");
					var wfs = new Wfs();
					wfs.attachMedia(video1, 'ch1');
				}
			};
		</script>

	</body>
</html>

注意wfs.js中要修改:

{
    key: 'onMediaAttached',
    value: function onMediaAttached(data) {
      if (data.websocketName != undefined) {
        //var client = new WebSocket( 'ws://' + window.location.host + '/' +  data.websocketName );
        //var uri = 'ws://' + '10.122.4.17:18080';
        //var protocol = 'binary';
        //var client = new WebSocket(uri, protocol);
		var client = new WebSocket('ws://10.122.4.17:18080/wstest');
        this.wfs.attachWebsocket(client, data.channelName);
      } else {
        console.log('websocketName ERROE!!!');
      }
    }
  }
{
    key: 'receiveSocketMessage',
    value: function receiveSocketMessage(event) {
		var buffer = new Uint8Array(event.data);
		this.wfs.trigger(_events2.default.H264_DATA_PARSING, { data:buffer });
	}
  }

 

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐