首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# 視訊監控系列(九):伺服器端資料捕獲——截圖與錄影實作(二)

     編輯:關於C語言

VC++ 官方範例深度解析:錄影完整流程

本篇對照官方 VC++ 範例 CHKVisionDlg,完整呈現從「開始錄影」到「停止錄影」的底層邏輯,為 C# 移植提供權威參考。

1. 開始錄影 OnStart() 核心流程

for(int i = 0; i < GetTotalDSPs(); i++){
    if(m_bDspPreset[i]){
        // 產生檔名:d:\stream[通道號]_[序號].264
        sprintf(fileName, "d:\\stream%d_%d.264", i, gFileNum++/GetTotalDSPs());
        
        // 開啟檔案:二進位、寫入、截斷
        gFileHandle[i] = _open(fileName, _O_CREAT | _O_BINARY | _O_WRONLY | _O_TRUNC, _S_IREAD | _S_IWRITE);
        
        // 重設統計變數
        gChannelFrames[i] = 0;
        gChannelTotalLength[i] = 0;
        gChannelFramesLost[i] = 0;
        gChannelOverflow[i] = 0;
        gCurrentFileLen[i] = 0;
        
        // 關鍵:寫入檔頭
        _write(gFileHandle[i], FileHeader[i], FileHeaderLen);
        gCaptureStartedNum++;
    }
}

2. 核心回調 StreamDirectReadCallback 完整邏輯

int __cdecl StreamDirectReadCallback(ULONG channelNum, void *DataBuf, DWord Length, int frameType, void *context)
{
    int status = 0;
    
    if(frameType > 0) {
        // 捕獲系統檔頭
        if(frameType == PktSysHeader){
            memcpy(FileHeader[channelNum], DataBuf, Length);
            FileHeaderLen = Length;
            TRACE("channel %d get the file header !\n", channelNum);
        }
        
        // I 幀/子 I 幀標記 status=1
        if(frameType == PktIFrames || frameType == PktSubIFrames)
            status = 1;
        else
            status = 0;
            
        // 移動偵測/原始圖像直接返回,不寫檔
        if(frameType == PktMotionDetection || frameType == PktOrigImage)
            return 0;
    }
    
    if(Length == 0) return 0;
    
    // 統計頻寬資訊(每秒更新一次)
    gChannelTotalLength[channelNum] += Length;
    gCurrentFileLen[channelNum] += Length;
    
    // 依影格類型分流寫入
    if(frameType == PktAudioFrames){
        _write(gFileHandleQcif[channelNum], DataBuf, Length);
        MP4_ServerWriteDataEx(channelNum, DataBuf, Length, frameType, status, 1);
        _write(gFileHandle[channelNum], DataBuf, Length);
        MP4_ServerWriteDataEx(channelNum, DataBuf, Length, frameType, status, 0);
    }
    else if(frameType == PktSubIFrames || frameType == PktSubPFrames || 
            frameType == PktSubBBPFrames || frameType == PktSubSysHeader){
        _write(gFileHandleQcif[channelNum], DataBuf, Length);
        MP4_ServerWriteDataEx(channelNum, DataBuf, Length, frameType, status, 1);
    }
    else{
        MP4_ServerWriteDataEx(channelNum, DataBuf, Length, frameType, status, 0);
    }
    return 0;
}

3. 停止錄影 OnStop() 關鍵步驟

for(int i = 0; i < GetTotalDSPs(); i++){
    if(m_bDspPreset[i]){
        ASSERT(gFileHandle[i] != -1);
        
        // 寫入檔尾結束碼 0x00000002
        #define END_CODE 0x00000002
        ULONG endCode = END_CODE;
        _write(gFileHandle[i], &endCode, sizeof(ULONG));
        _close(gFileHandle[i]);
        
        // 關閉子碼流檔案
        if(bEncodeCifAndQcif[i])
            _close(gFileHandleQcif[i]);
            
        gCaptureStartedNum--;
    }
}

移植至 C# 的關鍵對照表

VC++ 概念C# 對應實作注意事項
_open/_write/_closeFileStream + BinaryWriter使用 using 確保資源釋放
gFileHandle[] 指標陣列Dictionary<int, FileStream> 或陣列多通道需獨立檔案流
memcpy 檔頭複製Marshal.Copy(IntPtr, byte[], int, int)非托管→托管記憶體複製
PktSysHeader 單次觸發回調中判斷並暫存 FileHeader需宣告為類別層級欄位
結束碼 0x00000002uint endCode = 0x00000002; bw.Write(endCode);標準 H.264 檔案結束標記
頻寬統計 timeGetTime()Environment.TickCount 或 StopwatchUI 更新需 Invoke 跨執行緒

實作重點提醒

  • 檔頭變數型別:VC++ 用陣列 FileHeader[channelNum],C# 移植時建議用 byte[][] 或 List<byte[]> 對應多通道
  • 子碼流處理:gFileHandleQcif 對應 CIF/QCIF 子碼流,若啟用雙碼流需同步管理
  • MP4 封裝介面:MP4_ServerWriteDataEx 最後一參數 0/1 區分主/子碼流,C# 需正確 P/Invoke 簽名
  • 執行緒安全:回調在 SDK 內部執行緒觸發,寫檔操作建議用佇列 + 專用寫入執行緒,避免阻塞回調
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved