簡體   English   中英

在Java上延遲語音流

[英]Delay with voice streams on java

我一直在研究從USB調制解調器撥打電話的Java項目。 該應用程序可以在我的計算機上正常運行,但是當我嘗試在較低規格的計算機上運行該應用程序時,從PC進行呼叫的人的音頻流可以完美輸出,並且可以在所呼叫的電話中完美地聽到。 但是,PC用戶應該聽到的音頻會被延遲(3到5秒),並帶有白噪聲,實際上使進行對話變得不可能。

請注意以下幾點:

  • 我的電腦是i3 4gb RAM筆記本電腦,低配置是奔騰4 1gb RAM台式機。
  • 我測試了CPU和RAM的使用情況,該應用程序消耗了我計算機上20%到25%的cpu,在低規格版本上消耗了近100%的CPU,在兩種情況下都占用了RAM大約30至40mb。
  • 該應用程序還具有呼叫記錄功能,由於某種原因,輸出文件編寫得很完美(沒有延遲或重復)。

關於可能是什么問題或如何解決的任何線索?

啟動新線程后,處理音頻的類:(正在調用音頻)

public class SerialVoiceReader implements Runnable{

    /** The running. */
private volatile boolean running = true;

/** The in. */
DataInputStream in;

/** The af. */
AudioFormat af;

/** The samples per frame. */
private int samplesPerFrame = 160; 

/** The audio buffer size. */
private int audioBufferSize = samplesPerFrame * 2 ; //20ms delay

private String tel;

private String timestamp;

public SerialVoiceReader ( DataInputStream in,  AudioFormat af){
    this.in = in;
    this.af = af;
}

public void run (){
        try
        {
            Info infos = new Info(SourceDataLine.class, af);
            SourceDataLine dataLine  = (SourceDataLine) AudioSystem.getLine(infos);
            dataLine.open(dataLine.getFormat(),audioBufferSize *2);                     
            dataLine.start();   
// set the volume up
            if (dataLine.isControlSupported(FloatControl.Type.MASTER_GAIN)) {
                FloatControl volume = (FloatControl) dataLine.getControl(FloatControl.Type.MASTER_GAIN);
                volume.setValue(volume.getMaximum());
            }
// get a field from GUI to set as part of the file name
            tel = CallGUI.telField.getText();
            timestamp = new SimpleDateFormat("yyyyMMddHHmmss").format(Calendar.getInstance().getTime());

            // save the stream to a file to later set the header and make it .wav format
            FileOutputStream fos = new FileOutputStream("Llamadas/" + timestamp + "-" + tel + "-OUT.raw");
            // the audio buffer writing (this is the audio that goes out on the call)
            while (running){
                byte[] buffer = new byte[audioBufferSize];
                int offset = 0;
                int numRead = 0;
                while (running && (offset < buffer.length && (numRead = this.in.read(buffer, offset, buffer.length - offset)) >= 0)) 
                {
                    offset += numRead;
                }
                if(running && offset>=0){
                    dataLine.write(buffer, 0, offset);
                    fos.write(buffer);
                }
            }   
            dataLine.stop();
            dataLine.drain();
            dataLine.close();
            fos.close();

        }
        catch ( Exception e )
        {
        }          
    }

在啟動新線程后處理音頻的類:(撥出呼叫音頻)

public class SerialVoiceWriter implements Runnable{

    /** The running. */
    private volatile boolean running = true;

    /** The out. */
    DataOutputStream out;

    /** The af. */
    AudioFormat af;

    /** The samples per frame. */
    private int samplesPerFrame = 160; 

    /** The audio buffer size. */
    private int audioBufferSize = samplesPerFrame * 2; //20ms delay

    private String tel;

    private String timestamp;

    public SerialVoiceWriter ( DataOutputStream out, AudioFormat af, Boolean playMessage)
    {
        this.out = out;
        this.af = af;
    }

    public void run ()
    {   
        try
        {   
                Info infos = new Info(TargetDataLine.class, af);
                TargetDataLine dataLine  = (TargetDataLine) AudioSystem.getLine(infos);
                dataLine.open(dataLine.getFormat(),audioBufferSize*2 );
                dataLine.start();

                tel = CallGUI.telField.getText();
                timestamp = new SimpleDateFormat("yyyyMMddHHmmss").format(Calendar.getInstance().getTime());

                FileOutputStream fis = new FileOutputStream("Llamadas/" + timestamp + "-" + tel + "-IN.raw");
                while (running){
                    byte[] audioBuffer = new byte[audioBufferSize];
                    int offset = 0;
                    int numRead = 0;
                    while (running && (offset < audioBuffer.length && (numRead = dataLine.read(audioBuffer, offset, audioBuffer.length - offset)) > 0)) 
                    {
                        offset += numRead;
                    }
                    if(running && offset>=0){
                        this.out.write(audioBuffer);
                        fis.write(audioBuffer);
                    }
                }               
                    dataLine.flush();   
                    dataLine.stop();
                    dataLine.close();
                    fis.close();
                    dataLine = null;                

        }
        catch (Exception e )
        {
        }            
    }

謝謝你的建議

您需要采取的步驟是:

  1. 對應用程序進行概要分析/采樣,並找出實際花費的時間。 VisualVM功能強大且免費,並且是JDK的一部分。 啟動您的應用程序。 啟動VisualVM。 讓VisualVM連接到您的應用程序。 轉到“采樣器”選項卡,然后開始采樣CPU使用率。 幾分鍾后拍攝快照。 看它。 如果您無法解決,請在此處發布一些內容。
  2. 將音頻緩沖區初始化移出循環。 如果您的緩沖區是20ms,則將分配字節數組,並以每秒50x的速度收集垃圾。 這是顯而易見的,而且容易實現,但可能無法解決您的問題。
  3. 用BufferedOutputStreams包裝FileOutputStreams 像這樣: OutputStream fos = new BufferedOutputStream( new FileOutputStream("Llamadas/" + timestamp + "-" + tel + "-OUT.raw")); 您將獲得大大提高的性能。 現在,循環的每次迭代都等待緩沖區完成寫入磁盤的方式。 物理磁盤速度很慢,這會導致大量等待。
  4. 擺脫內部的while循環。 實際填充緩沖區並不重要。 當內部while循環填滿該緩沖區時,您將失去同步。 您要做的是嘗試從輸入流中讀取一次,如果已讀取內容,則將讀取的內容寫入輸出流。 而不是調用write(byte [])而是調用DataOutputStream write(byte [],off,len)
  5. 這將需要更多的工作:與其並行寫入,而不是依次寫入dataLine和THEN然后依次寫入fos。 他們每個人都花費一定的時間將數據寫入各自的目的地。 如果fos花費X微秒,dataLine花費Y,則當前代碼花費X + Y微秒。 如果並行執行,則最終只能等待max(X,Y)。 `

     ExecutorService es = Executors.newFixedThreadPool(2); Callable<Void>[] calls = new Callable[2]; //... your other code here... if (running && offset >= 0) { final int finalOffset = offset; Callable<Void> call1 = new Callable<Void>() { @Override public Void call() throws Exception { dataLine.write(buffer, 0, finalOffset); return null; } }; Callable<Void> call2 = new Callable<Void>() { @Override public Void call() throws Exception { fos.write(buffer); // or however you need to write. return null; } }; calls[0] = call1; calls[1] = call2; List<Callable<Void>> asList = Arrays.asList(calls); es.invokeAll(asList); // invokeAll will block until both callables have completed. } 

    `

  6. 如果#5的改進不夠好,則可以將寫入操作移到后台。 讀取完第一條數據后,您就可以在單獨的線程中開始寫操作了-但不要等待寫操作完成。 立即開始讀取下一條數據。 一旦獲得下一個數據位,然后等待第一次寫入完成,然后在后台開始第二次寫入。

我認為導致接近100%CPU的原因是罪魁禍首。 但這並不能告訴您任何具體的內容。 第一件事是,由於問題出在低端PC上,因此您可能需要檢查該設備上的音頻驅動程序是否最新。 之后,我將着眼於優化處理音頻回傳的部分代碼。 盡管較舊的PC規格較低,但我認為您嘗試實現的PC應該沒有任何問題。 我建議您在應用運行時運行性能分析器,以查看需要花費多長時間。

更新:您可以嘗試增加audioBufferSize來查看它是否有效果,20ms似乎很短。 提供的代碼僅用於從PC發送的音頻。 從電話接收的音頻怎么樣?

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM