flv文件解析(純c解析代碼)

来源:https://www.cnblogs.com/leaffei/archive/2019/03/02/10463111.html
-Advertisement-
Play Games

1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <arpa/inet.h> 5 6 #define TAB44 " " 7 #define PRINTF_DEBUG 8 9 #define MAX ...


參考鏈接:1. FLV科普12 FLV腳本數據解析-Metadata Tag解析 https://blog.csdn.net/cabbage2008/article/details/50500021
2. FLV科普9 FLV音頻信息 https://blog.csdn.net/cabbage2008/article/details/50445023
3. FLV科普6 FLV Tag以及Tag頭信息解析 https://blog.csdn.net/cabbage2008/article/details/50374083
4. FLV科普11 FLV視頻信息 https://blog.csdn.net/cabbage2008/article/details/50449857

致敬下圖工具:

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <string.h>
  4 #include <arpa/inet.h>
  5 
  6 #define TAB44 "    "
  7 #define PRINTF_DEBUG
  8 
  9 #define MAX_SIGNATURE_LEN 3
 10 #define MAX_PRE_TAG_SIZE_LEN 4
 11 #define MIN_FLV_HEADER_LEN 9
 12 #define MAX_TAG_HEADER_LEN 11
 13 #define MAX_PARSE_TAG_NUM 15
 14 #define MAX_AMF_STR_SIZE 255
 15 
 16 /************************************************************************************************************
 17 **                                        flv header: 記錄了flv的類型, 版本等信息, 是flv的開頭, 一般都差不多, 占9bytes
 18 **
 19 -------------------------------------------------------------------------------------------------------------
 20 **        欄位名稱               |    長度(bytes)    |        有關描述
 21 -------------------------------------------------------------------------------------------------------------
 22 **        signature              |    3             |        文件標識, 總是為"FLV", 0x46 0x4c 0x56
 23 **        version                |    1             |        版本(目前為0x01)
 24 **        flag                     |    3             |          文件的標誌位說明. 前5位保留, 必須為0; 
 25                                                              第6位為音頻Tag: 1表示有音頻; 第七位保留, 為0; 第8位為視頻Tag: 1表示有視頻
 26 **        headersize             |    4             |        整個header的長度, 一般為9(版本為0x01時); 大於9表示下麵還有擴展信息
 27 ************************************************************************************************************/
 28 /*
 29    1. unsigned char reserved5: 5, flags_audio: 1, reserved1: 1, flags_video: 1;
 30    2. unsigned char : 5, flags_audio: 1, : 1, flags_video: 1; (無名說明無法使用, 僅占位)
 31    3. 下麵結構體位域的另外兩種寫法.
 32 */
 33 typedef struct t_flv_header
 34 {
 35     unsigned char signature[MAX_SIGNATURE_LEN+1];
 36     unsigned char version;
 37     unsigned char : 5;
 38     unsigned char flags_audio: 1;
 39     unsigned char : 1;
 40     unsigned char flags_video: 1; 
 41     
 42     int headersize;
 43 } T_FLV_HEADER;
 44 
 45 /************************************************************************************************************
 46 **                                        tag header
 47 **
 48 -------------------------------------------------------------------------------------------------------------
 49 **        欄位名稱               |    長度(bytes)    |        有關描述
 50 -------------------------------------------------------------------------------------------------------------
 51 **        type                   |    1             |        數據類型, (0x12)為腳本類型; (0x08)為音頻類型; (0x09)為視頻類型
 52 **        data_size              |    3             |        數據區長度
 53 **        timestamp                 |    3             |          時間戳, 類型為(0x12)的tag時間戳一直為0, (0xFFFFFF)可以表示長度為4小時, 單位為毫秒.
 54 **        timestamp_extended     |    1             |        將時間戳擴展為4bytes, 代表高8位, 一般都為0, 長度為4小時的flv一般很少見了
 55 **        streamid               |    3             |        總為0
 56 ************************************************************************************************************/
 57 typedef struct t_flv_tag_header
 58 {
 59     int type;
 60     int data_size;
 61     int timestamp;
 62     int timestamp_extended;
 63     int streamid;
 64 } T_FLV_TAG_HEADER;
 65 
 66 /************************************************************************************************************
 67 **                                        video tag header
 68 **
 69 -------------------------------------------------------------------------------------------------------------
 70 **        欄位名稱               |    長度(bytes)    |        有關描述
 71 -------------------------------------------------------------------------------------------------------------
 72 **        FreameType             |    4(bits)             |  FrameType為數據類型, 1為關鍵幀, 2為非關鍵幀, 3為h263的非關鍵幀,
 73                                                              4為伺服器生成關鍵幀, 5為視頻信息或命令幀.
 74 **        CodecId                |    4(bits)             |  CodecID為包裝類型, 1為JPEG, 2為H263, 3為Screen video, 
 75                                                              4為On2 VP6, 5為On2 VP6, 6為Screen videoversion 2, 7為AVC
 76                                                              
 77 CodecID=2, 為H263VideoPacket;
 78 CodecID=3, 為ScreenVideopacket;
 79 CodecID=4, 為VP6FLVVideoPacket;
 80 CodecID=5, 為VP6FLVAlphaVideoPacket;
 81 CodecID=6, 為ScreenV2VideoPacket;
 82 CodecID=7, 為AVCVideoPacket.
 83 ************************************************************************************************************/
 84 typedef struct t_flv_tag_video_header
 85 {
 86     unsigned char freameType:4, codecId:4;
 87 } T_FLV_TAG_VIDEO_HEADER;
 88 
 89 /************************************************************************************************************
 90 **                                        AVCDecoderConfigurationRecord
 91 **
 92 -------------------------------------------------------------------------------------------------------------
 93 **        欄位名稱               |    長度(bytes)    |        有關描述
 94 -------------------------------------------------------------------------------------------------------------
 95 **        configurationVersion   |    1             |        配置版本占用8位, 一定為1
 96 **        AVCProfileIndication   |    1             |        profile_idc占用8位, 從H.264標準SPS第一個欄位profile_idc拷貝而來, 指明所用profile
 97 **        profile_compatibility  |    1             |        占用8位, 從H.264標準SPS拷貝的冗餘字
 98 **        AVCLevelIndication     |    1             |        level_idc占用8位, 從H.264標準SPS第一個欄位level_idc拷貝而來, 指明所用 level
 99 **        reserved               |    6b            |        保留位占6位, 值一定為'111111'
100 **        lengthSizeMinusOne     |    2b            |        占用2位, 表示NAL單元頭的長度, 0表示1位元組, 1表示2位元組, 2表示3位元組, 3表示4位元組
101 **        reserved               |    3b            |        保留位占3位, 值一定為'111'
102 **        numOfSPS               |    5b            |        numOfSequenceParameterSets占用5位, 表示當前SPS的個數
103 **        SPSLength               |    2             |        sequenceParameterSetLength占用16位, SPS占用的長度
104 **        SPSData                   |    *             |        
105 **        numOfPPS               |    5b            |        numOfPictureParameterSets占用8位, 表示當前PPS的個數
106 **        PPSLength               |    2             |        pictureParameterSetLength占用16位, PPS占用的長度
107 **        PPSData                    |    *             |        numOfPictureParameterSets占用8位, 表示當前PPS的個數
108 
109 AVCProfileIndication, profile_compatibility, AVCLevelIndication就是拷貝SPS的前3個位元組                                                 
110 ************************************************************************************************************/
111 typedef struct t_flv_tag_avc_dec_cfg
112 {
113     unsigned char configurationVersion;
114     unsigned char AVCProfileIndication;
115     unsigned char profile_compatibility;
116     unsigned char AVCLevelIndication;
117     unsigned char :6, lengthSizeMinusOne:2;
118     
119     unsigned char :3, numOfSequenceParameterSets:5;
120     unsigned short spsLen;
121     unsigned char *spsData;
122     
123     unsigned char numOfPictureParameterSets;
124     unsigned short ppsLen;
125     unsigned char *ppsData;
126 } T_FLV_TAG_AVC_DEC_CFG;
127 
128 /************************************************************************************************************
129 **                                        avc video packet header
130 **
131 -------------------------------------------------------------------------------------------------------------
132 **        欄位名稱               |    長度(bytes)    |        有關描述
133 -------------------------------------------------------------------------------------------------------------
134 **        AVCPacketType占用1位元組 |    1                |        
135 **        CompositionTime        |    3             |
136                                                              
137 AVCVideoPacket同樣包括Packet Header和Packet Body兩部分:
138 Packet Header:
139         AVCPacketType占用1位元組, 僅在AVC時有此欄位
140                0, AVC sequence header (SPS、PPS信息等)
141                1, AVC NALU
142                2, AVC end of sequence (lower level NALU sequence ender is not required or supported)
143 
144         CompositionTime占用24位, 相對時間戳, 如果AVCPacketType=0x01為相對時間戳; 其它, 均為0;
145         該值表示當前幀的顯示時間, tag的時間為解碼時間, 顯示時間等於 解碼時間+CompositionTime.
146 ************************************************************************************************************/
147 typedef struct t_flv_tag_avc_video_packet
148 {
149     unsigned char avcPacketType;
150     
151     int compositionTime;
152     
153     union videoPacket
154     {
155         T_FLV_TAG_AVC_DEC_CFG avcDecCfg;
156     } vp;
157 } T_FLV_TAG_AVC_VIDEO_PACKET;
158 
159 typedef struct t_flv_tag_audio_header
160 {
161     unsigned char soundFormat:4, soundRate:2, soundSize:1, soundType:1;
162 } T_FLV_TAG_AUDIO_HEADER;
163 
164 typedef struct t_flv_tag_aac_spec_cfg
165 {
166     unsigned char audioObjectType:5;
167     unsigned char samplingFreqIndex:4, channelCfg:2;
168 } T_FLV_TAG_AAC_SPEC_CFG;
169 
170 typedef struct t_flv_tag_aac_audio_packet
171 {
172     unsigned char aacPacketType;
173     
174     union audioPacket
175     {
176         T_FLV_TAG_AAC_SPEC_CFG aacSpecCfg;
177     } ap;
178 } T_FLV_TAG_AAC_AUDIO_PACKET;
179 
180 typedef struct t_flv_tag
181 {
182 } T_FLV_TAG;
183 
184 /* 小端轉double */
185 static double dealAmfNumber(unsigned char *amfNum)
186 {
187     double d = 0;
188     
189     unsigned char *dp = (unsigned char *)&d;
190 
191     dp[0] = amfNum[7];
192     dp[1] = amfNum[6];
193     dp[2] = amfNum[5];
194     dp[3] = amfNum[4];
195     dp[4] = amfNum[3];
196     dp[5] = amfNum[2];
197     dp[6] = amfNum[1];
198     dp[7] = amfNum[0];
199     
200     return d;
201 }
202 
203 /*
204   1. DealHeader(const unsigned char* headerData); 
205      這樣定義會報warning: assignment discards 'const' qualifier from pointer target type,
206      大意是指針丟掉"const"限定符.
207   2. 原因是: data = headerData; 這一句存在丟掉的風險(可通過給*data賦予不同的值, 使得headerData的數據也被修改, 失去const的作用)
208   3. const int *p; //這種情況表示*p是const無法進行修改, 而p是可以進行修改的;
209      int* const p; //這種情況表示p是const無法進行修改, 而*p是可以進行修改的;
210      const int* const p; //這種情況表示*p與p都無法進行修改.
211 */
212 static void DealFlvHeader(unsigned char* const headerData)
213 {
214     unsigned char *data = NULL;
215     
216     T_FLV_HEADER flvHeader = {0};
217     
218     data = headerData;
219     
220     memset(&flvHeader, 0x0, sizeof(T_FLV_HEADER));
221     
222     memcpy(flvHeader.signature, data, MAX_SIGNATURE_LEN);
223     
224     flvHeader.signature[MAX_SIGNATURE_LEN] = '\0';
225     
226     data += MAX_SIGNATURE_LEN;
227     
228     flvHeader.version = data[0];
229     
230     data += 1;
231     
232     flvHeader.flags_audio = data[0] >> 2 & 0x1;
233     flvHeader.flags_video = data[0] & 0x1;
234     
235     data += 1;
236     
237     flvHeader.headersize = (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3];
238     
239     if (0x1 != flvHeader.version)
240     {
241         printf("version is not 1, todo...\n");
242     }
243     
244 #ifdef PRINTF_DEBUG
245     printf("+FLV Header\n");
246     printf("%ssignature: %s, version: %d, flags_audio: %d, flags_video: %d, headersize: %d\n",
247             TAB44, flvHeader.signature, flvHeader.version, flvHeader.flags_audio, flvHeader.flags_video, flvHeader.headersize);
248 #endif
249 }
250 
251 static void DealTagHeader(unsigned char* const headerData, T_FLV_TAG_HEADER *tagHeader)
252 {
253     static int videoTagNum = 0;
254     static int audioTagNum = 0;
255     
256     unsigned char *data = NULL;
257     
258     T_FLV_TAG_HEADER header = {0};
259     
260     data = headerData;
261     
262     memset(&header, 0x0, sizeof(T_FLV_TAG_HEADER));
263     
264     header.type = data[0];
265     
266     data += 1;
267     
268     header.data_size = (data[0] << 16) | (data[1] << 8) | data[2];
269     
270     data += 3;
271     
272     header.timestamp = (data[0] << 16) | (data[1] << 8) | data[2];
273     
274     data += 3;
275     
276     header.timestamp_extended = data[0];
277     
278     data += 1;
279     
280     header.streamid = (data[0] << 16) | (data[1] << 8) | data[2];
281     
282     memcpy(tagHeader, &header, sizeof(T_FLV_TAG_HEADER));
283     
284 #ifdef PRINTF_DEBUG
285     switch (tagHeader->type)
286     {
287         case 0x12:
288             printf("%s+Script Tag\n", TAB44);
289             
290             break;
291             
292         case 0x9:
293             videoTagNum++;
294             
295             printf("%s+Video Tag[%d]\n", TAB44, videoTagNum);
296 
297             break;
298 
299         case 0x8:
300             audioTagNum++;
301             
302             printf("%s+Audio Tag[%d]\n", TAB44, audioTagNum);
303 
304             break;
305 
306         default:
307             break;
308     }
309     
310     printf("%s%s+Tag Header\n", TAB44, TAB44);
311     printf("%s%s%stype: %d, data_size: %d, timestamp: %d, timestamp_extended: %d, streamid: %d\n",
312             TAB44, TAB44, TAB44, tagHeader->type, tagHeader->data_size, tagHeader->timestamp, tagHeader->timestamp_extended, tagHeader->streamid);
313 #endif
314 }
315 
316 /*
317     第一個AMF包:
318            第1個位元組表示AMF包類型, 一般總是0x02, 表示字元串, 其他值表示意義請查閱文檔.
319            第2-3個位元組為UI16類型值, 表示字元串的長度, 一般總是0x000A("onMetaData"長度).
320            後面位元組為字元串數據, 一般總為"onMetaData".
321      
322     第二個AMF包:
323            第1個位元組表示AMF包類型, 一般總是0x08, 表示數組.
324            第2-5個位元組為UI32類型值, 表示數組元素的個數.
325            後面即為各數組元素的封裝, 數組元素為元素名稱和值組成的對. 表示方法如下:
326            第1-2個位元組表示元素名稱的長度, 假設為L. 後面跟著為長度為L的字元串. 第L+3個位元組表示元素值的類型.
327            後面跟著為對應值, 占用位元組數取決於值的類型.
328 
329     0 = Number type (double, 8)
330     1 = Boolean type
331     2 = String type
332     3 = Object type
333     4 = MovieClip type
334     5 = Null type
335     6 = Undefined type
336     7 = Reference type
337     8 = ECMA array type
338     10 = Strict array type
339     11 = Date type
340     12 = Long string type
341     
342     1. 不要頻繁的malloc小記憶體(記憶體碎片, 代價);
343     2. 如該函數中arrayKey, arrayValue, amfStrData設置成指針, 然後malloc就有問題(字元串後殘留上述三個最大長度中的字元);
344     3. 可能的解釋: 當用free釋放的你用malloc分配的存儲空間, 釋放的存儲空間並沒有從進程的地址空間中刪除, 而是保留在可用存儲區池中,
345        當再次用malloc時只要可用存儲區池中有足夠的地址空間, 都不會再向內可申請記憶體了, 而是在可用存儲區池中分配了.
346        
347 實際分析時: 8的數組後還有一串 00 00 09, 暫時不清楚, 先跳過if (tagDataSize <= 3)
348 */
349 static void DealScriptTagData(unsigned char* const tagData, unsigned int tagDataSize)
350 {
351     int i = 0;
352     int amfType = 0;
353     int amfIndex = 0;
354     int valueType = 0;
355     int valueSize = 0;
356     int keySize = 0;
357     int arrayCount = 0;
358     int amfStringSize = 0;
359     
360     double amfNum = 0;
361     
362     unsigned char amfStr[MAX_AMF_STR_SIZE+1] = {0};
363     
364     unsigned char *data = NULL;
365     
366     data = tagData;
367     
368     for (;;)
369     {
370         if (tagDataSize <= 3)
371         {
372             break;
373         }
374         
375         amfType = data[0];
376         
377         amfIndex += 1;
378         
379         data += 1;
380         tagDataSize -= 1;
381 
382 #ifdef PRINTF_DEBUG
383         printf("%s%s%sAMF%d type: %d\n", TAB44, TAB44, TAB44, amfIndex, amfType);
384 #endif
385         
386         switch (amfType)
387         {
388             case 2:
389                 amfStringSize = (data[0] << 8) | data[1];
390 
391 #ifdef PRINTF_DEBUG
392         printf("%s%s%sAMF%d String size: %d\n", TAB44, TAB44, TAB44, amfIndex, amfStringSize);
393 #endif
394                 
395                 data += 2;
396                 tagDataSize -= 2;
397                 
398                 memset(amfStr, 0x0, sizeof(amfStr));
399                 
400                 memcpy(amfStr, data, amfStringSize);
401                 
402                 amfStr[amfStringSize] = '\0';
403                     
404 #ifdef PRINTF_DEBUG
405         printf("%s%s%sAMF%d String: %s\n", TAB44, TAB44, TAB44, amfIndex, amfStr);
406 #endif
407                 
408                 data += amfStringSize;
409                 tagDataSize -= amfStringSize;
410 
411                 break;
412 
413             case 8:
414                 arrayCount = (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3];
415 
416 #ifdef PRINTF_DEBUG
417         printf("%s%s%sAMF%d Metadata count: %d\n", TAB44, TAB44, TAB44, amfIndex, arrayCount);
418         printf("%s%s%s+Metadata\n", TAB44, TAB44, TAB44);
419 #endif
420                 
421                 data += 4;
422                 tagDataSize -= 4;
423                 
424                 for (i=0; i<arrayCount; i++)
425                 {
426                     keySize = (data[0] << 8) | data[1];
427                     
428                     data += 2;
429                     tagDataSize -= 2;
430                     
431                     memset(amfStr, 0x0, sizeof(amfStr));
432 
433                     memcpy(amfStr, data, keySize);
434 
435                     amfStr[keySize] = '\0';
436                 
437 #ifdef PRINTF_DEBUG
438                     printf("%s%s%s%s%s: ", TAB44, TAB44, TAB44, TAB44, amfStr);
439 #endif
440                     
441                     data += keySize;
442                     tagDataSize -= keySize;
443                     
444                     valueType = data[0];
445                     
446                     data += 1;
447                     tagDataSize -= 1;
448                     
449                     if (0 == valueType)
450                     {
451                         amfNum = dealAmfNumber(data);
452 #ifdef PRINTF_DEBUG
453                         printf("%lf\n", amfNum);
454 #endif
455 
456                         data += 8;
457                         tagDataSize -= 8;
458                     }
459                     else if (1 == valueType)
460                     {
461 #ifdef PRINTF_DEBUG
462                         printf("%d\n", data[0]);
463 #endif
464                         data += 1;
465                         tagDataSize -= 1;
466                     }
467                     else if (2 == valueType)
468                     {
469                         valueSize = (data[0] << 8) | data[1];
470                         
471                         data += 2;
472                         tagDataSize -= 2;
473                         
474   

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1、下載JDK "官網" 打開後,直接下載最新版本。 選擇dmg文件下載 2、開始安裝,一直下一步。 3、打開終端,查詢安裝路徑: ,複製備用。 4、配置Java的環境變數 1)打開終端,到主目錄 2)查看是否有.bash_profile文件 3)如果沒有新建一個 4)有了這個文件以後,進行編輯 , ...
  • 基於Python結合pykafka實現kafka生產及消費速率&主題分區偏移實時監控 By: 授客 QQ:1033553122 1.測試環境 python 3.4 zookeeper-3.4.13.tar.gz 下載地址1: http://zookeeper.apache.org/releases. ...
  • 我的其他隨筆: 來一局緊張刺激的吃雞——淺談裝飾者模式 一起去開心的購物吧——淺談觀察者模式 記一場精彩的籃球比賽——淺談策略模式 大家好,前幾日連夜更了幾篇Java設計模式的小隨筆,從觀看量來說,我還是很高興的,有很多的朋友通過看了博文,也許接觸了新的知識,也許理解了自己之前沒弄懂的東西,也許只是 ...
  • 最近偶然到博客園看了一下,距離上次的博客已經過去很多天了,閱讀量卻少得可憐,對於博客園小白來說感覺不是很友好(主要是心理不平衡),而且有些博客被其他網站不帶出處的裝載了,它的閱讀量卻很多。於是靈光一閃,決定寫個程式增加一下閱讀量。(僅用於學術交流,實際上我就試了一下,沒有真正刷過) 一、原理 一般來 ...
  • 先看一段java代碼,func返回值為int: 正確的返回結果是,func返回1。 原因:如果finally中沒有return語句,但是改變了要返回的值,這裡有點類似與引用傳遞和值傳遞的區別,分以下兩種情況,: 1)如果return的數據是基本數據類型或文本字元串,則在finally中對該基本數據的 ...
  • 準備 安裝 "vscode" ,可直接下載deb包進行安裝,完成後安裝C/C++ for Visual Studio Code插件,安裝後重啟(最新1.3版本以後不需要重啟)。 生成目錄和文件 新建文件夾【test】,並新建文件helloworld.cpp文件,文件中內容如下, include in ...
  • 使用python-kafka類庫開發kafka生產者&消費者&客戶端 By: 授客 QQ:1033553122 1.測試環境 python 3.4 zookeeper-3.4.13.tar.gz 下載地址1: http://zookeeper.apache.org/releases.html#dow ...
  • 1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 5 #define PRINTF_DEBUG 6 7 #define BOX_TYPE_FTYPE "ftyp" 8 #define BOX_TYPE_MOOV "m ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...