對X264/FFMPEG架構探討---感覺不錯

?

3. FFMPEG架構分析

FFMPEG是目前被應用最廣泛的編解碼軟件庫,支持多種流行的編解碼器,它是C語言實現的,不僅被集成到各種PC軟件,也經常被移植到多種嵌入式設備中。使用面向對象的辦法來設想這樣一個編解碼庫,首先讓人想到的是構造各種編解碼器的類,然后對于它們的抽象基類確定運行數據流的規則,根據算法轉換輸入輸出對象。

在實際的代碼,將這些編解碼器分成encoder/decoder,muxer/demuxer和device三種對象,分別對應于編解碼,輸入輸出格式和設備。在main函數的開始,就是初始化這三類對象。在avcodec_register_all中,很多編解碼器被注冊,包括視頻的H.264解碼器和X264編碼器等,

REGISTER_DECODER (H264, h264);
REGISTER_ENCODER (LIBX264, libx264);

找到相關的宏代碼如下

#define REGISTER_ENCODER(X,x) { /
????????? extern AVCodec x##_encoder; /
????????? if(CONFIG_##X##_ENCODER)? avcodec_register(&x##_encoder); }
#define REGISTER_DECODER(X,x) { /
????????? extern AVCodec x##_decoder; /
????????? if(CONFIG_##X##_DECODER)? avcodec_register(&x##_decoder); }

這樣就實際在代碼中根據CONFIG_##X##_ENCODER這樣的編譯選項來注冊libx264_encoder和h264_decoder,注冊的過程發生在avcodec_register(AVCodec *codec)函數中,實際上就是向全局鏈表first_avcodec中加入libx264_encoder、h264_decoder特定的編解碼器,輸入參數AVCodec是一個結構體,可以理解為編解碼器的基類,其中不僅包含了名稱,id等屬性,而且包含了如下函數指針,讓每個具體的編解碼器擴展類實現。

??? int (*init)(AVCodecContext *);
??? int (*encode)(AVCodecContext *, uint8_t *buf, int buf_size, void *data);
??? int (*close)(AVCodecContext *);
??? int (*decode)(AVCodecContext *, void *outdata, int *outdata_size,
????????????????? const uint8_t *buf, int buf_size);
?? ?void (*flush)(AVCodecContext *);

繼續追蹤libx264,也就是X264的靜態編碼庫,它在FFMPEG編譯的時候被引入作為H.264編碼器。在libx264.c中有如下代碼

AVCodec libx264_encoder = {
??? .name = "libx264",
??? .type = CODEC_TYPE_VIDEO,
??? .id = CODEC_ID_H264,
??? .priv_data_size = sizeof(X264Context),
??? .init = X264_init,
??? .encode = X264_frame,
??? .close = X264_close,
??? .capabilities = CODEC_CAP_DELAY,
??? .pix_fmts = (enum PixelFormat[]) { PIX_FMT_YUV420P, PIX_FMT_NONE },
??? .long_name = NULL_IF_CONFIG_SMALL("libx264 H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10"),
};

這里具體對來自AVCodec得屬性和方法賦值。其中
??? .init = X264_init,
??? .encode = X264_frame,
??? .close = X264_close,
將函數指針指向了具體函數,這三個函數將使用libx264靜態庫中提供的API,也就是X264的主要接口函數進行具體實現。pix_fmts定義了所支持的輸入格式,這里4:2:0
PIX_FMT_YUV420P,?? ///< planar YUV 4:2:0, 12bpp, (1 Cr & Cb sample per 2x2 Y samples)

上面看到的X264Context封裝了X264所需要的上下文管理數據,
typedef struct X264Context {
??? x264_param_t params;
??? x264_t *enc;
??? x264_picture_t pic;
??? AVFrame out_pic;
} X264Context;
它屬于結構體AVCodecContext的void *priv_data變量,定義了每種編解碼器私有的上下文屬性,AVCodecContext也類似上下文基類一樣,還提供其他表示屏幕解析率、量化范圍等的上下文屬性和rtp_callback等函數指針供編解碼使用。

回到main函數,可以看到完成了各類編解碼器,輸入輸出格式和設備注冊以后,將進行上下文初始化和編解碼參數讀入,然后調用av_encode()函數進行具體的編解碼工作。根據該函數的注釋一路查看其過程:

1. 輸入輸出流初始化。
2. 根據輸入輸出流確定需要的編解碼器,并初始化。
3. 寫輸出文件的各部分

?

重點關注一下step2和3,看看怎么利用前面分析的編解碼器基類來實現多態。大概查看一下這段代碼的關系,發現在FFMPEG里,可以用類圖來表示大概的編解碼器組合。

可以參考【3】來了解這些結構的含義(見附錄)。在這里會調用一系列來自utils.c的函數,這里的avcodec_open()函數,在打開編解碼器都會調用到,它將運行如下代碼:
??? avctx->codec = codec;
??? avctx->codec_id = codec->id;
??? avctx->frame_number = 0;
??? if(avctx->codec->init){
??????? ret = avctx->codec->init(avctx);
進行具體適配的編解碼器初始化,而這里的avctx->codec->init(avctx)就是調用AVCodec中函數指針定義的具體初始化函數,例如X264_init。

在avcodec_encode_video()和avcodec_encode_audio()被output_packet()調用進行音視頻編碼,將同樣利用函數指針avctx->codec->encode()調用適配編碼器的編碼函數,如X264_frame進行具體工作。

從上面的分析,我們可以看到FFMPEG怎么利用面向對象來抽象編解碼器行為,通過組合和繼承關系具體化每個編解碼器實體。設想要在FFMPEG中加入新的解碼器H265,要做的事情如下:
1. 在config編譯配置中加入CONFIG_H265_DECODER
2. 利用宏注冊H265解碼器
3. 定義AVCodec 265_decoder變量,初始化屬性和函數指針
4. 利用解碼器API具體化265_decoder的init等函數指針

完成以上步驟,就可以把新的解碼器放入FFMPEG,外部的匹配和運行規則由基類的多態實現了。

4. X264架構分析

X264是一款從2004年有法國大學生發起的開源H.264編碼器,對PC進行匯編級代碼優化,舍棄了片組和多參考幀等性能效率比不高的功能來提高編碼效率,它被FFMPEG作為引入的.264編碼庫,也被移植到很多DSP嵌入平臺。前面第三節已經對FFMPEG中的X264進行舉例分析,這里將繼續結合X264框架加深相關內容的了解。

查看代碼前,還是思考一下對于一款具體的編碼器,怎么面向對象分析呢?對熵編碼部分對不同算法的抽象,還有幀內或幀間編碼各種估計算法的抽象,都可以作為類來構建。

在X264中,我們看到的對外API和上下文變量都聲明在X264.h中,API函數中,關于輔助功能的函數在common.c中定義
void x264_picture_alloc( x264_picture_t *pic, int i_csp, int i_width, int i_height );
void x264_picture_clean( x264_picture_t *pic );
int x264_nal_encode( void *, int *, int b_annexeb, x264_nal_t *nal );
而編碼功能函數定義在encoder.c
x264_t *x264_encoder_open?? ( x264_param_t * );
int???? x264_encoder_reconfig( x264_t *, x264_param_t * );
int???? x264_encoder_headers( x264_t *, x264_nal_t **, int * );
int???? x264_encoder_encode ( x264_t *, x264_nal_t **, int *, x264_picture_t *, x264_picture_t * );
void??? x264_encoder_close? ( x264_t * );
在x264.c文件中,有程序的main函數,可以看作做API使用的例子,它也是通過調用X264.h中的API和上下文變量來實現實際功能。

X264最重要的記錄上下文數據的結構體x264_t定義在common.h中,它包含了從線程控制變量到具體的SPS、PPS、量化矩陣、cabac上下文等所有的H.264編碼相關變量。其中包含如下的結構體
??? x264_predict_t????? predict_16x16[4+3];
??? x264_predict_t????? predict_8x8c[4+3];
??? x264_predict8x8_t?? predict_8x8[9+3];
??? x264_predict_t????? predict_4x4[9+3];
??? x264_predict_8x8_filter_t predict_8x8_filter;

??? x264_pixel_function_t pixf;
??? x264_mc_functions_t?? mc;
??? x264_dct_function_t?? dctf;
??? x264_zigzag_function_t zigzagf;
??? x264_quant_function_t quantf;
??? x264_deblock_function_t loopf;
跟蹤查看可以看到它們或是一個函數指針,或是由函數指針組成的結構,這樣的用法很想面向對象中的interface接口聲明。這些函數指針將在x264_encoder_open()函數中被初始化,這里的初始化首先根據CPU的不同提供不同的函數實現代碼段,很多與可能是匯編實現,以提高代碼運行效率。其次把功能相似的函數集中管理,例如類似intra16的4種和intra4的九種預測函數都被用函數指針數組管理起來。

x264_encoder_encode()是負責編碼的主要函數,而其內包含的x264_slice_write()負責片層一下的具體編碼,包括了幀內和幀間宏塊編碼。在這里,cabac和cavlc的行為是根據h->param.b_cabac來區別的,分別運行x264_macroblock_write_cabac()和x264_macroblock_write_cavlc()來寫碼流,在這一部分,功能函數按文件定義歸類,基本按照編碼流程圖運行,看起來更像面向過程的寫法,在已經初始化了具體的函數指針,程序就一直按編碼過程的邏輯實現。如果從整體架構來看,x264利用這種類似接口的形式實現了弱耦合和可重用,利用x264_t這個貫穿始終的上下文,實現信息封裝和多態。

本文大概分析了FFMPEG/X264的代碼架構,重點探討用C語言來實現面向對象編碼,雖不至于強行向C++靠攏,但是也各有實現特色,保證實用性。值得規劃C語言軟件項目所借鑒。?

?

【參考文獻】

1.“用例子說明面向對象和面向過程的區別”
2.?liyuming1978,“liyuming1978的專欄”
3. “FFMpeg框架代碼閱讀”

?

?

附錄:節選自【3】

3. 當前muxer/demuxer的匹配
在FFmpeg的文件轉換過程中,首先要做的就是根據傳入文件和傳出文件的后綴名[FIXME]匹配
合適的demuxer和muxer。匹配上的demuxer和muxer都保存在如下所示,定義在ffmpeg.c里的
全局變量file_iformat和file_oformat中:
? ? static AVInputFormat *file_iformat;
? ? static AVOutputFormat *file_oformat;

3.1 demuxer匹配
在libavformat/utils.c中的static AVInputFormat *av_probe_input_format2(
AVProbeData *pd, int is_opened, int *score_max)函數用途是根據傳入的probe data數據
,依次調用每個demuxer的read_probe接口,來進行該demuxer是否和傳入的文件內容匹配的
判斷。其調用順序如下:
void parse_options(int argc, char **argv, const OptionDef *options,?
? ? ? ? ? void (* parse_arg_function)(const char *));
? ? static void opt_input_file(const char *filename)
? ? ? ? int av_open_input_file(…… )
? ? ? ? ? ? AVInputFormat *av_probe_input_format(AVProbeData *pd,?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? int is_opened)
? ? ? ? ? ? ? ? static AVInputFormat *av_probe_input_format2(……)
opt_input_file函數是在保存在const OptionDef options[]數組中,用于
void parse_options(int argc, char **argv, const OptionDef *options)中解析argv里的
“-i” 參數,也就是輸入文件名時調用的。

3.2 muxer匹配
與demuxer的匹配不同,muxer的匹配是調用guess_format函數,根據main() 函數的argv里的
輸出文件后綴名來進行的。
void parse_options(int argc, char **argv, const OptionDef *options,?
? ? ? ? ? void (* parse_arg_function)(const char *));
? ? void parse_arg_file(const char *filename)
? ? ? ? static void opt_output_file(const char *filename)
? ? ? ? ? ? AVOutputFormat *guess_format(const char *short_name,?
? ? ? ? ? ? ? ? ? ? ? ? ? ? const char *filename,
? ? ? ? ? ? ? ? ? ? ? ? ? ? const char *mime_type)

3.3 當前encoder/decoder的匹配
在main()函數中除了解析傳入參數并初始化demuxer與muxer的parse_options( )函數以外,
其他的功能都是在av_encode( )函數里完成的。
在libavcodec/utils.c中有如下二個函數:
? ? AVCodec *avcodec_find_encoder(enum CodecID id)
? ? AVCodec *avcodec_find_decoder(enum CodecID id)
他們的功能就是根據傳入的CodecID,找到匹配的encoder和decoder。

在av_encode( )函數的開頭,首先初始化各個AVInputStream和AVOutputStream,然后分別調
用上述二個函數,并將匹配上的encoder與decoder分別保存在:
AVInputStream->AVStream *st->AVCodecContext *codec->struct AVCodec *codec與
AVOutputStream->AVStream *st->AVCodecContext *codec->struct AVCodec *codec變量。

4. 其他主要數據結構
4.1 AVFormatContext
AVFormatContext是FFMpeg格式轉換過程中實現輸入和輸出功能、保存相關數據的主要結構。
每一個輸入和輸出文件,都在如下定義的指針數組全局變量中有對應的實體。
? ? static AVFormatContext *output_files[MAX_FILES];
? ? static AVFormatContext *input_files[MAX_FILES];
對于輸入和輸出,因為共用的是同一個結構體,所以需要分別對該結構中如下定義的iformat
或oformat成員賦值。
? ? struct AVInputFormat *iformat;
? ? struct AVOutputFormat *oformat;
對一個AVFormatContext來說,這二個成員不能同時有值,即一個AVFormatContext不能同時
含有demuxer和muxer。在main( )函數開頭的parse_options( )函數中找到了匹配的muxer和
demuxer之后,根據傳入的argv參數,初始化每個輸入和輸出的AVFormatContext結構,并保
存在相應的output_files和input_files指針數組中。在av_encode( )函數中,output_files
和input_files是作為函數參數傳入后,在其他地方就沒有用到了。

4.2 AVCodecContext
保存AVCodec指針和與codec相關數據,如video的width、height,audio的sample rate等。
AVCodecContext中的codec_type,codec_id二個變量對于encoder/decoder的匹配來說,最為
重要。
? ? enum CodecType codec_type;? ? /* see CODEC_TYPE_xxx */
? ? enum CodecID codec_id;? ? ? ? /* see CODEC_ID_xxx */

如上所示,codec_type保存的是CODEC_TYPE_VIDEO,CODEC_TYPE_AUDIO等媒體類型,
codec_id保存的是CODEC_ID_FLV1,CODEC_ID_VP6F等編碼方式。

以支持flv格式為例,在前述的av_open_input_file(…… ) 函數中,匹配到正確的
AVInputFormat demuxer后,通過av_open_input_stream( )函數中調用AVInputFormat的
read_header接口來執行flvdec.c中的flv_read_header( )函數。在flv_read_header( )函數
內,根據文件頭中的數據,創建相應的視頻或音頻AVStream,并設置AVStream中
AVCodecContext的正確的codec_type值。codec_id值是在解碼過程中flv_read_packet( )函
數執行時根據每一個packet頭中的數據來設置的。

4.3 AVStream
AVStream結構保存與數據流相關的編解碼器,數據段等信息。比較重要的有如下二個成員:
? ? AVCodecContext *codec; /**< codec context */
? ? void *priv_data;
其中codec指針保存的就是上節所述的encoder或decoder結構。priv_data指針保存的是和具
體編解碼流相關的數據,如下代碼所示,在ASF的解碼過程中,priv_data保存的就是
ASFStream結構的數據。
? ? AVStream *st;
? ? ASFStream *asf_st;??
? ? … …
? ? st->priv_data = asf_st;

4.4 AVInputStream/ AVOutputStream
根據輸入和輸出流的不同,前述的AVStream結構都是封裝在AVInputStream和AVOutputStream
結構中,在av_encode( )函數中使用。AVInputStream中還保存的有與時間有關的信息。
AVOutputStream中還保存有與音視頻同步等相關的信息。

4.5 AVPacket
AVPacket結構定義如下,其是用于保存讀取的packet數據。
typedef struct AVPacket {
? ? int64_t pts;? ? ? ? ? ? ///< presentation time stamp in time_base units
? ? int64_t dts;? ? ? ? ? ? ///< decompression time stamp in time_base units
? ? uint8_t *data;
? ? int? size;
? ? int? stream_index;
? ? int? flags;
? ? int? duration;? ? ? ? ///< presentation duration in time_base units (0 if not available)
? ? void (*destruct)(struct AVPacket *);
? ? void *priv;
? ? int64_t pos;? ? ? ? ? ///< byte position in stream, -1 if unknown
} AVPacket;

在av_encode()函數中,調用AVInputFormat的
(*read_packet)(struct AVFormatContext *, AVPacket *pkt)接口,讀取輸入文件的一幀數
據保存在當前輸入AVFormatContext的AVPacket成員中。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/254998.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/254998.shtml
英文地址,請注明出處:http://en.pswp.cn/news/254998.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

如何快糙好猛的使用Shiqi.Yu老師的公開人臉檢測庫(附源碼)

前言 本次編寫所用的庫為于仕祺老師免費提供的人臉檢測庫。真心好用&#xff0c;識別率和識別速度完全不是Opencv自帶的程序能夠比擬的。將其配合Opencv的EigenFace算法&#xff0c;基本上可以形成一個小型的畢業設計。&#xff08;我是學機械的啊喂&#xff01;&#xff01;&a…

SQL語句增加字段、修改字段、修改類型、修改默認值

--一、修改字段默認值alter table 表名 drop constraint 約束名字 ------說明&#xff1a;刪除表的字段的原有約束alter table 表名 add constraint 約束名字 DEFAULT 默認值 for 字段名稱 -------說明&#xff1a;添加一個表的字段的約束并指定默認值--二、修改字段名&#…

node+koa2+mysql搭建博客后臺

本文將詳細講解使用nodekoa2mysql搭建博客后臺的全過程。 開發環境 node 8.3.0及以上npm 5.3.0及以上mysql 5.7.21具體的環境配置可查看我的上一篇文章 準備工作 npm下載pm2(進程守護)&#xff0c;并設置全局變量創建博客需要的數據庫與表 開啟mysql并創建數據庫test: create d…

ffmpeg使用x264編碼的配置+ ffmpeg與 x264編碼器參數完整對照表

ffmpeg使用x264編碼的配置 ffmpeg與 x264編碼器參數完整對照表 分類&#xff1a; 多媒體 2010-07-13 11:31 1072人閱讀 評論(0) 收藏 舉報 轉載自&#xff1a;扶凱[http://www.php-oa.com] 本文鏈接: http://www.php-oa.com/2009/03/09/ffmpeg_x264.html 轉帖:http://rhinghear…

javascript之變量

定義變量方式有var&#xff0c;let&#xff0c;const&#xff0c;變量的類型是根據變量的值來確定&#xff0c;變量的名稱跟大多數語言一樣&#xff0c;包含數字字母_$,不能以數字開頭且區分大小寫。 var 最早的定義變量的方式&#xff0c;可重復定義,但請避免這樣做。 var a …

Maven理解

2019獨角獸企業重金招聘Python工程師標準>>> Maven概念 參考maven入門 冰河winner Maven作為一個構建工具&#xff0c;不僅能幫我們自動化構建&#xff0c;還能夠抽象構建過程&#xff0c;提供構建任務實現;它跨平臺&#xff0c;對外提供了一致的操作接口&#xff0…

HALCON示例程序measure_circles.hdev測量圓的半徑

HALCON示例程序measure_circles.hdev測量圓的半徑 示例程序源碼&#xff08;加注釋&#xff09; 關于顯示類函數解釋 dev_update_off () read_image (Image, ‘circle_plate’) get_image_size (Image, Width, Height) dev_close_window () dev_open_window (0, 0, Width / 2…

OpenTLD 未完成 - 虎頭

TLD是一種算法的簡稱&#xff0c;原作者把它叫做Tracking-Learning-Detection。搞視覺的人看到這個名字都會嚇一跳&#xff0c;很ambitious的計劃。是09年的工作&#xff0c;不算太久&#xff0c;不過也不太新。網上關于這個的資源其實很多&#xff0c;很大程度和作者開放源代碼…

C# FileSystemWatcher 在監控文件夾和文件時的用法

********************************************************************************** 第一個文章: ********************************************************************************** 概述 最近學習FileSystemWatcher的用法,它主要是監控一個文件夾,當文件夾內的文件要…

比微軟kinect更強的視頻跟蹤算法--TLD跟蹤算法介紹

TLD (Tracking-Learning-Detection)是英國薩里大學的一個捷克籍博士生在其攻讀博士學位期間提出的一種新的單目標長時間&#xff08; long term tracking &#xff09;跟蹤算法。該算法與傳統跟蹤算法的顯著區別在于將傳統的跟蹤算法和傳統的檢測算法相結合來解決被跟蹤目標在被…

HALCON示例程序measure_circuit_width_lines_gauss.hdev電路板線寬檢測

HALCON示例程序measure_circuit_width_lines_gauss.hdev電路板線寬檢測 示例程序源碼&#xff08;加注釋&#xff09; 關于顯示類函數解釋 dev_update_off () dev_close_window () read_image (Image, ‘pcb_color’) get_image_size (Image, Width, Height) dev_open_window…

一張圖看懂混合云數據同步一站式解決方案

摘要&#xff1a; 針對不同數據庫間數據實時同步難的問題&#xff0c;日前&#xff0c;阿里云宣布推出混合云數據同步一站式解決方案&#xff0c;便于廣大云產品用戶實現實時數據同步的混合云支持&#xff0c;更為方便的是&#xff0c;該功能讓本地Oracle也能實現與云上數據庫的…

分布式性能測試

Jmeter分布式測試 在使用Jmeter進行性能測試時&#xff0c;如果并發數比較大(比如最近項目需要支持1000并發)&#xff0c;單臺電腦的配置(CPU和內存)可能無法支持&#xff0c;這時可以使用Jmeter提供的分布式測試的功能。 一、Jmeter分布式執行原理&#xff1a; 1、Jmeter分布式…

互斥鎖pthread_mutex_t的使用

1. 互斥鎖創建 有兩種方法創建互斥鎖&#xff0c;靜態方式和動態方式。POSIX定義了一個宏PTHREAD_MUTEX_INITIALIZER來靜態初始化互斥鎖&#xff0c;方法如下&#xff1a; pthread_mutex_t mutexPTHREAD_MUTEX_INITIALIZER; 在LinuxThreads實現中&#xff0c;pthread_…

無效設備解決辦法

touch /etc/ini.d/FirstLoadkill 殺死槍彈柜程序 或重啟reboot轉載于:https://www.cnblogs.com/yygsj/p/5634384.html

HALCON示例程序measure_grid.hdev使用XLD分割鍵盤輪廓

HALCON示例程序measure_grid.hdev使用XLD分割鍵盤輪廓 示例程序源碼&#xff08;加注釋&#xff09; 關于顯示類函數解釋 dev_update_off () read_image (Image, ‘keypad’) get_image_pointer1 (Image, Pointer, Type, Width, Height) dev_close_window () dev_open_window…

[BZOJ3992]序列統計

DP一下&#xff0c;設$f_{i,j}$表示生成$i$個數且乘積$\%Mj$的方案數&#xff0c;則$f_{i1,l}\sum\limits_{jk\%Ml}[k\in S]f_{i,j}$ 我們很不希望DP式中下標的位置出現乘法&#xff0c;因為這樣不好轉移&#xff0c;考慮把乘法換成加法 因為模數$M$是質數&#xff0c;所以它有…

socket,TCP/IP的理解(轉)

TCP/IP 要想理解socket首先得熟悉一下TCP/IP協議族&#xff0c; TCP/IP&#xff08;Transmission Control Protocol/Internet Protocol&#xff09;即傳輸控制協議/網間協議&#xff0c;定義了主機如何連入因特網及數據如何再它們之間傳輸的標準&#xff0c; 從字面意思來看TCP…

最小中間和

題目描述 給定一個正整數序列a1,a2,...,an&#xff0c;不改變序列中的每個元素在序列中的位置&#xff0c;把它們相加&#xff0c;并用括號記每次加法所得的和&#xff0c;稱為中間和。編程&#xff1a;找到一種方法&#xff0c;添上n-1對括號&#xff0c;加法運算依括號順序進…

HALCON示例程序measure_metal_part_extended.hdev金屬零件尺寸測量

HALCON示例程序measure_metal_part_extended.hdev金屬零件尺寸測量 示例程序源碼&#xff08;加注釋&#xff09; 關于顯示類函數解釋 dev_update_off () read_image (Image, ‘metal-parts/metal-parts-01’) init_visualization (Image, 3, ‘white’, ‘margin’, Width, …