首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

JMF 音視頻捕獲實戰(一):從設備發現到文件存儲完整流程

     編輯:Java 相關知識

JMF 捕獲功能讓 Java 應用能直接存取攝像頭、麥克風等硬體設備。本文通過完整代碼範例,演示音頻捕獲的標準流程:設備查詢 → DataSource 創建 → Processor 配置 → 編碼存檔,並提供生產級狀態管理輔助類。

捕獲核心流程五步驟

  1. 查詢設備:CaptureDeviceManager.getDeviceList(format) 依格式篩選可用設備
  2. 獲取定位器:CaptureDeviceInfo.getLocator() 獲得 MediaLocator
  3. 創建 Processor:Manager.createProcessor(locator) 比 Player 更適合捕獲 (支援編碼控制)
  4. 配置與實現:configure → setContentDescriptor → realize → prefetch
  5. 啟動捕獲:連接 DataSink 寫入文件,processor.start() 開始錄製

關鍵類別職責

類別角色關鍵方法
CaptureDeviceManager設備註冊表getDeviceList(), getDevice()
CaptureDeviceInfo設備元數據getLocator(), getFormats()
Processor捕獲處理引擎configure(), setContentDescriptor(), getDataOutput()
DataSink數據輸出目的地open(), start(), close()
StateHelper異步狀態協調器configure(), realize(), prefetch(), playToEndOfMedia()

完整音頻捕獲代碼:CaptureAudio.java

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(); }
    }
}

生產級狀態管理:StateHelper.java

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 防止磁盤寫滿
  • 資源釋放順序:stop → close DataSink → close Processor → removeListener

視頻捕獲擴展

視頻捕獲僅需變更格式查詢條件:

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 整合。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved