通过海康SDK预览获取回调的PS流数据自己解析然后前端播放

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

第一版:

用海康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 });
	}
  }

 

  • 8
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 61
    评论
要通过海康SDK获取回看PS数据,需要按照以下步骤进行操作: 1. 运用海康SDK提供的相关API,首先需要初始化SDK环境并登录设备。通过调用SDK的登录接口,提供相应的设备信息,如IP地址、端口号、用户名和密码等,来建立与设备的连接。 2. 在成功登录设备后,需要设置回看的相关参数,包括回看的起始时间和结束时间等。可以通过调用SDK的设置回看参数的接口来进行配置。 3. 创建一个回看的句柄。通过调用SDK的创建回看的接口,并传入设备的登录ID以及回看参数等信息,来创建一个回看。 4. 通过回看的句柄,使用SDK提供的回放控制接口来控制回看的操作,如开始回放、暂停回放、恢复回放、停止回放等。 5. 接收回看数据。通过注册回放数据回调函数,SDK会将回看的PS数据通过该回调函数传递给应用程序。应用程序可以在回调函数中对接收到的PS数据进行处理和解析,以适应自己的业务需求。 6. 释放回看资源和关闭设备连接。在回看使用完成后,需要调用SDK提供的释放回看资源的接口来释放回看所占用的资源。最后,调用SDK的注销登录接口来关闭设备的连接。 综上所述,通过海康SDK获取回看PS数据,需要进行SDK的初始化和登录、设置回看参数、创建回看句柄、控制回放操作、接收回看数据、释放回看资源和关闭设备连接等一系列操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 61
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值