Kaldi I/O C++ Level 筆記, 主要介紹以下幾點, 以及它們在 Kaldi c++ 裡如何關聯:
- 標準 low-level I/O for Kaldi Object
- XXXHolder類別: 一個符合標準 low-level I/O 的類別
- Kaldi Table Object:
<key,value>
pairs 組成的 Kaldi 格式檔案 (scp, ark), 其中 value 為 XXXHolder 類別
標準 low-level I/O for Kaldi Object
Kaldi Object 有自己的標準 I/O 介面:
因此定義了該 Kaldi Class 如何針對 istream 讀取 (ostream 寫入). 在 Kaldi 中, istream/ostream 一般是由 Input/Output(在 util/kaldi-io.h 裡定義) 這個 class 來開啟的. 那為何不用一般的 c++ iostream 開啟一個檔案呢? 這是因為 Kaldi 想要支援更多樣的檔案開啟方式, 稱為 “Extended filenames: rxfilenames and wxfilenames“.
例如可以從 stdin/stdout, pipe, file 和 file with offset 讀取寫入, 詳細請看文檔的 “Extended filenames: rxfilenames and wxfilenames” 部分.
所以 Input/Ouput Class 會自動解析 rxfilenames/wxfilenames 然後開啟 istream/ostream. 開啟後, Kaldi Object 就可以透過標準的 I/O 介面呼叫 Read/Write 方法了. 官網範例如下:
有時候會看到更精簡的寫法如下
其中 ReadKaldiObject
and WriteKaldiObject
(defined in util/kaldi-io.h) 的作用只是將 Input/Output 開啟 xfilenames 為 iostream, 並傳給 my_object 的標準 I/O 介面包裝起 來而已. 擷取 define 片段如下:
|
|
Kaldi Table Object
Table Object 不直接透過標準的 Read/Write 操作, 是因為 Table object 的構成是由 <key,value>
pairs 組成的, 而 value 才會是一個符合標準 Read/Write 操作的 object. 這種 table 所需要的讀寫可能有很多方式, 譬如 sequential access, random access 等等, 因此單純的 Read/Write 比較不能滿足需求, 更需要的是要有 Next, Done, Key, Value 等等的操作方式. 例如以下範例:
|
|
主要有幾種 table classes:
TableWriter, SequentialTableReader, RandomAccessTableReader 等等, 都定義在 util/kaldi-table.h. 我們就以 SequentialTableReader 來舉例. 上面的範例 feature_reader
就是一個 SequentialTableReader, 他的 <key,value>
pairs 中的 value 定義為 BaseFloatMatrixHolder 類別 (一個符合標準 low-level I/O 的 Kaldi Class, 等於是多一層包裝).
XXXHolder (如 KaldiObjectHolder, BasicHolder, BasicVectorHolder, BasicVectorVectorHolder, …) 指的是符合標準 low-level I/O 的 Kaldi Object, 因此這些 XXXHolder 都可以統一透過 Read/Write 來呼叫. 這些 Holder 的定義在 util/kaldi-holder.h.
另外 kaldi-holder.h 最後一行會 include kaldi-holder-inl.h. “-inl” 意思是 inline, 通常會放在相對應沒有 -inl 的 .h 最後面, 用來當作是 inline implementation 用.
SequentialTableReader 的定義在 “util/kaldi-table.h”, 擷取要介紹的片段:
Done(), Next(), Key(), and Value() 都可以從 feature_reader
看到如何使用, 應該很直覺, 而 Holder 的解釋上面說了. 剩下要說明的是這行 SequentialTableReaderImplBase<Holder> *impl_;
. 在呼叫 SequentialTableReader 的 Next() 時, 他實際上呼叫的是 impl_
的 Next(). 定義在 util/kaldi-table-inl.h 片段:
|
|
impl_
的 class 宣告是 “SequentialTableReaderImplBase”, 該類別的角色是提供一個父類別, 實際上會根據 impl_
真正的類別呼叫其對應的 Next(), 就是多型的使用. 現在假設 impl_
真正的類別是 SequentialTableReaderArchiveImpl. 我們可以在 util/kaldi-table-inl.h 看到他的 Next (line 531) 實作如下:
到這才真正看到透過 XXXHolder 使用 low-level I/O 的 Read()!
Kaldi Codes 品質很高阿, 要花不少時間讀, 果然 c++ 底子還是太差了.