0

私は、USB モデムから呼び出しを行う Java プロジェクトに取り組んできました。アプリケーションは私のコンピューターでは完全に動作しますが、スペックの低いコンピューターで実行しようとすると、PC から呼び出している人のオーディオ ストリームが完全に消え、電話で完全に聞こえます。しかし、PC ユーザーに聞こえるはずの音声が遅延し (3 ~ 5 秒)、ホワイト ノイズが発生し、文字通り会話ができなくなります。

留意すべき点:

  • 私のコンピューターは i3 4GB RAM ノートブックで、低スペックは Pentium 4 1GB RAM デスクトップです。
  • CPU と RAM の使用率をテストしたところ、アプリケーションはコンピューターの CPU の 20 ~ 25% を消費し、低スペックのコンピューターではほぼ 100%、両方のケースで RAM から約 30 ~ 40MB を消費しました。
  • アプリケーションには通話録音機能もあり、何らかの理由で出力ファイルは完全に書き込まれます (遅延や干渉はありません)。

何が問題なのか、またはどのように解決できるのかについての手がかりはありますか?

新しいスレッドを開始した後にオーディオを処理するように作成されたクラス:(ingoing call audio)

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 )
        {
        }            
    }

アドバイスありがとう

4

2 に答える 2

1

必要な手順は次のとおりです。

  1. アプリケーションをプロファイリング/サンプリングし、実際に時間が費やされている場所を見つけます。VisualVM は強力で無料で、JDK の一部として提供されます。アプリケーションを開始します。VisualVM を起動します。VisualVM をアプリケーションに接続します。Sampler タブに移動し、CPU 使用率のサンプリングを開始します。数分後、スナップショットを撮ります。それを見てください。わからない場合は、ここに何かを投稿してください。
  2. オーディオ バッファの初期化をループの外に移動します。バッファが 20 ミリ秒の場合、バイト配列が割り当てられ、ガベージ コレクションが毎秒 50 回行われます。これは明白で簡単に実行できますが、おそらく問題は解決しません。
  3. FileOutputStreams を BufferedOutputStreams でラップします。このように:OutputStream fos = new BufferedOutputStream( new FileOutputStream("Llamadas/" + timestamp + "-" + tel + "-OUT.raw")); パフォーマンスが大幅に向上します。現在の方法では、ループのすべての反復がバッファがディスクへの書き込みを完了するのを待ちます。物理ディスクは低速であり、待機時間が長くなります。
  4. 内側の while ループを取り除きます。実際にバッファをいっぱいにすることは重要ではありません。内側の while ループがそのバッファーをいっぱいにすると、同期が取れなくなります。やりたいことは、入力ストリームから一度読み取りを試み、何かが読み取られた場合は、読み取られたものを出力ストリームに書き込むことです。write(byte[]) を呼び出す代わりに、DataOutputStream write(byte[], off, len) を呼び出します。
  5. これにはもう少し作業が必要です。dataLine に書き込んでから 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 の改善が十分でない場合は、書き込みをバックグラウンドに移動できます。最初のデータを読み取ったら、別のスレッドで書き込みを開始しますが、書き込みが完了するまで待たないでください。すぐに次のデータの読み取りを開始します。データの次のビットを取得したら、最初の書き込みが完了するのを待ってから、バックグラウンドで 2 番目の書き込みを開始します。
于 2013-09-24T16:36:02.423 に答える