JMF 捕獲功能讓 Java 應用能直接存取攝像頭、麥克風等硬體設備。本文通過完整代碼範例,演示音頻捕獲的標準流程:設備查詢 → DataSource 創建 → Processor 配置 → 編碼存檔,並提供生產級狀態管理輔助類。
CaptureDeviceManager.getDeviceList(format) 依格式篩選可用設備CaptureDeviceInfo.getLocator() 獲得 MediaLocatorManager.createProcessor(locator) 比 Player 更適合捕獲 (支援編碼控制)processor.start() 開始錄製| 類別 | 角色 | 關鍵方法 |
|---|---|---|
| CaptureDeviceManager | 設備註冊表 | getDeviceList(), getDevice() |
| CaptureDeviceInfo | 設備元數據 | getLocator(), getFormats() |
| Processor | 捕獲處理引擎 | configure(), setContentDescriptor(), getDataOutput() |
| DataSink | 數據輸出目的地 | open(), start(), close() |
| StateHelper | 異步狀態協調器 | configure(), realize(), prefetch(), playToEndOfMedia() |
public class CaptureAudio {
public static void main(String[] args) {
// 1. 查詢線性 PCM 立體聲 44.1kHz 16bit 設備
Vector deviceList = CaptureDeviceManager.getDeviceList(
new AudioFormat(AudioFormat.LINEAR, 44100, 16, 2));
if (deviceList.isEmpty()) System.exit(-1);
CaptureDeviceInfo di = (CaptureDeviceInfo) deviceList.firstElement();
try {
// 2. 創建 Processor
Processor p = Manager.createProcessor(di.getLocator());
StateHelper sh = new StateHelper(p);
// 3. 配置階段:設置輸出格式為 WAVE
if (!sh.configure(10000)) return;
p.setContentDescriptor(new FileTypeDescriptor(FileTypeDescriptor.WAVE));
// 4. 實現與預取
if (!sh.realize(10000)) return;
if (!sh.prefetch(10000)) return;
// 5. 獲取輸出 DataSource 並創建文件 DataSink
DataSource source = p.getDataOutput();
MediaLocator dest = new MediaLocator("file:///D:/capture/foo.wav");
DataSink fileWriter = Manager.createDataSink(source, dest);
fileWriter.open();
// 6. 可選:限制文件大小 (5MB)
StreamWriterControl swc = (StreamWriterControl)
p.getControl("javax.media.control.StreamWriterControl");
if (swc != null) swc.setStreamSizeLimit(5000000);
// 7. 啟動錄製 5 秒
fileWriter.start();
sh.playToEndOfMedia(5000); // 阻塞 5 秒或直到結束
sh.close();
fileWriter.close();
} catch (Exception e) { e.printStackTrace(); }
}
}
JMF 狀態轉換為異步事件驅動,StateHelper 封裝同步等待邏輯:
public class StateHelper implements ControllerListener {
private Player player;
private boolean configured, realized, prefetched, eom, failed, closed;
public StateHelper(Player p) { player = p; p.addControllerListener(this); }
// 同步等待配置完成
public boolean configure(int timeoutMs) {
synchronized(this) {
if (player instanceof Processor) ((Processor)player).configure();
waitFor(() -> configured || failed, timeoutMs);
return configured;
}
}
// 同步等待實現完成
public boolean realize(int timeoutMs) { /* 類似實現 */ }
// 同步等待預取完成
public boolean prefetch(int timeoutMs) { /* 類似實現 */ }
// 播放指定時長或直到結束
public boolean playToEndOfMedia(int timeoutMs) {
synchronized(this) {
eom = false; player.start();
waitFor(() -> eom || failed, timeoutMs);
return eom && !failed;
}
}
// 統一事件處理
public synchronized void controllerUpdate(ControllerEvent ce) {
if (ce instanceof ConfigureCompleteEvent) configured = true;
else if (ce instanceof RealizeCompleteEvent) realized = true;
else if (ce instanceof PrefetchCompleteEvent) prefetched = true;
else if (ce instanceof EndOfMediaEvent) eom = true;
else if (ce instanceof ControllerErrorEvent) failed = true;
else if (ce instanceof ControllerClosedEvent) closed = true;
notifyAll();
}
private void waitFor(BooleanSupplier condition, int timeoutMs) {
long start = System.currentTimeMillis();
while (!condition.getAsBoolean() && !failed) {
wait(Math.min(timeoutMs, timeoutMs - (System.currentTimeMillis() - start)));
}
}
}
getDeviceList(format) 以期望格式篩選,避免運行時格式不匹配setContentDescriptor(FileTypeDescriptor.WAVE) 指定容器格式TrackControl 可在 Configured 狀態調整編碼參數 (比特率、幀率等)StreamWriterControl 防止磁盤寫滿視頻捕獲僅需變更格式查詢條件:
new VideoFormat(VideoFormat.YUV, 320, 240, Format.NOT_SPECIFIED, 15, 24)
並將輸出容器改為 FileTypeDescriptor.QUICKTIME 或 AVI,其餘流程完全一致。
| 現象 | 可能原因 | 解決方案 |
|---|---|---|
| 無設備發現 | 驅動未安裝/權限不足 | 檢查 OS 設備管理器、JMF 註冊表 |
| configure 失敗 | 格式不支援 | 嘗試不同 VideoFormat/AudioFormat 組合 |
| 錄製文件損壞 | 未正確 close DataSink | 確保 finally 塊中 close 所有資源 |
| 音視頻不同步 | 捕獲端時間戳漂移 | 使用硬體時間戳、NTP 同步 |
掌握 JMF 捕獲流程與 StateHelper 模式,可快速構建穩健的音視頻錄製應用。下篇將探討即時串流傳輸與自定義 Codec 整合。