本篇對照官方 VC++ 範例 CHKVisionDlg,完整呈現從「開始錄影」到「停止錄影」的底層邏輯,為 C# 移植提供權威參考。
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++;
}
}
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;
}
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--;
}
}
| VC++ 概念 | C# 對應實作 | 注意事項 |
|---|---|---|
_open/_write/_close | FileStream + BinaryWriter | 使用 using 確保資源釋放 |
gFileHandle[] 指標陣列 | Dictionary<int, FileStream> 或陣列 | 多通道需獨立檔案流 |
memcpy 檔頭複製 | Marshal.Copy(IntPtr, byte[], int, int) | 非托管→托管記憶體複製 |
PktSysHeader 單次觸發 | 回調中判斷並暫存 FileHeader | 需宣告為類別層級欄位 |
結束碼 0x00000002 | uint endCode = 0x00000002; bw.Write(endCode); | 標準 H.264 檔案結束標記 |
頻寬統計 timeGetTime() | Environment.TickCount 或 Stopwatch | UI 更新需 Invoke 跨執行緒 |
FileHeader[channelNum],C# 移植時建議用 byte[][] 或 List<byte[]> 對應多通道gFileHandleQcif 對應 CIF/QCIF 子碼流,若啟用雙碼流需同步管理MP4_ServerWriteDataEx 最後一參數 0/1 區分主/子碼流,C# 需正確 P/Invoke 簽名