【Java Sound】(四)捕獲音頻
- Java Sound
-
- (四)捕獲音頻
-
- 設定TargetDataLine
- 從TargetDataLine讀取資料
- 監控線路狀态
- 處理傳入的音頻
Java Sound
摘自:The Java™ Tutorials,翻譯為機翻+少量修正
(四)捕獲音頻
捕獲是指從計算機外部擷取信号的過程。音頻捕獲的常見應用是錄制,例如将麥克風輸入錄制到音頻檔案中。但是,捕獲并不等同于錄制,因為錄制意味着應用程式始終會儲存傳入的聲音資料。捕獲音頻的應用程式不一定會存儲音頻。取而代之的是,它可能會對傳入的資料進行某些處理(例如将語音轉錄為文本),但是一旦音頻緩沖結束,就立即丢棄每個音頻緩沖。
如 《樣本包概述》中所述,Java Sound API實作中的典型音頻輸入系統包括:
- 輸入端口,例如麥克風端口或輸入端口,音頻資料從其流入。
- 混合器,可以把輸入資料放置在其中。
- 一個或多個目标資料行,應用程式可以從中檢索資料。
不太會翻譯這三條,以下為原文:
- An input port, such as a microphone port or a line-in port, which feeds its incoming audio data into:
- A mixer, which places the input data in:
- One or more target data lines, from which an application can retrieve the data.
通常,一次隻能打開一個輸入端口,但是也可以使用音頻輸入混合器來混合來自多個端口的音頻。另一種情況是混音器沒有端口,而是通過網絡擷取音頻輸入。
《線路接口層次》下簡要介紹了
TargetDataLine
接口。
TargetDataLine
與
SourceDataLine
接口直接相似,在《播放音頻》中對此進行了廣泛的讨論 。回想一下該
SourceDataLine
接口包括:
- 一種将音頻發送到調音台的
方法write
- 一種确定可以在不阻塞的情況下将多少資料寫入緩沖區的
方法available
同樣,
TargetDataLine
包括:
- 一種從混音器擷取音頻的
方法read
- 一種确定在不阻塞的情況下可以從緩沖區讀取多少資料的
方法available
設定TargetDataLine
《通路音頻系統資源》中描述了擷取目标資料線的過程, 但為友善起見,在此再次列出該過程:
TargetDataLine line;
DataLine.Info info = new DataLine.Info(TargetDataLine.class,
format); // format is an AudioFormat object
if (!AudioSystem.isLineSupported(info)) {
// Handle the error ...
}
// Obtain and open the line.
try {
line = (TargetDataLine) AudioSystem.getLine(info);
line.open(format);
} catch (LineUnavailableException ex) {
// Handle the error ...
}
您可以改為調用
Mixer
的
getLine
方法,而不是
AudioSystem
的。
如本例所示,一旦獲得目标資料線,就可以通過調用
SourceDataLine
的
open
方法将其保留給應用程式使用,這與《播放音頻》中源資料線的情況完全相同 。
open
方法的單參數版本使線路的緩沖區使用預設大小。您可以根據應用程式的需要,通過調用兩個參數的版本來設定緩沖區大小:
從TargetDataLine讀取資料
線路打開後,就可以開始捕獲資料了,但是它還不處于活躍狀态。要實際開始音頻捕獲,請使用
DataLine
的
start
方法。這開始将輸入的音頻資料傳送到線路的緩沖區,以供您的應用程式讀取。您的應用程式僅在準備好開始從線路開始讀取時,才應該調用
start
;否則,浪費大量的處理時間來填充捕獲緩沖區,隻是使其溢出(即丢棄資料)。
要開始從緩沖區檢索資料,請調用
TargetDataLine
的
read
方法:
此方法嘗試從數組中的位元組位置
offset
開始,将
length
長度位元組的資料讀取到數組
b
中。該方法傳回實際讀取的位元組數。
與
SourceDataLine
的
write
方法一樣,您可以請求傳輸超出緩沖區中的實際容量的資料量,因為即使您請求傳輸很多的緩沖資料,方法也會阻塞直到傳遞了請求的資料量為止。
為避免應用程式在錄制過程中挂起,可以在循環中調用
read
方法,直到檢索到所有音頻輸入為止,如以下示例所示:
// 假設已經擷取并打開了TargetDataLine, line
ByteArrayOutputStream out = new ByteArrayOutputStream();
int numBytesRead;
byte[] data = new byte[line.getBufferSize() / 5];
// 開始捕獲音頻.
line.start();
// 在這裡,stopped是另一個線程設定的全局布爾值
while (!stopped) {
// 從TargetDataLine讀取下一個資料塊.
numBytesRead = line.read(data, 0, data.length);
// 儲存此資料塊.
out.write(data, 0, numBytesRead);
}
請注意,在此示例中,将讀取資料的位元組數組的大小設定為了線路緩沖區的大小的五分之一。如果改為将其設定為與線路的緩沖區一樣大并嘗試讀取整個緩沖區,則需要非常精确的時序,因為如果混音器從線路中讀取資料時向線路中傳輸資料,那麼資料将會被丢棄 。如此處所示,通過使用線路緩沖區大小的一部分,您的應用程式将可以更好地與混音器共享對線路緩沖區的通路。
TargetDataLine
的
read
方法有三個參數:一個位元組數組,在數組中的偏移量,以及你想要讀取的資料的位元組數。在此示例中,第三個參數隻是位元組數組的長度。該
read
方法傳回實際讀入數組的位元組數。
通常,如本例所示,您是從循環中的線路line中讀取資料。在
while
循環内,以任何适于應用程式的方式處理檢索到的每個資料塊——在這個例子中,将它們寫入了
ByteArrayOutputStream
。這裡未顯示使用單獨的線程來設定boolean型變量
stopped
來終止循環。當使用者單擊“停止”按鈕時,以及在偵聽器從該線路接收到
CLOSE
或
STOP
事件時,都可以将該布爾值設定為
true
。對于
CLOSE
事件,偵聽器是必需的,對于
STOP
事件,也建議使用偵聽器。否則,如果在某種程度上停止了該線路而沒有将其設定為
true
,
while
循環将在每次疊代中捕獲零位元組,進而運作很快并且浪費CPU周期。一個更詳盡的代碼示例将顯示如果捕獲動作再次變為活動狀态,則重新進入循環。
與源資料線一樣,可以
drain
或
flush
目标資料線。例如,如果要将輸入記錄到檔案中,則可能需要在使用者單擊“停止”按鈕時調用
drain
方法。
drain
方法将導緻混頻器的剩餘資料被傳遞到目标資料線的緩沖區。如果不清除資料,捕獲的音頻可能會在尾部被過早地截斷。
在某些情況下,您可能想重新整理
flush
資料。無論如何,如果您既不重新整理也不清空資料,則資料将留在混頻器中。這意味着當重新開始捕獲時,在新錄制的開始時會有一些剩餘的聲音,這可能是不希望的。是以,重新啟動捕獲之前重新整理
flush
目标資料線路可能很有用。
監控線路狀态
因為
TargetDataLine
接口是繼承于
DataLine
,是以目标資料線以與源資料線相同的方式生成事件。您可以注冊一個對象,以便在目标資料線路打開open,關閉close,開始start或停止stop時接收事件。有關更多資訊,請參見前面有關《監視線路狀态》的讨論 。
處理傳入的音頻
像某些源資料線一樣,某些混頻器的目标資料線也具有信号處理控件(signal-processing controls),例如增益(gain),聲像(pan),混響(reverb)或采樣率控件(sample-rate controls)。輸入端口可能也具有類似的控件,尤其是增益控件。在下一節中,您将學習如何确定某行是否具有此類控件,以及如何使用它們。