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

 

四。项目地址:

    代码实现并非特别严谨,希望有时间再进行优化。生活不易,且行且珍惜。

posted @ 2023-11-02 17:09  飞翔天空energy  阅读(1849)  评论(1编辑  收藏  举报