录像回放流程
-
录像回放流程
录像搜索
搜索条件获取
在客户端中选择通道(最多同时选择4个),点击搜索。搜索的响应函数如下
void CSearchPane::OnSearch() { //获取搜索参数,存于queryFactor中 StreamSearchMode::TVideoSearchCondition queryFactor; if (0 != GetSearchParam(queryFactor)) return; //录像搜索需要调用StreamSearchModel IStreamSearchMode* pInstance = GetInlPtr<IStreamSearchMode*>(_T("StreamSearchMode")); if (pInstance == NULL) return; //获取回放面板的指针 CWnd* videoWnd = NULL; ::SendMessage(CReplayUI::replayUI->GetSafeHwnd(), WM_GET_VIDEOWND, WPARAM(&videoWnd), 0); //开始搜索 int ret; std::vector<StreamSearchMode::TVideoSearchCondition*> params; params.push_back(&queryFactor); ret = pInstance->StartStreamSearch(LONG(StreamSearchMode::eReplay), params); //错误处理 }
搜索录像需要结构体TVideoSearchCondition保存搜索条件
struct TVideoSearchCondition { ERecordPlace place; //录像位置 ERecordType type; //录像类型 SDPTIME begin; //搜索条件起始时间 SDPTIME end; //搜索条件结束时间 std::string source; //CMS源 std::vector<std::string> choid; //搜索通道的oid std::vector<std::string> oidNru; //对nru设备上的录像回放时,记录nru源 };
搜索条件在GetSearchParam函数中获取
int CSearchPane::GetSearchParam(StreamSearchMode::TVideoSearchCondition &queryFactor) { //获取选中的通道 ASSERT(m_tree); std::vector<TreeNode*> nodes; m_tree->GetCheckedNode(nodes); //录像存放的位置不同,搜索条件也不同。中心录像需要oid和nruOid,而前端录像只需要oid //ERecordPlace_centerServer——中心录像 ERecordPlace_equipment——前端录像 if (m_recordPlace == ERecordPlace_centerServer) { for(std::vector<TreeNode*>::iterator it = nodes.begin(); it != nodes.end(); it++) { NruMitObject *obj = dynamic_cast<NruMitObject*>(*it); if (obj->info.kind == ENCKIND) { queryFactor.choid.push_back(Type_cast<std::string>(obj->info.oid)); queryFactor.oidNru.push_back(Type_cast<std::string>(obj->info.nruOid)); } } } else if (m_recordPlace==ERecordPlace_equipment) { for (std::vector<TreeNode*>::iterator it = nodes.begin(); it != nodes.end(); it++) { MitObject *pMitObj = dynamic_cast<MitObject*>(*it); if (pMitObj->info.kind == ENCKIND) { queryFactor.choid.push_back(Type_cast<std::string>(pMitObj->info.oid)); } } } //CMS源 queryFactor.source = "\\"; //搜索时间,为所选日期当天的00:00:00-23:59:59 SYSTEMTIME searchDay; COleDateTime refDateTime; m_wndDatePicker.GetCurSel(refDateTime); //获取日历控件上的日期 TSDPLocalTime sdpTime; memset(&sdpTime, 0, sizeof(TSDPLocalTime)); sdpTime.year=refDateTime.GetYear(); sdpTime.month=refDateTime.GetMonth()-1; //TSDPLocalTime这一结构体中月份的取值范围为0-11 sdpTime.day=UCHAR(refDateTime.GetDay()); sdpTime.hour=sdpTime.min=sdpTime.sec=0; SDPTIME dwTime; sdpMakeLocalTime(dwTime, sdpTime); queryFactor.begin = dwTime; //开始时间 sdpTime.hour=23; sdpTime.min=59; sdpTime.sec=59; sdpTime.wday=0; sdpTime.yday=0; sdpTime.ms=0; sdpMakeLocalTime(dwTime, sdpTime); queryFactor.end = dwTime; //结束时间 //搜索录像类型 int nSelect = m_com_event.GetCurSel(); ASSERT(nSelect>=int(ERecordType_AllRecord) && nSelect <= int(ERecordType_AlarmLinked)); switch (nSelect) { case 0: queryFactor.type = ERecordType_AllRecord; //全部视频 break; case 1: queryFactor.type = ERecordType_TimerRecord; //定时录像 break; case 2: queryFactor.type = ERecordType_LinkedRecord; //联动录像 break; case 3: queryFactor.type = ERecordType_MobileLinked; //移动侦测 break; case 4: queryFactor.type = ERecordType_AlarmLinked; //报警触发 break; default: queryFactor.type = ERecordType_AllRecord; //全部视频 } //录像位置,即中心录像或前端录像 queryFactor.place = m_recordPlace; return 0; }
-
《明涛说代码》系列2333
-
开始搜索
调用StartStreamSearch函数,初步检查参数的有效性
int CStreamSearchMode::StartStreamSearch(LONG id, std::vector<StreamSearchMode::TVideoSearchCondition*> &queryFactor) { //检查参数是否有效 if(queryFactor.size() == 0) return -1; bool bValid = false; for(std::vector<StreamSearchMode::TVideoSearchCondition*>::iterator it = queryFactor.begin(); it!=queryFactor.end(); it++) { StreamSearchMode::TVideoSearchCondition *tmp = *it; if (tmp->begin < tmp->end && tmp->source.compare("") != 0 && (tmp->type >= ERecordType_AllRecord && tmp->type <= ERecordType_AlarmLinked) && (tmp->place == ERecordPlace_equipment || tmp->place == RecordPlace_centerServer) && tmp->choid.size() != 0) { bValid = true; break; } } if (!bValid) { return -1; } //检查该类型的搜索是否正在进行 m_lock.Acquire(); std::set<LONG>::iterator it = m_set_id.find(id); if (it != m_set_id.end()) { m_lock.Release(); return -1; } m_set_id.insert(id); m_lock.Release(); //保存搜索参数 SearchParams params; for (std::vector<StreamSearchMode::TVideoSearchCondition*>::iterator it = queryFactor.begin(); it != queryFactor.end(); it++) { params.m_queryFactor.push_back(**it); } params.id = id; PostEvent(EVT_SEARCH_PARAMS, ¶ms); return 0; }
SearchParams保存事件数据
class SearchParams : public IEventData { public: LONG id; //执行线程id std::vector<StreamSearchMode::TVideoSearchCondition> m_queryFactor; //搜索条件 };
事件EVT_SEARCH_PARAMS的响应函数为OnQueryVideo,值得注意的是中心录像和前端录像搜索时所需的参数不同(分别对应ERecordPlace_centerServer和ERecordPlace_equipment)。搜索中心录像时需要同时提供id和channel,id格式为"\nru==%d",channel格式为"\hkdvr=%d\enc=%d"。前者为录像所属nru源的id,可通过channel获取;后者的hkdvr的值为所要搜索录像的摄像头的设备id,而enc的值一般为1。而搜索前端录像时只需要提供channel(group固定为0),这里的channel不再是所要搜索录像的摄像头的设备id,而是在录像存储设备中(cvr、nvr或dvr),存储对应摄像头录像的通道的id。此时channel格式虽然也为"\hkdvr=%d\enc=%d",但hkdvr的值是录像存储设备的设备id,enc的值则标识了设备下对应的通道id。
void CStreamSearchMode::OnQueryVideo(IEventData* pData) { std::set<LONG>::iterator it; SearchParams* params = dynamic_cast<SearchParams*>(pData); if(pData == NULL) { return; } LONG id = params->id; //创建搜索消息结构体 StreamSearchMode::StreamSearchData srv_data; //保存搜索条件 StreamSearchMode::TVideoSearchResult result; result.id = id; //组件 IConnectModel *pInstance = GetInlPtr<IConnectModel*>(_T("ConnectModel")); if(pInstance == NULL) { /*错误处理*/ } SDPHANDLE connection =pInstance->GetHandle(); //typedef void* SDPHANDLE; if(connection == NULL) { /*错误处理*/ } //AVR Manager Instance for(std::vector<StreamSearchMode::TVideoSearchCondition>::iterator it_condition = params->m_queryFactor.begin(); it_condition != params->m_queryFactor.end(); it_condition++) { if(it_condition->place == ERecordPlace_equipment) { //设备端录像文件查询 MitObjInfo info; IMitModel* mitModel = GetInlPtr<IMitModel*>(_T("MitModel")); if (mitModel == NULL) { /*错误处理*/ } bool bSuc = false; //是否成功 bool bFail = false; //是否失败 StreamSearchMode::TVideoSearchResult result; result.id = params->id; //设备id result.condition = *it_condition; //搜索条件 //搜索录像文件 std::vector<std::string>::iterator itor = it_condition->choid.begin(); for (; itor != it_condition->choid.end(); itor++) { /*检查退出标志位*/ SDPSEQUENCE<TRecordFile> files; //SDPSEQUENCE为模板类 files.construct(tid_TRecordFile); //tid_TRecordFile为事件ID //判断父结点类型 CString strParent; mitModel->GetMitParent(Type_cast<CString>(*itor), strParent); mitModel->GetMitObjInfo(strParent, info); //搜索条件填入json Json::Value param,resp; param["channel"] = (*itor).c_str(); param["group"] = 0; param["type"] = it_condition->type; TSDPLocalTime localTime; CString str; sdpGetLocalTime(it_condition->begin, localTime); str.Format(_T("%04d-%02d-%02d %02d:%02d:%02d"), localTime.year, localTime.month+1, localTime.day, localTime.hour, localTime.min, localTime.sec); param["begin"] = std::string(str).c_str(); sdpGetLocalTime(it_condition->end, localTime); str.Format(_T("%04d-%02d-%02d %02d:%02d:%02d"), localTime.year, localTime.month+1, localTime.day, localTime.hour, localTime.min, localTime.sec); param["end"] = std::string(str).c_str(); LONG nRet = instanceRMIManager()->rest_get(connection,"/Channels/queryRecordDevice",param,resp); //调用接口 if (nRet != errNone) { /*错误处理*/ } bSuc = true; std::string source; //源oid IMitModel *pModel = GetInlPtr<IMitModel*>(_T("MitModel")); if (pModel) { CString poid; pModel->GetMitParent(Type_cast<CString>((*itor)), poid); source = Type_cast<std::string>(poid); } StreamSearchMode::TChannelVideoRecord vr; vr.source = *itor; //处理搜索结果 int size = resp.size(); for (ULONG j = 0; j < size; j++) { if (resp[j]["begin"].asDouble() >= resp[j]["end"].asDouble()) { continue; } StreamSearchMode::TRecordFileEx record; record.begin = resp[j]["begin"].asDouble(); record.end = resp[j]["end"].asDouble(); record.size = resp[j]["size"].asDouble(); record.source = source; std::string tmp(resp[j]["name"].asCString()); record.name = tmp; int size_1 = resp[j]["flags"].size(); for (ULONG m = 0; m < size_1; m++) { StreamSearchMode::TRecordFlags flags; flags.time = resp[j]["flags"][m]["time"].asDouble(); flags.flags = resp[j]["flags"][m]["data"].asUInt(); if(flags.flags && (info.kind == HK_DVRKIND || info.kind == DH_DVRKIND)) { flags.begTime = resp[j]["flags"][m]["beginTime"].asDouble(); flags.endTime = resp[j]["flags"][m]["endTime"].asDouble(); flags.fileName = resp[j]["flags"][m]["filename"].asCString(); flags.fileSize = resp[j]["flags"][m]["filesize"].asUInt(); } //辨别预录和延时录像两种类型,预录类型替换成下一片段的类型,如果没有下一片段,替换成定时录像类型 //延时录像类型替换成上一片段的类型,如果没有上一片段,替换成定时录像类型 if (flags.flags & 1) { if (m+1 < size_1) flags.flags |= resp[j]["flags"][m+1]["data"].asUInt(); else { flags.flags |= 4; //延时录像类型,替换成定时录像 } } else if (flags.flags & 2) { if (m != 0) flags.flags |= resp[j]["flags"][m-1]["data"].asUInt(); else { flags.flags |= 4; //延时录像类型,替换成定时录像 } } record.flags.push_back(flags); } vr.files.push_back(record); } result.records.push_back(vr); } if ( bSuc && !bFail) result.resultFlag = StreamSearchMode::VideoSearchSucceed; else if (bSuc) result.resultFlag = StreamSearchMode::VideoSubSucceed; else result.resultFlag = StreamSearchMode::VideoSearchFailed; srv_data.result.push_back(result); } else if (it_condition->place == ERecordPlace_centerServer) { //NRU录像文件查询 bool bSuc = false; bool bFail = false; StreamSearchMode::TVideoSearchResult result; result.id = params->id; //设备id result.condition = *it_condition; //搜索条件 //搜索录像文件 std::vector<std::string>::iterator itor = it_condition->choid.begin(); std::vector<std::string>::iterator it_nru = it_condition->oidNru.begin(); for (; itor != it_condition->choid.end() && it_nru != it_condition->oidNru.end(); itor++, it_nru++) { /*检查退出标志位*/ //搜索条件填入json Json::Value param,resp; param["id"] = (*it_nru).c_str(); param["channel"] = (*itor).c_str(); param["type"] = it_condition->type; TSDPLocalTime localTime; CString str; sdpGetLocalTime(it_condition->begin, localTime); str.Format(_T("%04d-%02d-%02d %02d:%02d:%02d"), localTime.year, localTime.month+1, localTime.day, localTime.hour, localTime.min, localTime.sec); param["begin"] = std::string(str).c_str(); sdpGetLocalTime(it_condition->end, localTime); str.Format(_T("%04d-%02d-%02d %02d:%02d:%02d"), localTime.year, localTime.month+1, localTime.day, localTime.hour, localTime.min, localTime.sec); param["end"] = std::string(str).c_str(); LONG nRet = instanceRMIManager()->rest_get(connection,"Channels/queryRecordServer", param, resp); if (nRet != errNone) { /*错误处理*/ } bSuc=true; StreamSearchMode::TChannelVideoRecord vr; vr.source = *itor; //处理搜索结果 int size = resp.size(); for(ULONG j = 0; j < size; j++) { if(resp[j]["begin"].asDouble() >= resp[j]["end"].asDouble()) { continue; } StreamSearchMode::TRecordFileEx record; record.begin = resp[j]["begin"].asDouble(); record.end = resp[j]["end"].asDouble(); record.size = resp[j]["size"].asDouble(); record.source = *it_nru; std::string tmp(resp[j]["name"].asCString()); record.name = tmp; int size_1 = resp[j]["flags"].size(); for(ULONG m = 0; m < size_1; m++) { StreamSearchMode::TRecordFlags flags; flags.time = resp[j]["flags"][m]["time"].asDouble(); flags.flags = resp[j]["flags"][m]["data"].asUInt(); //辨别预录和延时录像两种类型,预录类型替换成下一片段的类型,如果没有下一片段,替换成定时录像类型 //延时录像类型替换成上一片段的类型,如果没有上一片段,替换成定时录像类型 if(flags.flags & 1) { if(m + 1 < size_1) flags.flags |= resp[j]["flags"][m+1]["data"].asUInt(); else { flags.flags |= 4; //延时录像类型,替换成定时录像 } } else if (flags.flags & 2) { if (m != 0) flags.flags |= resp[j]["flags"][m-1]["data"].asUInt(); else { flags.flags |= 4; //延时录像类型,替换成定时录像 } } record.flags.push_back(flags); } vr.files.push_back(record); } result.records.push_back(vr); } if (bSuc && !bFail) result.resultFlag = StreamSearchMode::VideoSearchSucceed; else if(bSuc) result.resultFlag = StreamSearchMode::VideoSubSucceed; else result.resultFlag = StreamSearchMode::VideoSearchFailed; srv_data.result.push_back(result); } else { //录像查询,录像位置异常 } } /*检查退出标志位*/ //调用接口搜索录像完毕,发送通知 Notify(EVT_STREAMSEARCHMODE_RESULT,&srv_data); /*清理相关标志*/ }
TVideoSearchResult为保存搜索结果的结构体
struct TVideoSearchResult { LONG id; //搜索启动成功返回的标志ID TVideoSearchCondition condition; //搜索条件 EVideoSearchResult resultFlag; //搜索结果类型 std::vector<TChannelVideoRecord> records; //搜索结果 };
TChannelVideoRecord为保存通道视频记录的结构体
struct TChannelVideoRecord { std::string source; //通道源 std::vector<TRecordFileEx> files; //搜索结果文件集 };
TRecordFileEx为记录视频文件信息的结构体
struct TRecordFileEx { std::string source; //源 std::string name; //文件名 ULONG size; //文件大小 SDPTIME begin; //起始时间 SDPTIME end; //结束时间 std::vector<TRecordFlags> flags; //录像段类型标记 };
TRecordFlags为记录视频录像段信息的结构体
struct TRecordFlags { SDPTIME time; //时间 ULONG flags; //录像类型标记(ERecordTypeFlags枚举的或值,为0表示没有录像) std::string fileName; //文件名 ULONG fileSize; //文件大小 SDPTIME begTime; //起始时间 SDPTIME endTime; //结束时间 };
MitObjInfo为记录设备节点信息的结构体
struct MitObjInfo { CString oid; //对象ID CString name; //名称(区域,设备,通道对象名称) CString fullname; //全称 long kind; //种类 long type; //种类 };
-
这波确实详细嗷
-
处理搜索结果
事件EVT_STREAMSEARCHMODE_RESULT有好几处,分别为Alarm和Map项目中的ReplayDialog,CUModel中的VideoReplayModel,VideoDownloadModel和CUBase中的SearchResultWnd。而和录像回放中的录像搜索有关的是CUModel中的VideoReplayModel和CUBase中的SearchResultWnd
CUModel/VideoReplayModel
LRESULT CVideoReplayModel::OnRefreshSearchResult(IEventData *pData) { IMitModel* pMitModel = GetInlPtr<IMitModel*>(_T("MitModel")); if(NULL == pMitModel) { return S_FALSE; } StreamSearchMode::StreamSearchData *data = dynamic_cast<StreamSearchMode::StreamSearchData*>(pData); if(data && data->result.size() != 0) { StreamSearchMode::TVideoSearchResult *result = &(*(data->result.begin())); if(result == NULL || (result->id != StreamSearchMode::eReplay && result->id != StreamSearchMode::eEMap && result->id != StreamSearchMode::eAlarm)) { //result->id用于确定搜索的请求来源 return S_FALSE; } //判断搜索成功与否 if(result->resultFlag == StreamSearchMode::VideoSubSucceed || result->resultFlag == StreamSearchMode::VideoSearchSucceed) { CloseVideoReplayAll(); //关闭当前所有回放 m_sdpBaseTime = result->condition.begin; //整理搜索结果 //清空已有搜索结果 for(std::list<ReplayItemData>::iterator it_del = m_ItemBuf.begin(); it_del != m_ItemBuf.end(); it_del++) { for(std::vector<TRecordTimeSlice>::iterator it_tmp = it_del->slices.begin(); it_tmp != it_del->slices.end(); it_tmp++) { it_tmp->flags.clear(); } it_del->slices.clear(); } m_ItemBuf.clear(); for(std::vector<VideoResult>::iterator it_del1 = m_results.begin(); it_del1 != m_results.end(); it_del1++) { for(std::list<TRecordTimeSlice>::iterator it_file = it_del1->slices.begin(); it_file != it_del1->slices.end(); it_file++) { it_file->flags.clear(); } it_del1->slices.clear(); } m_results.clear(); //通道插入将没有录像文件的通道放在后面 //可以将没有文件的通道不显示出来且不参与分屏,目前显示 int i = 1; for(std::vector<StreamSearchMode::TChannelVideoRecord>::iterator it = result->records.begin(); it != result->records.end(); it++) { if(it->files.size() == 0) { continue; } ReplayItemData item; item.oid = Type_cast<CString>((*it).source); item.nSpeed = 0; item.bEdit = false; item.nBright = 8; item.nContrast = 8; item.cur_time = 0; //保存搜索结果 VideoResult oneCh; oneCh.source = (*it).source; oneCh.slices.clear(); item.nIndexItem = i; i++; std::list<StreamSearchMode::TRecordFileEx> vTempFiles; for(std::vector<StreamSearchMode::TRecordFileEx>::iterator iter = it->files.begin(); iter != it->files.end(); iter++) { //排序插入 TSDPLocalTime testBeg; TSDPLocalTime testEnd; sdpGetLocalTime(iter->begin, testBeg); sdpGetLocalTime(iter->end, testEnd); if(iter->begin == iter->end) //录像文件长度为0 { continue; } if (vTempFiles.size()==0) { //第一个插入 vTempFiles.push_back(*iter); continue; } std::list<StreamSearchMode::TRecordFileEx>::iterator it_tmp = vTempFiles.begin(); for(; it_tmp != vTempFiles.end(); it_tmp++) { if (it_tmp->begin > iter->begin) { vTempFiles.insert(it_tmp, *iter); break; } } if (it_tmp == vTempFiles.end()) { vTempFiles.push_back(*iter); } } //合并文件时间片 std::list<StreamSearchMode::TRecordFileEx>::iterator it_cur = vTempFiles.begin(); std::list<StreamSearchMode::TRecordFileEx>::iterator it_next; while(it_cur != vTempFiles.end()) { TRecordTimeSlice slice; slice.begin = it_cur->begin; slice.end = it_cur->end; slice.flags = it_cur->flags; it_next = it_cur; while (1) { it_next++; if(it_next == vTempFiles.end()) { break; } if(it_next->begin > slice.end) { break; } else { for(std::vector<StreamSearchMode::TRecordFlags>::iterator it_flags=it_next->flags.begin(); it_flags!=it_next->flags.end(); ++it_flags) { slice.flags.push_back(*it_flags); } slice.end = it_next->end; } } oneCh.slices.push_back(slice); item.slices.push_back(slice); it_cur = it_next; } //修正时间片的最后一个片段的结束时间 ULONG slicesLength = item.slices.size(); if(slicesLength > 0) { TRecordTimeSlice& tmp_slice = item.slices.at(slicesLength-1); tmp_slice.end = tmp_slice.end > result->condition.end ? result->condition.end : tmp_slice.end; } if(oneCh.slices.size() > 0) { std::list<TRecordTimeSlice>::iterator it_oneCh = oneCh.slices.end(); it_oneCh--; it_oneCh->end = it_oneCh->end > result->condition.end ? result->condition.end : it_oneCh->end; } it_cur = vTempFiles.begin(); SDPTIME temp_begin = it_cur->begin; it_cur = vTempFiles.end(); it_cur--; SDPTIME temp_end = it_cur->end > result->condition.end?result->condition.end:it_cur->end; CHAR szPid[MITOIDLEN] = {0}; TMitObjIdKVPair kv; mitGetPid(item.oid, MITOIDLEN, szPid, &kv); IMitModel* pMitModel = GetInlPtr<IMitModel*>(_T("MitModel")); if (NULL == pMitModel) { return S_FALSE; } EncoderInfo info; if(!pMitModel->GetDevInfo(szPid,info)) { return S_FALSE; } if (result->condition.place == ERecordPlace_centerServer) { item.fileRes.Format(_T("%s\\\\replay=%s\\%d\\%I64d\\%I64d"), it_cur->source.c_str(), info.sn, kv.rid, temp_begin, temp_end); } else { item.fileRes.Format(_T("%s%s"), it_cur->source.c_str(), it_cur->name.c_str()); } item.bExistedFile = true; item.oidSrc = Type_cast<CString>(it_cur->source); oneCh.srcParent = it_cur->source; item.tm_begin = item.slices.at(0).begin; item.tm_end = item.slices.at(0).end; if (item.tm_begin < m_sdpBaseTime) { item.jumpPos = (m_sdpBaseTime - item.tm_begin)/1000; // 从零点开始播放 } item.streamId = -1; item.status = eReplayStatusStop; item.sliceIndex = 0; item.bPlaying = false; std::list<TRecordTimeSlice>::iterator tmp = oneCh.slices.begin(); while (tmp != oneCh.slices.end()) { //搜索录像文件结果展示,升序排列 TSDPLocalTime begin,end; sdpGetLocalTime((*tmp).begin, begin); sdpGetLocalTime((*tmp).end, end); CString str; str.Format(_T("%2d-%02d-%02d %02d:%02d:%02d --> %2d-%02d-%02d %02d:%02d:%02d "), begin.year, begin.month, begin.day, begin.hour, begin.min, begin.sec, end.year, end.month, end.day, end.hour, end.min, end.sec); tmp++; } m_ItemBuf.push_back(item); oneCh.nIndex=0; // 有可能本通道并没有文件,但此索引不影响使用 m_results.push_back(oneCh); } //插入空白通道 for (std::vector<StreamSearchMode::TChannelVideoRecord>::iterator it = result->records.begin(); it!=result->records.end(); it++) { if (it->files.size()!=0) continue; ReplayItemData item; item.oid = Type_cast<CString>((*it).source); item.bExistedFile = false; item.nIndexItem = i; item.bEdit=false; i++; item.status = eReplayStatusStop; //重新设置标识 item.streamId = -1; item.bSound = FALSE; item.tm_begin=0; item.tm_end = 0; item.bValid = false; item.bPlaying = false; m_ItemBuf.push_back(item); //没有录像文件的通道尾部插入 //保存搜索结果 VideoResult oneCh; oneCh.source = (*it).source; oneCh.slices.clear(); oneCh.nIndex = 0; //有可能本通道并没有文件,但此索引不影响使用 m_results.push_back(oneCh); } InitScreenSpace(m_ItemBuf.size()); //初始化分屏 } } Notify(EVT_QUERY_FINISH, pData); return S_OK; }
ReplayItemData为保存缓存回放数据的结构体
struct ReplayItemData { bool bValid; //用于移除和添加指定通道 bool bExistedFile; //该通道是否存在录像文件 CString oid; //通道oid CString oidSrc; //通道oid的上一级,主要是针对nru回放,如果是非nru,则为通道所属的设备 CString fileRes; //用于启动回放的文件标识符 SDPTIME tm_begin; //文件开始时间,以ms计算 SDPTIME tm_end; //文件结束时间,以ms计算 SDPTIME cur_time; //异步回放需要的参数, int sliceIndex; std::vector<TRecordTimeSlice> slices; bool bEdit; //是否剪辑该通道 bool bSound; //该窗口视频是否开启音频 int nBright; //亮度 int nContrast; //对比度 SDPTIME jumpPos; int nSpeed; //取值范围[-4,+4] UINT nIndexItem; //回放窗口索引位 LONG streamId; //流id EReplayStreamStatus status; bool bLocalZoom; //是否处在本地放大状态 bool bPlaying; };
TRecordTimeSlice为记录录像时间段的结构体
struct TRecordTimeSlice { SDPTIME begin; //起始时间 SDPTIME end; //结束时间 std::vector<StreamSearchMode::TRecordFlags> flags; //录像段类型标记 };
TRecordFlags为记录视频文件录像段类型标记的结构体
struct TRecordFlags { SDPTIME time; //时间 ULONG flags; //录像类型标记(ERecordTypeFlags枚举的或值,为0表示没有录像) std::string fileName; ULONG fileSize; SDPTIME begTime; SDPTIME endTime; };
VideoResult为保存通道视频记录的结构体
struct VideoResult { std::string source; //通道源 std::string srcParent; //上一级oid,nru则为纳入源,设备为设备oid int nIndex; //当前播放文件索引,从0开始 std::list<TRecordTimeSlice> slices; //搜索结果时间片列表 };
事件EVT_QUERY_FINISH的响应函数位于项目Replay中的SearchPane
void CSearchPane::OnQueryFinished(IEventData *data) { StreamSearchMode::StreamSearchData *evtData = dynamic_cast<StreamSearchMode::StreamSearchData*>(data); if (evtData && evtData->result.size() != 0) { StreamSearchMode::TVideoSearchResult *result = &(*(evtData->result.begin())); if(result == NULL || result->id != (LONG)StreamSearchMode::eReplay) { return; } ::SendMessage(m_tipsDlg->m_hWnd, QUERYFINISHED, 0,0); //关闭提示框 bool bNull = true; CWnd * videoWnd = NULL; ::SendMessage(CReplayUI::replayUI->GetSafeHwnd(), WM_GET_VIDEOWND, WPARAM(&videoWnd), 0); //获取回放面板的句柄 CString str, strTips; GetModuleCString(strTips, IDS_TIPS); if (result->resultFlag==StreamSearchMode::VideoSearchSucceed || result->resultFlag==StreamSearchMode::VideoSubSucceed) { for (std::vector<StreamSearchMode::TChannelVideoRecord>::iterator it = result->records.begin(); it != result->records.end(); it++) { if ((*it).files.size()!=0) { bNull=false; break; } } if (bNull) { GetModuleCString(str, IDS_SEARCH_NO_VIDEO); CenterMessageBox(videoWnd, str, strTips, MB_OK|MB_ICONINFORMATION); } } else { GetModuleCString(str, IDS_SEARCH_FAILED); CenterMessageBox(videoWnd, str, strTips, MB_OK|MB_ICONERROR); } } }
CUBase/SearchResultWnd
LRESULT CSearchResultWnd::OnStreamSearchResult(IEventData *pData) { StreamSearchMode::StreamSearchData *evtData = dynamic_cast<StreamSearchMode::StreamSearchData*>(pData); if(evtData && evtData->result.size() != 0) { StreamSearchMode::TVideoSearchResult *result = &(*(evtData->result.begin())); if(result == NULL || result->id != (LONG)m_id) { return S_FALSE; } bool bSuc = false; // true包含半成功 for(std::vector<StreamSearchMode::TVideoSearchResult>::iterator it=evtData->result.begin(); it != evtData->result.end(); it++) { if(it->resultFlag == StreamSearchMode::VideoSearchSucceed || it->resultFlag == StreamSearchMode::VideoSubSucceed) { bSuc = true; break; } } if (bSuc) { //全部通道搜索成功,或者部分通道搜索成功 //刷新结果显示区域 m_table_result.RefreshVideoSearchResult(evtData->result); // 将筛选标志置为所有 m_pict_status = 0xFFFFFFFF; } } return S_OK; }
调用RefreshVideoSearchResult转换搜索结果
void CVideoSearchResultTable::RefreshVideoSearchResult(std::vector<StreamSearchMode::TVideoSearchResult> ¶ms) { if(params.size() == 0) return; int i; for(i = 0;i < MAX_NUM_REPLAY_SUP; i++) { m_SearchResult[i].bValid = false; m_SearchResult[i].files.clear(); m_SearchResult[i].ch_oid = ""; m_SearchResult[i].nru_oid = ""; } //刷新结果时初始化显示全天 m_dLeftTime = 0; m_nDegreeResize = 0; //当天的起始时间SDPTIME值 TSDPLocalTime tmpTime; sdpGetLocalTime(params.begin()->condition.begin,tmpTime); //搜索条件里面的开始时间不一定是一天的凌晨 tmpTime.hour = 0; tmpTime.min = 0; tmpTime.sec = 0; tmpTime.ms = 0; sdpMakeLocalTime(m_sdptime_base, tmpTime); TSDPLocalTime time; sdpGetLocalTime(m_sdptime_base, time); INT searchDay = time.day; i = 0; m_nValidChannel=0; std::vector<StreamSearchMode::TVideoSearchResult>::iterator it_result = params.begin(); for(; it_result != params.end(); it_result++) { std::vector<StreamSearchMode::TChannelVideoRecord>::iterator it = it_result->records.begin(); for(;it != it_result->records.end(); it++) {// 先填有录像的通道 if ((*it).files.size()==0) continue; m_nValidChannel++; m_SearchResult[i].bValid=true; m_SearchResult[i].nSearchDay=searchDay; m_SearchResult[i].ch_oid=(*it).source; if (it_result->condition.oidNru.size() != 0) { //nru录像 std::vector<std::string>::iterator it_nruoid = it_result->condition.oidNru.begin(); for (std::vector<std::string>::iterator it_oid = it_result->condition.choid.begin(); it_oid != it_result->condition.choid.end(); it_oid++,it_nruoid++) { if (m_SearchResult[i].ch_oid.compare(*it_oid) == 0) { m_SearchResult[i].nru_oid = *it_nruoid; break; } } } for (std::vector<StreamSearchMode::TRecordFileEx>::iterator it_Rec = (*it).files.begin(); it_Rec != (*it).files.end(); it_Rec++) { m_SearchResult[i].files.push_back(*it_Rec); } i++; } } it_result = params.begin(); for (; it_result != params.end(); it_result++) { std::vector<StreamSearchMode::TChannelVideoRecord>::iterator it = it_result->records.begin(); for (;it!=it_result->records.end(); it++) { //将没有录像的通道殿后 if ((*it).files.size()!=0) continue; m_nValidChannel++; m_SearchResult[i].bValid=true; m_SearchResult[i].nSearchDay=searchDay; m_SearchResult[i].ch_oid=(*it).source; if (it_result->condition.oidNru.size() != 0) { //nru录像 std::vector<std::string>::iterator it_nruoid = it_result->condition.oidNru.begin(); for (std::vector<std::string>::iterator it_oid = it_result->condition.choid.begin(); it_oid != it_result->condition.choid.end(); it_oid++,it_nruoid++) { if (m_SearchResult[i].ch_oid.compare(*it_oid)==0) { m_SearchResult[i].nru_oid = *it_nruoid; break; } } } i++; } } m_nBaseChannelIndex = 0; //优先使用前4个窗口 if (m_replayModel == eAsyncReplay) { //异步模式,切换当前显示的通道时,重新输入数据,中间最多存在1秒的空白,SetCurTime刷新当前时间 for (int i = 0; i < 4; i++) { m_timeCursor[i].strOid = Type_cast<CString>(m_SearchResult[i+m_nBaseChannelIndex].ch_oid); m_timeCursor[i].bVisible=false; m_timeCursor[i].curTime = 0; m_timeCursor[i].posPre = 0; m_timeCursor[i].posCur=0; } } m_status_filter = 0XFFFFFFFF; CalcDisplayRect(); CRect rect; GetClientRect(&rect); InvalidateRect(&rect); UpdateWindow(); m_btn_UpPage.EnableWindow(FALSE); // 初始显示前4路 BOOL bStatus = m_nValidChannel <= 4 ? FALSE : TRUE; ::SendMessage(GetParent()->GetSafeHwnd(), RT_DOWNBTN_STATUS, WPARAM(bStatus),0); }
-
录像播放
选中需要播放录像的通道后,单击播放按钮,响应函数如下
void CReplayPaneDlg::OnPlay() { IVideoReplayModel* pModel = GetInlPtr<IVideoReplayModel*>(_T("VideoReplayModel")); if(pModel == NULL) { return; } if(m_bPlaying) { //解除暂停 if (pModel->IsSycReplay()) { pModel->ResumeReplayAll(); } else { pModel->ResumeReplayChannel(); } m_bPause = false; } else { //播放 int ret; if (pModel->IsSycReplay()) { ret = pModel->OpenVideoReplayAll(); } else { ret = pModel->OpenVideoReplayChannel(); } if (ret == 1) { /*播放前请先勾选通道并进行录像文件的搜索*/ } else if (ret==2) { /*没有录像文件*/ } m_bPlaying = true; m_bLinked = false; m_slider_speed.SetPos(4); } RefreshBtnStatus(); //更新播放控制面板上的按钮状态 }
主要调用的是VideoReplayModel里的几个函数,先看OpenVideoReplayChannel
LONG CVideoReplayModel::OpenVideoReplayChannel() { if(!m_bAsyncReplayFlag) //异步回放 { return errError; } if(m_selectedItemIndex < 1 || m_selectedItemIndex > 4) { return errBadArg; } ReplayCmdData cmdData; cmdData.cmd = eReplayStart; m_modelLock.Acquire(); //判断是否搜索过 if(m_ItemBuf.size() == 0) { m_modelLock.Release(); return errNotExist; } for(std::list<ReplayItemData>::iterator it = m_ItemBuf.begin(); it != m_ItemBuf.end(); it++) { if(it->bExistedFile && m_selectedItemIndex == it->nIndexItem && !it->bPlaying) { cmdData.listItem.push_back(*it); it->status = eReplayStatusLinking; it->bPlaying = true; it->bValid = true; break; } } m_modelLock.Release(); if(cmdData.listItem.size()==0) { return errNotExist; } Notify(EVT_REPLAY_CMD, &cmdData); return errNone; }
事件EVT_REPLAY_CMD的响应函数位于Replay中的PlaybackManager,这里对播放命令汇总再转发出去,这里先看eReplayStart
LRESULT CPlaybackManager::OnReplayCmd(IEventData *pData) { ReplayCmdData *cmdData = dynamic_cast<ReplayCmdData*>(pData); if (cmdData) { switch (cmdData->cmd) { case eReplayStart: StartReplay(cmdData->listItem); break; case eReplayStop: StopReplay(cmdData->listItem); break; case eReplayJump: JumpReplay(cmdData->listItem); break; case eReplayPause: PauseReplay(cmdData->listItem); break; case eReplayResume: ResumeReplay(cmdData->listItem); break; case eReplayChangeSpeed: SetSpeed(cmdData->listItem, cmdData->param); break; case eReplayFast: PlayFast(cmdData->listItem); break; case eReplaySlow: PlaySlow(cmdData->listItem); break; case eReplayCapture: CaptureImage(cmdData->listItem); break; case eReplayStartEditVideo: EditVideo(cmdData->listItem, true); break; case eReplayStopEditVideo: EditVideo(cmdData->listItem, false); break; case eReplayIncreaseBright: IncreaseBright(); break; case eReplayReduceBright: ReduceBright(); break; case eReplayIncreaseContrast: IncreaseContrast(); break; case eReplayReduceContrast: ReduceContrast(); break; default: break; } } return S_OK; }
调用StartReplay
int CPlaybackManager::StartReplay(std::list<ReplayItemData> &listItem) { IVideoReplayControl* replayControl = GetInlPtr<IVideoReplayControl*>(_T("VideoReplayControl")); if (replayControl==NULL) { return 1; } IVideoReplayModel* pModel = GetInlPtr<IVideoReplayModel*>(_T("VideoReplayModel")); if (pModel==NULL) { return 1; } IMitModel * pMitObj = GetInlPtr<IMitModel*>(_T("MitModel")); if (pMitObj==NULL) { return 1; } { std::list<ReplayItemData>::iterator it = listItem.begin(); for (; it != listItem.end(); it++) { CPlaybacker *wnd = dynamic_cast<CPlaybacker*>(GetItem(it->nIndexItem)); HWND hWnd = wnd->GetPlayWnd(); if (hWnd) { LONG streamId; LONG ret; ret = replayControl->Play(it->fileRes, it->oid, ULONG((it->tm_end-it->tm_begin)/1000), hWnd, NULL, it->tm_begin+it->jumpPos*1000, streamId, DrawZoomInFun, (LONG)wnd); if (ret == errNone) { pModel->SetReplayStreamID(it->nIndexItem, streamId); //设置title状态 wnd->SetStreamStatus(eReplayStatusLinking, streamId); } else { wnd->SetStreamStatus(eReplayStatusMaxLimit,-1); } } } } return 0; }
主要看调用的play函数
LONG CVideoReplayControl::Play(CString szOid, CString szEncOid, ULONG nTotalTime,HWND hWnd, void* pParam, SDPTIME jumpPos, LONG &id, DrawFunEx func, LONG drawData) { IAVStream *pAVStream = GetInlPtr<IAVStream*>(_T("AVStream")); if (pAVStream==NULL) { return errNotExist; } //note未知修改,但是回放、电子地图有可能同时回放同一段录像文件 //先判断是否重复,依据窗口句柄 m_lockDataList.Acquire(); std::list<ReplayInf>::iterator it = m_listReplayInf.begin(); for (; it != m_listReplayInf.end(); it++) { if (it->hWnd == hWnd) { DEBUGINFO(DEBUG_WARNING, _T("warning -- 在同一窗口中重复开启录像文件"), szOid); m_lockDataList.Release(); return 1; } } m_lockDataList.Release(); ULONG maxwidth = 0; ULONG maxheight = 0; if(pAVStream->Connect(szOid, ENEAVProtocol_all,ReplayStream,CB_RecvStream,hWnd,false,id,NULL,maxwidth,maxheight) == true) { ReplayInf rp; rp.id = id; rp.strOid = szOid; rp.nBright = 8; rp.nContrast = 8; rp.nSpeed = 0; rp.hWnd = hWnd; rp.nStatus = 0; rp.bSound = FALSE; rp.bFull = FALSE; rp.nTm = jumpPos; rp.nTotalTime=nTotalTime; rp.bLink = FALSE; rp.strEncOid = szEncOid; rp.nNoDataCome = 0; rp.width=maxwidth; rp.height=maxheight; rp.drawFunc=func; rp.drawData=drawData; addReplayInf(rp); ReplayDataInf rpDataInf; rpDataInf.id = id; rpDataInf.nPacket = 0; rpDataInf.bPlay = TRUE; rpDataInf.pData = NULL; rpDataInf.bCheck = FALSE; rpDataInf.bCanInputData = TRUE; rpDataInf.bFirst = false; rpDataInf.fEdit=NULL; addReplayDataInf(rpDataInf); m_mapAvStreamStatus[id] = 1; return errNone; } return errError; }
在后面的处理都位于AVStream中,在此不再深究。