海康ISUP协议二次开发(C++)
一。功能支持:
- 1.设备注册。支持接入秘钥
- 2.实时预览。支持H264/H265视频,不支持音频。
- 3.录像回放。支持H264/H265视频,不支持音频。同一个通道只支持一路回放。
- 4.云台控制。
- 5.在线状态查询。
二。技术思路:
- 1.使用海康SDK进程CMS和SMS的初始化监听。
- 2.接收设备的注册请求,并缓存设备登录信息。
- 3.注册实时预览数据回调函数。
- 4.发送实时预览请求,接收设备推流回调。解析接收到的PS_HEAD中的PSM包,识别出视频流的编码信息(H264/H265)。(https://www.cnblogs.com/feixiang-energy/p/17735762.html)
- 5.初始化使用ffmpeg编写的rtmp推流模块,创建推流连接。(https://www.cnblogs.com/feixiang-energy/p/17700008.html)
- 6.之后接收到回调的视频流按视频帧组包之后进行rtmp推流,将视频流推送到流媒体服务zlmediakit。实现视频接入。
- 7.录像回放的操作与实时预览类似。
三。关键代码:
1.设备注册回调函数。参考SDK的官方DEMO
BOOL CALLBACK CB_REGISTER(LONG iUserID, DWORD dwDataType, void* pOutBuffer, DWORD dwOutLen, void* pInBuffer, DWORD dwInLen, void* pUser) { EhomeSDK* pEhome = (EhomeSDK*)pUser; if (ENUM_DEV_ON == dwDataType) { if (pOutBuffer != NULL) { NET_EHOME_DEV_REG_INFO_V12* pDevInfo = (NET_EHOME_DEV_REG_INFO_V12*)pOutBuffer; string deviceID((char*)pDevInfo->struRegInfo.byDeviceID); pEhome->DevOnFun(deviceID, iUserID); } } else if (ENUM_DEV_OFF == dwDataType || ENUM_DEV_SESSIONKEY_ERROR == dwDataType) { pEhome->DevOffFun(iUserID); } else if (ENUM_DEV_AUTH == dwDataType) { if (pOutBuffer != NULL) { NET_EHOME_DEV_REG_INFO_V12* pDevInfo = (NET_EHOME_DEV_REG_INFO_V12*)pOutBuffer; strcpy((char*)pInBuffer, pEhome->mEhomeKey.c_str()); } } else if (ENUM_DEV_SESSIONKEY == dwDataType) { if (pOutBuffer != NULL) { NET_EHOME_DEV_REG_INFO_V12* pDevInfo = (NET_EHOME_DEV_REG_INFO_V12*)pOutBuffer; NET_EHOME_DEV_SESSIONKEY struSessionkey = { 0 }; memcpy(struSessionkey.sDeviceID, pDevInfo->struRegInfo.byDeviceID, MAX_DEVICE_ID_LEN); memcpy(struSessionkey.sSessionKey, pDevInfo->struRegInfo.bySessionKey, MAX_MASTER_KEY_LEN); NET_ECMS_SetDeviceSessionKey(&struSessionkey); } } else if (ENUM_DEV_DAS_REQ == dwDataType) { int dwPort = pEhome->mCmsPort; string szLocalIP = pEhome->mSmsIp; sprintf((char*)pInBuffer, "{\"Type\":\"DAS\",\"DasInfo\":{\"Address\":\"%s\"," "\"Domain\":\"test.ys7.com\",\"ServerID\":\"das_%s_%d\",\"Port\":%d,\"UdpPort\":%d}}" , szLocalIP.c_str(), szLocalIP.c_str(), dwPort, dwPort, dwPort); } else if (ENUM_DEV_SESSIONKEY_REQ == dwDataType) { } else if (ENUM_DEV_ADDRESS_CHANGED == dwDataType) {} else if (ENUM_DEV_DAS_REREGISTER == dwDataType) { if (pOutBuffer != NULL) { NET_EHOME_DEV_REG_INFO_V12* pDevInfo = (NET_EHOME_DEV_REG_INFO_V12*)pOutBuffer; string deviceID((char*)pDevInfo->struRegInfo.byDeviceID); pEhome->DevReOnFun(deviceID, iUserID); } } else if (ENUM_DEV_DAS_PINGREO == dwDataType) {} else if (ENUM_DEV_SLEEP == dwDataType) {} else if (ENUM_DEV_DAS_EHOMEKEY_ERROR == dwDataType) {} return TRUE; }
2.实时预览回调函数:
void CALLBACK CB_STREAM_PREVIEW(LONG lPreviewHandle, NET_EHOME_PREVIEW_CB_MSG* pPreviewCBMsg, void* pUserData) { if (pPreviewCBMsg == NULL || pUserData == NULL) { return; } StuUserDataPreview* pNewUserData = (StuUserDataPreview*)pUserData; EhomeSDK* pEhome = (EhomeSDK*)pNewUserData->pEhome; PreviewInfo* pPreview = (PreviewInfo*)pNewUserData->pPreview; pEhome->StreamPreviewFun(pPreview, pPreviewCBMsg); } BOOL CALLBACK CB_NEWLINK_PREVIEW(LONG lPreviewHandle, NET_EHOME_NEWLINK_CB_MSG* pNewLinkCBMsg, void* pUserData) { if (pNewLinkCBMsg == NULL || pUserData == NULL) { return false; } string deviceID((char*)pNewLinkCBMsg->szDeviceID); LOG_INFO("CB_NEWLINK_PREVIEW, deviceID:" << deviceID.c_str() << ",channel:" << pNewLinkCBMsg->dwChannelNo << ",session:" << pNewLinkCBMsg->iSessionID << ",handle:" << lPreviewHandle); EhomeSDK* pEhome = (EhomeSDK*)pUserData; PreviewInfo* pPreview = pEhome->findPreview(deviceID, pNewLinkCBMsg->dwChannelNo, pNewLinkCBMsg->iSessionID); if (pPreview == NULL) { cout << "can't find preview" << std::endl; return FALSE; } pPreview->handleSms = lPreviewHandle; NET_EHOME_PREVIEW_DATA_CB_PARAM struDataCB = { 0 }; struDataCB.fnPreviewDataCB = CB_STREAM_PREVIEW; struDataCB.byStreamFormat = 0;//封装格式:0- PS StuUserDataPreview* pNewUserData = new StuUserDataPreview{}; pNewUserData->pPreview = pPreview; pNewUserData->pEhome = pUserData; struDataCB.pUserData = (void*)pNewUserData; if (!NET_ESTREAM_SetPreviewDataCB(lPreviewHandle, &struDataCB)) { cout << "NET_ESTREAM_SetPreviewDataCB failed:" << NET_ESTREAM_GetLastError() << endl; return FALSE; } return TRUE; }
3.录像回放回调函数:
BOOL CALLBACK CB_STREAM_PLAYBACK(LONG lPreviewHandle, NET_EHOME_PLAYBACK_DATA_CB_INFO* pPlaybackCBMsg, void* pUserData) { if (pPlaybackCBMsg == NULL || pUserData == NULL) { return FALSE; } StuUserDataPlayback* pNewUserData = (StuUserDataPlayback*)pUserData; EhomeSDK* pEhome = (EhomeSDK*)pNewUserData->pEhome; PlaybackInfo* pPlayback = (PlaybackInfo*)pNewUserData->pPlayback; return pEhome->StreamPlaybackFun(pPlayback, pPlaybackCBMsg); } BOOL CALLBACK CB_NEWLINK_PLAYBACK(LONG lPlayBackLinkHandle, NET_EHOME_PLAYBACK_NEWLINK_CB_INFO* pNewLinkCBMsg, void* pUserData) { if (pNewLinkCBMsg == NULL || pUserData == NULL) { return FALSE; } string deviceID((char*)pNewLinkCBMsg->szDeviceID); LOG_INFO("CB_NEWLINK_PLAYBACK, deviceID:" << deviceID.c_str() << ", channel : " << pNewLinkCBMsg->dwChannelNo << ", session : " << pNewLinkCBMsg->lSessionID << ", handle : " << lPlayBackLinkHandle); EhomeSDK* pEhome = (EhomeSDK*)pUserData; PlaybackInfo* pPlayback = pEhome->findPlayback(deviceID, pNewLinkCBMsg->dwChannelNo, pNewLinkCBMsg->lSessionID); pPlayback->handleSms = lPlayBackLinkHandle; if (pPlayback == NULL) { cout << "can't find playback" << std::endl; return FALSE; } NET_EHOME_PLAYBACK_DATA_CB_PARAM struDataCB = { 0 }; struDataCB.fnPlayBackDataCB = CB_STREAM_PLAYBACK; struDataCB.byStreamFormat = 0;//封装格式:0-PS 格式 StuUserDataPlayback* pNewUserData = new StuUserDataPlayback{}; pNewUserData->pPlayback = pPlayback; pNewUserData->pEhome = pUserData; struDataCB.pUserData = (void*)pNewUserData; if (!NET_ESTREAM_SetPlayBackDataCB(lPlayBackLinkHandle, &struDataCB)) { printf("NET_ESTREAM_SetPlayBackDataCB failed, error code: %d\n", NET_ESTREAM_GetLastError()); return FALSE; } return TRUE; }
4.Ehome初始化:
bool EhomeSDK::InitEhome(int cmsPort, string smsIp, int smsPreviewPort, int smsPlaybackPort, string ehomeKey, string rtmpIp, int rtmpPort, string zlkSecret) { mCmsPort = cmsPort; mSmsIp = smsIp; mSmsPreviewPort = smsPreviewPort; mSmsPlaybackPort = smsPlaybackPort; mEhomeKey = ehomeKey; mRtmpIp = rtmpIp; mRtmpPort = rtmpPort; mZlkSecret = zlkSecret; // 1. SDK版本号 DWORD dwVersion = NET_ECMS_GetBuildVersion(); DWORD dwV1 = (dwVersion >> 24); DWORD dwV2 = ((dwVersion & 0x00FF0000) >> 16); DWORD dwV3 = ((dwVersion & 0x0000FF00) >> 8); DWORD dwV4 = (dwVersion & 0x000000FF); LOG_INFO("Ehome SDK Version[" << dwV1 << "." << dwV2 << "." << dwV3 << "." << dwV4 << "]"); // 2.启动CMS注册监听。0.0.0.0:7660 NET_EHOME_CMS_LISTEN_PARAM struCMSListenPara = { 0 }; string cmsIp = "0.0.0.0"; memcpy(struCMSListenPara.struAddress.szIP, cmsIp.c_str(), cmsIp.length()); struCMSListenPara.struAddress.wPort = (WORD)mCmsPort; struCMSListenPara.fnCB = CB_REGISTER; struCMSListenPara.pUserData = this; struCMSListenPara.dwKeepAliveSec = 15; struCMSListenPara.dwTimeOutCount = 6; hListenCms = NET_ECMS_StartListen(&struCMSListenPara); if (hListenCms < 0) { LOG_ERROR("NET_ECMS_StartListen Failed:" << NET_ECMS_GetLastError()); return false; } LOG_INFO("NET_ECMS_StartListen Success! IP:" << cmsIp.c_str() << "Port:" << mCmsPort); // 3.启动SMS实时预览监听。 0.0.0.0:7661 NET_EHOME_LISTEN_PREVIEW_CFG struListenPreview = { 0 }; memcpy(struListenPreview.struIPAdress.szIP, mSmsIp.c_str(), mSmsIp.length()); struListenPreview.struIPAdress.wPort = (WORD)mSmsPreviewPort; struListenPreview.fnNewLinkCB = CB_NEWLINK_PREVIEW; struListenPreview.pUser = this; struListenPreview.byLinkMode = 0; //0-TCP, 1-UDP(保留) struListenPreview.byLinkEncrypt = 0; hListenSmsPreview = NET_ESTREAM_StartListenPreview(&struListenPreview); if (hListenSmsPreview < 0) { LOG_ERROR("NET_ESTREAM_StartListenPreview Failed:" << NET_ECMS_GetLastError()); return false; } LOG_INFO("NET_ESTREAM_StartListenPreview Success! IP:" << mSmsIp.c_str() << ", Port:" << mSmsPreviewPort); // 4.启动SMS录像回放监听。 0.0.0.0:7662 NET_EHOME_PLAYBACK_LISTEN_PARAM struListenPlayBcak = { 0 }; memcpy(struListenPlayBcak.struIPAdress.szIP, mSmsIp.c_str(), mSmsIp.length()); struListenPlayBcak.struIPAdress.wPort = mSmsPlaybackPort; struListenPlayBcak.fnNewLinkCB = CB_NEWLINK_PLAYBACK; struListenPlayBcak.pUserData = this; struListenPlayBcak.byLinkMode = 0; //0-TCP, 1-UDP(保留) struListenPlayBcak.byLinkEncrypt = 0; hListenSmsPlayback = NET_ESTREAM_StartListenPlayBack(&struListenPlayBcak); if (hListenSmsPlayback < 0) { LOG_ERROR("NET_ESTREAM_StartListenPlayBack Failed: " << NET_ESTREAM_GetLastError()); return false; } LOG_INFO("NET_ESTREAM_StartListenPlayBack Success! IP:" << mSmsIp.c_str() << ", Port:" << mSmsPlaybackPort); return true; }
5.开始预览:
bool EhomeSDK::StartPreview(string deviceID, int channel, string& playUrl) { DeviceInfo* pDevice = findDevice(deviceID); if (pDevice == nullptr) { LOG_ERROR("can't find device:" << deviceID.c_str()); return false; } string token = MakeChannelToken(deviceID, channel, TYPE_PLAY::Preview); PreviewInfo* pPreview = findPreview(deviceID, channel); if (pPreview != nullptr) { StopPreview(deviceID, channel); // 先停止之前的预览 } NET_EHOME_PREVIEWINFO_IN_V11 struPreviewIn = { 0 }; struPreviewIn.iChannel = channel; struPreviewIn.dwLinkMode = 0; //0-TCP,1-UDP struPreviewIn.dwStreamType = 0; //0-主码流,1-子码流,2-第三码率 memcpy(struPreviewIn.struStreamSever.szIP, mSmsIp.c_str(), mSmsIp.length()); struPreviewIn.struStreamSever.wPort = mSmsPreviewPort; struPreviewIn.byEncrypt = 0; struPreviewIn.byDelayPreview = 1;//是否延时取流 NET_EHOME_PREVIEWINFO_OUT struPreviewOut = { 0 }; if (!NET_ECMS_StartGetRealStreamV11(pDevice->loginID, &struPreviewIn, &struPreviewOut)) { LOG_ERROR("NET_ECMS_StartGetRealStreamV11 failed:" << NET_ECMS_GetLastError()); return false; } LOG_INFO("NET_ECMS_StartGetRealStreamV11 success,deviceID:" << deviceID.c_str() << ", channel:" << channel << ", session:" << struPreviewOut.lSessionID); NET_EHOME_PUSHSTREAM_IN struPushStreamIn = { 0 }; struPushStreamIn.dwSize = sizeof(struPushStreamIn); struPushStreamIn.lSessionID = struPreviewOut.lSessionID; NET_EHOME_PUSHSTREAM_OUT struPushStreamOut = { 0 }; struPushStreamOut.dwSize = sizeof(struPushStreamOut); pPreview = new PreviewInfo{}; pPreview->deviceID = deviceID; pPreview->channel = channel; pPreview->session = struPreviewOut.lSessionID; pPreview->pVideoBuffOffset = 0; pPreview->pVideoBuff = new unsigned char[MAX_VIDEO_BUFF]; pPreview->hasVideo = false; pPreview->handleCms = struPreviewOut.lHandle; pPreview->token = MakeChannelToken(deviceID, channel, TYPE_PLAY::Preview); addPreview(pPreview); if (!NET_ECMS_StartPushRealStream(pDevice->loginID, &struPushStreamIn, &struPushStreamOut)) { LOG_ERROR("NET_ECMS_StartPushRealStream failed:" << NET_ECMS_GetLastError()); return false; } LOG_INFO("NET_ECMS_StartPushRealStream success: deviceID:" << deviceID.c_str() << ",channel:" << channel << ",SessionID:" << struPushStreamIn.lSessionID); playUrl = token; return true; }
6.停止预览:
bool EhomeSDK::StopPreview(string deviceID, int channel) { PreviewInfo* pPreview = findPreview(deviceID, channel); if (pPreview == nullptr) { return true; } DeviceInfo* pDevice = findDevice(deviceID); if (pDevice == nullptr) { erasePreview(deviceID, channel); return true; } NET_EHOME_STOPSTREAM_PARAM struStopParam = { 0 }; struStopParam.lHandle = pPreview->handleCms; struStopParam.lSessionID = pPreview->session; if (!NET_ECMS_StopGetRealStreamEx(pDevice->loginID, &struStopParam)) { LOG_ERROR("NET_ECMS_StopGetRealStreamEx failed, deviceID:" << deviceID.c_str() << ",channel: " << channel << ",error code : " << NET_ECMS_GetLastError()); erasePreview(deviceID, channel); return false; } LOG_INFO("NET_ECMS_StopGetRealStreamEx success, deviceID:" << deviceID.c_str() << ",channel: " << channel << ",session:" << pPreview->session); if (!NET_ESTREAM_StopPreview(pPreview->handleSms)) { LOG_ERROR("NET_ESTREAM_StopPreview failed, deviceID: " << deviceID.c_str() << ", channel:" << channel << ",error code : " << NET_ECMS_GetLastError()); erasePreview(deviceID, channel); return false; } LOG_INFO("NET_ESTREAM_StopPreview success, deviceID:" << deviceID.c_str() << ",channel: " << channel << ",session:" << pPreview->session << ",handle:" << pPreview->handleSms); erasePreview(pPreview->deviceID, pPreview->channel); return true; }
7.实时预览视频流处理(音频暂不处理):
bool EhomeSDK::StreamPreviewFun(PreviewInfo* pPreview, NET_EHOME_PREVIEW_CB_MSG* pPreviewCBMsg) { if (pPreview == nullptr) { return false; } //cout << "deviceID:" << preview->deviceID << ";channel:" << preview->channel << ";dataLen:" << pPreviewCBMsg->dwDataLen << endl; //fwrite(pPreviewCBMsg->pRecvdata, 1, pPreviewCBMsg->dwDataLen, m_fPsFile); //string tempFile = "ps/test_" + to_string(NCOUNT) + ".ps"; //FILE* f = fopen(tempFile.c_str(), "wb"); //fwrite(pPreviewCBMsg->pRecvdata, 1, pPreviewCBMsg->dwDataLen, f); //NCOUNT++; //fclose(f); //unsigned char* temp = (unsigned char*)pPreviewCBMsg->pRecvdata; //printf("size:%04d\t%02X %02X %02X %02X %02X\n", pPreviewCBMsg->dwDataLen, temp[0], temp[1], temp[2], temp[3], temp[4]); unsigned char* streamBuff = NULL; int streamLen = 0; ps_stream_type streamType; if (!GetStreamFromPS((unsigned char*)pPreviewCBMsg->pRecvdata, pPreviewCBMsg->dwDataLen, &streamBuff, streamLen, streamType)) { LOG_ERROR("preview GetStreamFromPS failed, deviceID" << pPreview->deviceID.c_str() << ",channel:" << pPreview->channel); if (streamBuff) { delete[] streamBuff; streamBuff = NULL; } return false; } if (streamType == ps_stream_type::head) { if (!pPreview->hasVideo && streamBuff && streamLen >= 1) { AVCodecID videoCodecID = AV_CODEC_ID_NONE; string codec = "unknow"; if (streamBuff[0] == 0x1B) { videoCodecID = AV_CODEC_ID_H264; codec = "H264"; pPreview->hasVideo = true; } else if (streamBuff[0] == 0x24) { videoCodecID = AV_CODEC_ID_H265; codec = "H265"; pPreview->hasVideo = true; } else { pPreview->hasVideo = false; } if (pPreview->hasVideo) { string token = MakeChannelToken(pPreview->deviceID, pPreview->channel, TYPE_PLAY::Preview); string rtmpUrl = MakeRtmpUrl(token); if (pPreview->pusher.InitRtmp(rtmpUrl.c_str(), videoCodecID) < 0) { LOG_ERROR("InitRtmp failed:" << rtmpUrl.c_str()); if (streamBuff) { delete[] streamBuff; streamBuff = NULL; } pPreview->hasVideo = false; return false; } LOG_INFO("InitRtmp success,url" << rtmpUrl.c_str() << ", codec:" << codec.c_str()); } } } else if (streamType == ps_stream_type::video) { if (streamBuff && streamLen >= 4) { if (streamBuff[0] == 0 && streamBuff[1] == 0 && streamBuff[2] == 0 && streamBuff[3] == 1) { if (pPreview->pVideoBuffOffset > 0) { //printf("H264 size:%d\n", preview->fPusherBuffOffset); //fwrite(preview->fPusherBuff, 1, preview->fPusherBuffOffset, m_fVideoFile); if (pPreview->hasVideo) { pPreview->pusher.WriteVideoFrame((char*)pPreview->pVideoBuff, pPreview->pVideoBuffOffset); } pPreview->pVideoBuffOffset = 0; } if (pPreview->pVideoBuffOffset + streamLen < MAX_VIDEO_BUFF) { memcpy(pPreview->pVideoBuff, streamBuff, streamLen); pPreview->pVideoBuffOffset += streamLen; } } else { if (pPreview->pVideoBuffOffset > 0 && pPreview->pVideoBuffOffset + streamLen < MAX_VIDEO_BUFF) { memcpy(pPreview->pVideoBuff + pPreview->pVideoBuffOffset, streamBuff, streamLen); pPreview->pVideoBuffOffset += streamLen; } } } } else { } if (streamBuff) { delete[] streamBuff; streamBuff = NULL; } return true; }
8.开始录像回放,停止录像回放。略
9.录像回放视频流处理:(回放流的处理不是特别严谨:回放的回调中会包含多帧数据,需要进行拆包,找到帧头,然后再组成一帧数据之后发送;通过控制发送的速度和帧率比较进行控制回调的速度,防止回放速度过快)
bool EhomeSDK::StreamPlaybackFun(PlaybackInfo* pPlayback, NET_EHOME_PLAYBACK_DATA_CB_INFO* pPlaybackCBMsg) { if (pPlayback == nullptr) { return false; } if (!pPlaybackCBMsg->pData || pPlaybackCBMsg->dwDataLen < 4){ return false; } //unsigned char* streamBuf = NULL; //int streamLen = 0; unsigned char* temp = (unsigned char*)pPlaybackCBMsg->pData; //printf("type:%d\tsize:%04d\t%02X %02X %02X %02X %02X\n", pPlaybackCBMsg->dwType, pPlaybackCBMsg->dwDataLen, temp[0], temp[1], temp[2], temp[3], temp[4]); //fwrite(pPlaybackCBMsg->pData, 1, pPlaybackCBMsg->dwDataLen, m_fPsFile); int preIndex = 0; for (int i = 0; i < pPlaybackCBMsg->dwDataLen - 4; i++) { if (temp[i] == 0 && temp[i + 1] == 0 && temp[i + 2] == 1 && (temp[i+3] == 0xBA || temp[i+3] == 0xE0)) { //printf("-------->\t%02X %02X %02X %02X %02X\n", temp[i], temp[i+1], temp[i+2], temp[i+3], temp[4]); int nowIndex = i; int tempLen = nowIndex - preIndex; if (tempLen > 0 && (pPlayback->pTempOffset + tempLen) < MAX_VIDEO_BUFF) { memcpy(pPlayback->pTempBuff + pPlayback->pTempOffset, pPlaybackCBMsg->pData + preIndex, tempLen); preIndex = nowIndex; pPlayback->pTempOffset += tempLen; } //string tempFile = "ps/test_" + to_string(NCOUNT) + ".ps"; //FILE* f = fopen(tempFile.c_str(), "wb"); //fwrite(pPlayback->pTempBuff, 1, pPlayback->pTempOffset, f); //NCOUNT++; //fclose(f); StreamPlaybackFun2(pPlayback, pPlayback->pTempBuff, pPlayback->pTempOffset); pPlayback->pTempOffset = 0; } } int tempLen = pPlaybackCBMsg->dwDataLen - preIndex; if (tempLen > 0 && (pPlayback->pTempOffset + tempLen) < MAX_VIDEO_BUFF) { memcpy(pPlayback->pTempBuff + pPlayback->pTempOffset, pPlaybackCBMsg->pData + preIndex, tempLen); pPlayback->pTempOffset += tempLen; } return true; } bool EhomeSDK::StreamPlaybackFun2(PlaybackInfo* pPlayback, unsigned char* buff, int bufflen) { unsigned char* streamBuf = NULL; int streamLen = 0; ps_stream_type streamType; if (!GetStreamFromPS(buff, bufflen, &streamBuf, streamLen, streamType)) { LOG_ERROR("playback GetStreamFromPS failed, deviceID" << pPlayback->deviceID.c_str() << ",channel:" << pPlayback->channel); if (streamBuf) { delete[] streamBuf; streamBuf = NULL; } return false; } if (streamType == ps_stream_type::head) { if (!pPlayback->hasVideo && streamBuf && streamLen >=1) { AVCodecID videoCodecID = AV_CODEC_ID_NONE; string codec = "unknow"; if (streamBuf[0] == 0x1B) { videoCodecID = AV_CODEC_ID_H264; codec = "H264"; pPlayback->hasVideo = true; } else if (streamBuf[0] == 0x24) { videoCodecID = AV_CODEC_ID_H265; codec = "H265"; pPlayback->hasVideo = true; } else { pPlayback->hasVideo = false; } if (pPlayback->hasVideo) { string token = MakeChannelToken(pPlayback->deviceID, pPlayback->channel, TYPE_PLAY::PlayBack); string rtmpUrl = MakeRtmpUrl(token); if (pPlayback->pusher.InitRtmp(rtmpUrl.c_str(), videoCodecID) < 0) { LOG_ERROR("InitRtmp failed:" << rtmpUrl.c_str()); if (streamBuf) { delete[] streamBuf; streamBuf = NULL; } pPlayback->hasVideo = false; return false; } LOG_INFO("InitRtmp success,url" << rtmpUrl.c_str() << ", codec:" << codec.c_str()); } } } else if (streamType == ps_stream_type::video) { if (!pPlayback->hasVideo) { if (streamBuf) { delete[] streamBuf; streamBuf = NULL; } return false; } if (streamBuf && streamLen >= 4) { if (streamBuf[0] == 0 && streamBuf[1] == 0 && streamBuf[2] == 0 && streamBuf[3] == 1) { if (pPlayback->pVideoBuffOffset > 0) { if (pPlayback->hasVideo) { if (pPlayback->prePts != 0) { int gap = getNowMsTime() - pPlayback->prePts; int rate = 1000 / FRAME_RATE; if (gap < rate) { Sleep(rate - gap); //休眠阻塞,防止回放视频播放过快 } } pPlayback->pusher.WriteVideoFrame((char*)pPlayback->pVideoBuff, pPlayback->pVideoBuffOffset); pPlayback->prePts = getNowMsTime(); } pPlayback->pVideoBuffOffset = 0; } if (pPlayback->pVideoBuffOffset + streamLen < MAX_VIDEO_BUFF) { memcpy(pPlayback->pVideoBuff, streamBuf, streamLen); pPlayback->pVideoBuffOffset += streamLen; } } else { if (pPlayback->pVideoBuffOffset > 0 && pPlayback->pVideoBuffOffset + streamLen < MAX_VIDEO_BUFF) { memcpy(pPlayback->pVideoBuff + pPlayback->pVideoBuffOffset, streamBuf, streamLen); pPlayback->pVideoBuffOffset += streamLen; } } } } else { } if (streamBuf) { delete[] streamBuf; streamBuf = NULL; } return true; }
10.海康PS流解析:
bool EhomeSDK::GetStreamFromPS(BYTE* pBuffer, int nBufLenth, BYTE** pStream, int& nStreamLenth, ps_stream_type& streamType) { if (!pBuffer || nBufLenth < 4) { return false; } streamType = ps_stream_type::unknow; BYTE* pStreamBuffer = NULL; int nHerderLen = 0; if (pBuffer && pBuffer[0] == 0x00 && pBuffer[1] == 0x00 && pBuffer[2] == 0x01 && pBuffer[3] == 0xE0) { streamType = ps_stream_type::video; nHerderLen = 9 + (int)pBuffer[8]; pStreamBuffer = pBuffer + nHerderLen; if (*pStream == NULL) { *pStream = new BYTE[nBufLenth]; } if (*pStream && pStreamBuffer && (nBufLenth - nHerderLen) > 0) { memcpy(*pStream, pStreamBuffer, (nBufLenth - nHerderLen)); } nStreamLenth = nBufLenth - nHerderLen; return true; } else if (pBuffer && pBuffer[0] == 0x00 && pBuffer[1] == 0x00 && pBuffer[2] == 0x01 && pBuffer[3] == 0xC0) { streamType = ps_stream_type::audio; nHerderLen = 9 + (int)pBuffer[8]; pStreamBuffer = pBuffer + nHerderLen; if (*pStream == NULL) { *pStream = new BYTE[nBufLenth]; } if (*pStream && pStreamBuffer && (nBufLenth - nHerderLen) > 0) { memcpy(*pStream, pStreamBuffer, (nBufLenth - nHerderLen)); } nStreamLenth = nBufLenth - nHerderLen; return true; } else if (pBuffer && pBuffer[0] == 0x00 && pBuffer[1] == 0x00 && pBuffer[2] == 0x01 && pBuffer[3] == 0xBA) { //unsigned char pBuffer[1024] = { // 0x00, 0x00, 0x01, 0xBA, 0x45, 0xEC, 0x36, 0x4E, 0xA4, 0x01, 0x00, 0x00, 0x03, 0xFE, 0xFF, 0xFF, // 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0xBC, 0x00, 0x5A, 0xE0, 0xFF, 0x00, 0x24, 0x40, 0x0E, // 0x48, 0x4B, 0x00, 0x00, 0x17, 0xA3, 0xB5, 0x98, 0xA0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x41, 0x12, // 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x00, 0x00, 0x00, 0x2C, 0x1B, 0xE0, 0x00, 0x10, 0x42, 0x0E, 0x00, 0x00, 0xA0, 0x21, 0x05, 0x00, // 0x02, 0xD0, 0x12, 0x1C, 0x3F, 0x00, 0x1C, 0x21, 0x0F, 0xC0, 0x00, 0x0C, 0x43, 0x0A, 0x00, 0x00, // 0xFE, 0x00, 0xFA, 0x03, 0x01, 0xF4, 0x03, 0xFF, 0xBD, 0xBD, 0x00, 0x00, 0xBF, 0xBF, 0x00, 0x00, // 0x00, 0x00, 0x00, 0x00 //}; //FILE* f = fopen("head.ps", "wb"); //fwrite(pBuffer, nBufLenth, 1, f); //fclose(f); streamType = ps_stream_type::head; if (*pStream == NULL) { *pStream = new BYTE[nBufLenth]; } unsigned char mediaType[2] = { 0 }; ParsePSHead(pBuffer, nBufLenth, mediaType[0], mediaType[1]); memcpy(*pStream, mediaType, 2); nStreamLenth = 2; return true; } else if (pBuffer && pBuffer[0] == 0x00 && pBuffer[1] == 0x00 && pBuffer[2] == 0x01 && pBuffer[3] == 0xBC) { return true; } else if (pBuffer && pBuffer[0] == 0x00 && pBuffer[1] == 0x00 && pBuffer[2] == 0x01 && pBuffer[3] == 0xBD) { // 海康私有码流,丢弃 return true; } else if (pBuffer && pBuffer[0] == 0x49 && pBuffer[1] == 0x4D && pBuffer[2] == 0x4B && pBuffer[3] == 0x48) { // 海康私有码流头,丢弃 return true; } printf("-------->\t%02X %02X %02X %02X %02X\n", pBuffer[0], pBuffer[1], pBuffer[2], pBuffer[3], pBuffer[4]); return false; } bool EhomeSDK::ParsePSHead(BYTE* pBuffer, int nBufLenth, unsigned char& videoType, unsigned char& audioType) { videoType = audioType = 0x00; int index = 0; ps_head* PSHead = (ps_head*)pBuffer; PSHead->stuffinglen = PSHead->stuffinglen & 0x07; index = sizeof(ps_head) + PSHead->stuffinglen; program_stream* PSProgrm = (program_stream*)(pBuffer + index); PSProgrm->packlen = ((PSProgrm->packlen & 0x0F) << 8) + (PSProgrm->packlen >> 8); PSProgrm->program_strem_info_len = ((PSProgrm->program_strem_info_len & 0x0F) << 8) + (PSProgrm->program_strem_info_len >> 8); index = index + sizeof(program_stream) + PSProgrm->program_strem_info_len; index += 2; int elementary_stream_map_length = ((pBuffer[index] << 8) + pBuffer[index + 1]); while (index < nBufLenth) { elementary_stream* PSElementary1 = (elementary_stream*)(pBuffer + index); if (PSElementary1->elementary_stream_id[0] == 0xE0) { videoType = PSElementary1->stream_type[0]; } else if (PSElementary1->elementary_stream_id[0] == 0xC0) { audioType = PSElementary1->stream_type[0]; } else { break; } PSElementary1->elementary_stream_info_len = ((PSElementary1->elementary_stream_info_len & 0x0F) << 8) + (PSElementary1->elementary_stream_info_len >> 8); index = index + sizeof(elementary_stream) + PSElementary1->elementary_stream_info_len; } return true; }
四。项目地址:
代码实现并非特别严谨,希望有时间再进行优化。生活不易,且行且珍惜。