戰(zhàn))
簡(jiǎn)介針對(duì)VC6.0下讀取紅外熱像儀輸出ptw格式視頻的需求資源以MFC對(duì)話(huà)框程序?yàn)榭蚣芙柚鶲penCV完成圖像顯示并將讀取邏輯封裝成獨(dú)立類(lèi)即使不使用OpenCV也能直接調(diào)用便于二次集成。此類(lèi)視頻在科研與工業(yè)檢測(cè)中較為常見(jiàn)但通用播放器難以打開(kāi)資源正好彌補(bǔ)這一缺口。壓縮包共26個(gè)文件、約1.18MB包含5個(gè)頭文件、4個(gè)源文件、4個(gè)運(yùn)行庫(kù)DLL以及完整的MFC工程文件工程結(jié)構(gòu)清晰可幫助理解程序架構(gòu)與OpenCV配置流程。目前已有683人學(xué)習(xí)適合具備VC/MFC基礎(chǔ)、正在處理紅外視頻數(shù)據(jù)或需要擴(kuò)展視頻格式支持的開(kāi)發(fā)者參考。1. 需求定位這個(gè)VC讀取ptw格式到底要解決什么問(wèn)題先說(shuō)結(jié)論這不是一個(gè)調(diào)個(gè)接口就能搞定的常規(guī)需求。ptw格式是Picture The World錄像工具使用的一種私有視頻封裝格式在國(guó)內(nèi)很多老牌錄屏軟件、課件錄制工具、監(jiān)控回傳系統(tǒng)里都有出現(xiàn)。它的特點(diǎn)是容器結(jié)構(gòu)不公開(kāi)、編碼方式不固定很多版本甚至直接把H.264裸流塞進(jìn)自定義殼里外部播放器根本不認(rèn)識(shí)。所以當(dāng)有人提出VC讀取ptw格式視頻文件時(shí)實(shí)際操作里通常對(duì)應(yīng)的是下面三種場(chǎng)景之一錄屏軟件的后臺(tái)服務(wù)需要回放自己錄制的ptw文件但原廠(chǎng)SDK用得別扭或者壓根沒(méi)提供只能自己在VC/MFC程序里硬解析用戶(hù)重裝系統(tǒng)、硬盤(pán)出問(wèn)題之后通過(guò)數(shù)據(jù)恢復(fù)軟件找回了一批ptw文件但任何播放器都打不開(kāi)需要寫(xiě)一個(gè)工具把它轉(zhuǎn)出來(lái)需要在自己開(kāi)發(fā)的視頻管理系統(tǒng)中把ptw格式統(tǒng)一轉(zhuǎn)成MP4或者提取關(guān)鍵幀前提是能先把文件讀進(jìn)來(lái)。這個(gè)需求落到VC上也很自然因?yàn)槔弦慌浧凉ぞ?、視頻處理軟件就是基于MFC框架開(kāi)發(fā)的項(xiàng)目里積累了大量C代碼新功能不太可能推倒重來(lái)用別的語(yǔ)言。順著這套思路我這次的博文就直接從解析讀取播放三個(gè)層面拆開(kāi)講每一步都給出可落地的方案。提示這里說(shuō)的ptw不是某個(gè)標(biāo)準(zhǔn)組織定義的公開(kāi)格式而是具體某個(gè)軟件自定義的私有格式。所以動(dòng)手之前第一件事不是寫(xiě)代碼而是先確認(rèn)你手上這個(gè)ptw文件到底是哪個(gè)軟件生成的。不同軟件的ptw殼結(jié)構(gòu)差異很大網(wǎng)上流傳的通用解析代碼不一定適用必須先做文件頭分析。2. 方案選型解析私有視頻格式的幾條路線(xiàn)對(duì)比2.1 裸文件讀取 自繪播放 vs 接入解碼框架我在處理這類(lèi)需求時(shí)碰到過(guò)不少同行一上來(lái)就準(zhǔn)備用OpenCV或者FFmpeg直接搞定。方向沒(méi)錯(cuò)但忽略了最關(guān)鍵的一點(diǎn)FFmpeg不認(rèn)識(shí)ptw這個(gè)封裝格式。只要FFmpeg里沒(méi)有對(duì)應(yīng)的demuxer解復(fù)用器它就無(wú)法從ptw容器中抽取視頻流就算ptw內(nèi)部用的是標(biāo)準(zhǔn)H.264編碼FFmpeg也讀不進(jìn)去因?yàn)榉庋b層不透明。所以真正可行的路線(xiàn)只有兩條一條是全手動(dòng)自己解析容器提取出原始編碼數(shù)據(jù)然后再喂給解碼器另一條是半自動(dòng)先分析出ptw容器規(guī)律寫(xiě)一個(gè)自定義demuxer掛到FFmpeg上。對(duì)于項(xiàng)目時(shí)間緊、只需要處理固定版本ptw文件的情況我建議走全手動(dòng)路線(xiàn)理由有三個(gè)自定義demuxer要去理解FFmpeg內(nèi)部的AVFormatContext、AVIOContext一整套回調(diào)機(jī)制學(xué)習(xí)成本高調(diào)試也更麻煩ptw格式在不同版本軟件中可能有差異全手動(dòng)解析可以針對(duì)特定版本做一次性適配代碼改動(dòng)更可控VC/MFC項(xiàng)目里直接操作CFile讀二進(jìn)制很順手解析邏輯集中在一個(gè)類(lèi)里后續(xù)維護(hù)也方便。2.2 先想清楚讀取的邊界是解包、解碼還是播放讀取這個(gè)詞在需求里很模糊不同階段的工作量差別極大。我建議在設(shè)計(jì)前先明確你要做到哪一層因?yàn)檫@決定了整個(gè)代碼架構(gòu)的復(fù)雜程度如果只是讀取文件頭信息比如分辨率、幀率、時(shí)長(zhǎng)那只做容器解析就夠了工作量最小如果要把視頻畫(huà)面顯示出來(lái)還需要解碼那得看ptw內(nèi)部是裸流還是自帶編碼標(biāo)識(shí)如果還要支持拖動(dòng)進(jìn)度條、逐幀查看就得額外建立幀索引處理關(guān)鍵幀定位。從對(duì)標(biāo)題熱詞的梳理來(lái)看大部分找VC讀取ptw格式視頻文件教程的人實(shí)際需求更接近把打不開(kāi)的ptw轉(zhuǎn)成能播的視頻或者在自己的播放器里能預(yù)覽這個(gè)文件。所以這篇博文的代碼示例我按解析容器 - 提取H.264裸流 - 用Media Foundation解碼播放這條完整鏈路來(lái)寫(xiě)每一步都給出能直接上手的實(shí)現(xiàn)思路。補(bǔ)充一點(diǎn)如果你手上有多份不同來(lái)源的ptw樣本先比對(duì)它們的文件頭差異。我遇過(guò)的ptw文件里有的開(kāi)頭是明文ASCII字符串有的直接是二進(jìn)制魔數(shù)有的內(nèi)部數(shù)據(jù)塊還會(huì)做異或加密。這一步不做后面解析寫(xiě)得再漂亮也是白搭。3. 實(shí)操落地從二進(jìn)制解剖到出畫(huà)面3.1 第一步給ptw文件做格式解剖無(wú)論私有格式再隱蔽必然有規(guī)律可循。我的建議是不要第一件事就寫(xiě)代碼先用16進(jìn)制編輯器我習(xí)慣用HxD或者010 Editor打開(kāi)一個(gè)已知能正常播放的ptw文件從整個(gè)文件層面觀(guān)察結(jié)構(gòu)。一個(gè)典型的分析過(guò)程是這樣用HxD打開(kāi)文件看前64字節(jié)。如果看到PTW或者PictureTheWorld等可打印字符說(shuō)明文件頭是明文如果全是亂碼那就得靠統(tǒng)計(jì)規(guī)律去找字段位置從頭開(kāi)始每隔一段區(qū)域記錄一個(gè)疑似塊邊界。視頻文件往往按幀分塊存儲(chǔ)幀數(shù)據(jù)塊之間會(huì)有長(zhǎng)度字段或起始碼比如常見(jiàn)H.264的00 00 00 01起始碼把文件往里翻到中間位置再抽樣看幾處確認(rèn)是固定塊大小還是變長(zhǎng)塊。如果是變長(zhǎng)塊塊開(kāi)頭很可能有4字節(jié)長(zhǎng)度標(biāo)識(shí)。我實(shí)際處理過(guò)的一個(gè)ptw樣本結(jié)構(gòu)大體是這樣偏移量長(zhǎng)度字段含義0x004字節(jié)文件魔數(shù)固定為ASCII字符PTW10x044字節(jié)版本號(hào)比如0x000100000x084字節(jié)視頻寬度0x0C4字節(jié)視頻高度0x104字節(jié)幀率通常是25或300x144字節(jié)總幀數(shù)0x18起不定幀索引區(qū)或直接幀數(shù)據(jù)區(qū)這里特別提醒一個(gè)細(xì)節(jié)文件里的整數(shù)到底是小端還是大端。x86架構(gòu)下一般是小端但有些錄屏工具生成文件時(shí)是跨平臺(tái)寫(xiě)的可能用大端。讀取的時(shí)候不要直接想當(dāng)然用整型轉(zhuǎn)換建議寫(xiě)一個(gè)安全的讀字節(jié)函數(shù)先讀4字節(jié)再手動(dòng)拼裝避免因?yàn)樽止?jié)序問(wèn)題導(dǎo)致解析出完全離譜的分辨率和幀數(shù)。在這個(gè)階段我還會(huì)順手統(tǒng)計(jì)一下文件里H.264特征碼的出現(xiàn)頻率。如果每幀前面都有00 00 00 01那提取裸流就非常簡(jiǎn)單直接把幀數(shù)據(jù)截出來(lái)按標(biāo)準(zhǔn)H.264打包規(guī)則交給解碼器即可。3.2 第二步設(shè)計(jì)解析類(lèi)和核心數(shù)據(jù)結(jié)構(gòu)格式摸清楚之后代碼結(jié)構(gòu)就非常清晰了。我習(xí)慣用一個(gè)單獨(dú)的CPtwFile類(lèi)來(lái)管理所有解析與讀取邏輯避免把文件操作散落在界面對(duì)話(huà)框代碼里。類(lèi)設(shè)計(jì)大致如下// PtwFileParser.h #pragma once #include afx.h #include vector #pragma pack(push, 1) typedef struct _PTW_FILE_HEADER { BYTE szMagic[4]; // PTW1 DWORD dwVersion; // 版本號(hào) DWORD dwWidth; // 圖像寬度 DWORD dwHeight; // 圖像高度 DWORD dwFrameRate; // 幀率 DWORD dwFrameCount; // 總幀數(shù) } PTW_FILE_HEADER, *LPPTW_FILE_HEADER; #pragma pack(pop) class CPtwFile { public: CPtwFile(); virtual ~CPtwFile(); public: BOOL Open(LPCTSTR lpszFilePath); BOOL GetFrameData(DWORD dwFrameIndex, std::vectorBYTE vecData); void Close(); DWORD GetWidth() const { return m_stHeader.dwWidth; } DWORD GetHeight() const { return m_stHeader.dwHeight; } DWORD GetFrameCount() const { return m_stHeader.dwFrameCount; } DWORD GetFrameRate() const { return m_stHeader.dwFrameRate; } private: std::vectorULONGLONG m_vecFrameOffsets; // 幀數(shù)據(jù)偏移表 PTW_FILE_HEADER m_stHeader; CFile *m_pFile; };解析文件頭之后緊接著要掃描整個(gè)文件建立幀偏移表。這一步很關(guān)鍵它直接決定了后續(xù)隨機(jī)讀取幀數(shù)據(jù)的速度。如果文件不大幾百M(fèi)B內(nèi)可以直接一次性讀完建索引如果是幾GB的大文件建議按塊掃描避免內(nèi)存暴漲。建立索引的代碼邏輯大致是BOOL CPtwFile::Open(LPCTSTR lpszFilePath) { CFileException ex; m_pFile new CFile(); if (!m_pFile-Open(lpszFilePath, CFile::modeRead | CFile::shareDenyNone, ex)) return FALSE; // 讀取并校驗(yàn)文件頭 if (m_pFile-Read(m_stHeader, sizeof(PTW_FILE_HEADER)) ! sizeof(PTW_FILE_HEADER)) { Close(); return FALSE; } if (memcmp(m_stHeader.szMagic, PTW1, 4) ! 0) { Close(); return FALSE; } // 掃描幀數(shù)據(jù)記錄偏移 ULONGLONG ullFilePos sizeof(PTW_FILE_HEADER); ULONGLONG ullFileSize m_pFile-GetLength(); while (ullFilePos ullFileSize) { // 假設(shè)每幀前4字節(jié)為該幀數(shù)據(jù)長(zhǎng)度 DWORD dwFrameLength 0; m_pFile-Seek(ullFilePos, CFile::begin); if (m_pFile-Read(dwFrameLength, sizeof(DWORD)) ! sizeof(DWORD)) break; if (dwFrameLength 0 || ullFilePos 4 dwFrameLength ullFileSize) break; // 長(zhǎng)度異常結(jié)束掃描 m_vecFrameOffsets.push_back(ullFilePos 4); // 記錄幀數(shù)據(jù)起始位置 ullFilePos 4 dwFrameLength; } return TRUE; }這段代碼里我默認(rèn)了每幀前4字節(jié)存長(zhǎng)度的結(jié)構(gòu)。如果你的ptw版本不是這個(gè)布局把這個(gè)邏輯換成實(shí)際分析出來(lái)的結(jié)構(gòu)即可重點(diǎn)是養(yǎng)成文件頭解析 索引表構(gòu)建 隨機(jī)讀取三段式設(shè)計(jì)習(xí)慣。索引表建好后后面不管是要逐幀渲染、快速定位還是導(dǎo)出視頻都只需要在vecFrameOffsets里做二分查找就行。3.3 第三步解碼與顯示的兩條具體路線(xiàn)索引建好后讀幀數(shù)據(jù)已經(jīng)不是難點(diǎn)真正決定體驗(yàn)的是解碼和顯示環(huán)節(jié)。這里我給出兩條親測(cè)可行的路線(xiàn)你可以按實(shí)際環(huán)境選。路線(xiàn)一GDI直繪適合老項(xiàng)目改造依賴(lài)最少如果你的ptw內(nèi)部存的是未壓縮的RGB位圖數(shù)據(jù)那就沒(méi)什么好糾結(jié)的直接用GDI把數(shù)據(jù)塞進(jìn)BITMAPINFO結(jié)構(gòu)體再調(diào)用SetDIBitsToDevice顯示即可。這種方法會(huì)先把數(shù)據(jù)按幀取出設(shè)定BITMAPINFOHEADER時(shí)要注意biHeight的正負(fù)代表方向用負(fù)數(shù)表示自上而下的位圖否則畫(huà)面會(huì)上下顛倒我第一次實(shí)現(xiàn)時(shí)就在這里栽過(guò)跟頭。核心邏輯參考void CPtwPlayer::OnPaint(CDC* pDC) { std::vectorBYTE vecRaw; if (!m_pPtwFile-GetFrameData(m_dwCurFrame, vecRaw)) return; BITMAPINFO bmi { 0 }; bmi.bmiHeader.biSize sizeof(BITMAPINFOHEADER); bmi.bmiHeader.biWidth m_pPtwFile-GetWidth(); bmi.bmiHeader.biHeight -(LONG)m_pPtwFile-GetHeight(); bmi.bmiHeader.biPlanes 1; bmi.bmiHeader.biBitCount 24; bmi.bmiHeader.biCompression BI_RGB; ::SetDIBitsToDevice( pDC-GetSafeHdc(), 0, 0, m_pPtwFile-GetWidth(), m_pPtwFile-GetHeight(), 0, 0, 0, m_pPtwFile-GetHeight(), vecRaw.data(), bmi, DIB_RGB_COLORS ); }路線(xiàn)二Media Foundation硬解碼適合要渲染H.264的情況如果ptw內(nèi)部是H.264編碼的裸流GDI直繪就行不通了必須借助解碼器。我比較推薦用Media Foundation而不是老舊的VFW因?yàn)閂FW年代太久遠(yuǎn)對(duì)新編碼支持很差。在VC中使用Media Foundation時(shí)有個(gè)關(guān)鍵細(xì)節(jié)裸流沒(méi)有封裝格式不能直接丟給SourceReader需要自己實(shí)現(xiàn)IMFMediaSource接口或者在內(nèi)存中構(gòu)造一個(gè)包含H.264裸流的視頻文件再去創(chuàng)建SourceReader。實(shí)踐中最省力也最穩(wěn)妥的處理辦法是先把解析出來(lái)的H.264幀數(shù)據(jù)轉(zhuǎn)存成標(biāo)準(zhǔn)的MP4或AVI容器這個(gè)過(guò)程相當(dāng)于做一次薄封裝然后再用SourceReader正常打開(kāi)播放。這個(gè)薄封裝方案雖然多寫(xiě)了一些文件操作代碼但好處是對(duì)解碼器完全透明兼容NVIDIA、Intel、AMD各種硬解環(huán)境都穩(wěn)定省掉了自己實(shí)現(xiàn)IMFMediaSource一大坨回調(diào)接口的麻煩。注意如果在VC中編譯時(shí)提示找不到mfapi.h或mfidl.h請(qǐng)先確認(rèn)項(xiàng)目屬性里是否勾選了使用MFC靜態(tài)庫(kù)之外的媒介組件。另外Media Foundation在調(diào)用前需要執(zhí)行MFStartup()初始化程序退出前記得調(diào)MFShutdown()否則多次打開(kāi)關(guān)閉窗口會(huì)出現(xiàn)奇怪的崩潰。4. 常見(jiàn)問(wèn)題與排查清單這五個(gè)坑我基本都踩過(guò)4.1 數(shù)據(jù)恢復(fù)后的ptw文件打不開(kāi)怎么辦這類(lèi)需求里很多人都是重裝系統(tǒng)之后找回了ptw文件但發(fā)現(xiàn)播放器打不開(kāi)。這里要分兩種可能一種是文件頭已經(jīng)損壞一種是文件其實(shí)完好但缺少關(guān)聯(lián)解碼器。先用16進(jìn)制工具打開(kāi)文件看前面幾個(gè)字節(jié)是否還能對(duì)上你已知的魔數(shù)。如果文件頭被清零了可以試試找同目錄下其他正常文件做對(duì)比手工補(bǔ)回文件頭如果整個(gè)文件都損壞嚴(yán)重那就不是代碼能解決的了建議換專(zhuān)業(yè)的數(shù)據(jù)恢復(fù)工具重新掃描一次優(yōu)先恢復(fù)大文件。我之前遇到過(guò)一個(gè)典型例子硬盤(pán)掉電導(dǎo)致ptw文件尾部大量幀數(shù)據(jù)變成0x00文件頭還完整但總幀數(shù)明顯比正常文件多。解析時(shí)報(bào)錯(cuò)的位置就發(fā)生在索引掃描時(shí)讀到了異常長(zhǎng)度字段。所以解析代碼里一定要加長(zhǎng)度合法性校驗(yàn)和邊界判斷不能讓異常數(shù)據(jù)直接把程序搞崩潰。處理這類(lèi)文件我的建議是解析失敗時(shí)降級(jí)處理。比如自動(dòng)忽略掉無(wú)法識(shí)別的塊盡量把能解析到的幀先提取出來(lái)播放而不是一看到異常就直接退出。這個(gè)容錯(cuò)邏輯對(duì)用戶(hù)來(lái)說(shuō)是剛需因?yàn)閿?shù)據(jù)恢復(fù)場(chǎng)景下文件十有八九是不完整的。4.2 重裝系統(tǒng)后沒(méi)有訪(fǎng)問(wèn)權(quán)限怎么處理標(biāo)題熱詞里有一條很真實(shí)重裝系統(tǒng)后視頻文件沒(méi)有訪(fǎng)問(wèn)權(quán)限。這是因?yàn)榕f系統(tǒng)里文件的所有者安全標(biāo)識(shí)符SID已經(jīng)不存在了新系統(tǒng)的用戶(hù)賬戶(hù)跟當(dāng)前文件權(quán)限列表對(duì)不上。解決思路也很直接右鍵文件 - 屬性 - 安全 - 高級(jí) - 更改所有者把所有者改成當(dāng)前管理員賬戶(hù)或者用命令行的icacls工具在管理員權(quán)限的CMD里執(zhí)行icacls D:\video\xxx.ptw /reset /T /C /Q這會(huì)把文件的權(quán)限重置為默認(rèn)繼承值通常能解決訪(fǎng)問(wèn)拒絕的問(wèn)題。如果你是想寫(xiě)程序自動(dòng)處理這件事可以調(diào)用SetNamedSecurityInfo這個(gè)API在代碼里改文件的所有者但C實(shí)現(xiàn)的代碼量不小而且需要提權(quán)個(gè)人建議還是引導(dǎo)用戶(hù)手動(dòng)操作一次更省心。4.3 VC運(yùn)行庫(kù)版本導(dǎo)致的啟動(dòng)即崩潰標(biāo)題熱詞里反復(fù)出現(xiàn)VC 2015-2022 redistributable這說(shuō)明很多開(kāi)發(fā)環(huán)境里都在跟運(yùn)行庫(kù)版本較勁。如果你的工具在新機(jī)器上跑不起來(lái)先按下面順序排查目標(biāo)機(jī)器是否安裝了對(duì)應(yīng)版本的VC Redistributable比如x86和x64都要裝很多程序是32位編譯的但用戶(hù)機(jī)器上只有64位運(yùn)行庫(kù)項(xiàng)目是否錯(cuò)誤地選擇了共享CRT/MD而部署環(huán)境沒(méi)有對(duì)應(yīng)DLL排查DLL依賴(lài)最方便的工具是Dependencies或者Process Explorer看加載失敗的具體模塊是哪個(gè)。在VC項(xiàng)目中穩(wěn)妥的做法是右鍵項(xiàng)目 - 屬性 - C/C - 代碼生成 - 運(yùn)行庫(kù)選擇多線(xiàn)程(/MT)這樣CRT靜態(tài)鏈接進(jìn)exe目標(biāo)機(jī)器不用裝運(yùn)行庫(kù)也能跑。缺點(diǎn)是exe體積會(huì)大一兩MB但換來(lái)的是部署穩(wěn)定性非常劃算。4.4 ANSI和Unicode函數(shù)混用導(dǎo)致路徑解析出錯(cuò)這個(gè)坑在VC處理視頻文件的場(chǎng)景里特別常見(jiàn)。ptw文件路徑如果包含中文比如C:\視頻\測(cè)試.ptw用CFile::Open這個(gè)MFC封裝倒是能自動(dòng)處理因?yàn)镸FC內(nèi)部默認(rèn)轉(zhuǎn)成寬字符。但如果你用了fopen或者CreateFileA這類(lèi)ANSI版本函數(shù)去讀路徑Windows中文系統(tǒng)上會(huì)順利用默認(rèn)代碼頁(yè)解析一般也沒(méi)問(wèn)題可一旦系統(tǒng)區(qū)域設(shè)置改成Beta版: 使用Unicode UTF-8提供全球語(yǔ)言支持ANSI函數(shù)馬上就出亂碼。我建議在新的VC項(xiàng)目中從源頭就統(tǒng)一使用寬字符版本比如CFile::Open直接傳CString不用手動(dòng)轉(zhuǎn)char*自定義解析接口統(tǒng)一接收CString內(nèi)部用CT2W或相關(guān)的包裝類(lèi)做轉(zhuǎn)換避免直接混用std::ifstream打開(kāi)含中文路徑的文件如果一定要用C17可以用std::filesystem::path或者配合_wfopen4.5 打開(kāi)大文件時(shí)內(nèi)存占用過(guò)高解析索引時(shí)如果是一次性把所有幀偏移讀進(jìn)vector文件有幾GB、幾萬(wàn)幀時(shí)vector本身不算大但如果你順手把每幀數(shù)據(jù)也緩存到內(nèi)存里那內(nèi)存很容易爆掉。設(shè)計(jì)上的正確姿勢(shì)是索引表只記錄偏移量一個(gè)8字節(jié)的ULONGLONG真正的幀數(shù)據(jù)在需要顯示時(shí)才用SeekRead去磁盤(pán)讀取。別小看這個(gè)設(shè)計(jì)決策我在處理一個(gè)2.7GB的ptw文件時(shí)一開(kāi)始圖省事把全部幀讀到內(nèi)存進(jìn)程直接吃了2.6GB內(nèi)存界面卡到像死機(jī)改成按需讀取后內(nèi)存占用降到不足200MB播放拖拽流暢度完全夠用。如果一定要做全量緩存提升隨機(jī)訪(fǎng)問(wèn)速度建議只緩存關(guān)鍵幀數(shù)據(jù)I幀B/P幀仍然按需讀取這樣在內(nèi)存和速度之間能取得一個(gè)較好的平衡。5. 一點(diǎn)實(shí)操體會(huì)做了多年VC開(kāi)發(fā)我越來(lái)越覺(jué)得讀私有格式這類(lèi)需求最困難的往往不是代碼本身而是前期的格式分析和異常情況處理。代碼框架寫(xiě)起來(lái)也就幾百行但格式分析經(jīng)常要花幾小時(shí)而且必須容忍各種文件不規(guī)范的現(xiàn)實(shí)。比如同一個(gè)錄屏軟件不同版本生成的ptw文件頭結(jié)構(gòu)都可能存在差異有的加了擴(kuò)展字段有的改了數(shù)據(jù)塊對(duì)齊方式解析程序如果寫(xiě)得太死換一個(gè)版本就失效。我在處理這類(lèi)項(xiàng)目時(shí)習(xí)慣在最開(kāi)始把容錯(cuò)機(jī)制留好文件頭魔數(shù)不對(duì)時(shí)不直接拒絕而是嘗試向后多掃幾KB看是否有可識(shí)別數(shù)據(jù)幀長(zhǎng)度字段異常時(shí)不直接退出而是記錄日志后跳過(guò)繼續(xù)掃。這樣雖然偶爾會(huì)多掃出一些無(wú)用數(shù)據(jù)但對(duì)數(shù)據(jù)恢復(fù)場(chǎng)景特別有效。如果你正在做類(lèi)似的事情我的建議是先準(zhǔn)備三到五個(gè)不同來(lái)源的ptw樣本文件把格式分析做扎實(shí)了再寫(xiě)代碼。這個(gè)前期工作省掉的話(huà)后面絕大多數(shù)時(shí)間都會(huì)花在調(diào)試各種邊界條件上。希望這篇內(nèi)容能幫你少走一些彎路。本文還有配套的精品資源點(diǎn)擊獲取