Java - 从混音器录制

Posted

技术标签:

【中文标题】Java - 从混音器录制【英文标题】:Java - recording from mixer 【发布时间】:2011-08-25 12:54:32 【问题描述】:

我有一个与我之前的问题有关的问题。我想从混音器(扬声器)录制音频,我正在使用 javax.sound。我必须设置audioFormat,但我不知道在那里输入什么:/使用类ListMixer(我在这里找到-> http://forums.oracle.com/forums/thread.jspa?threadID=2198477&tstart=2),我写了这样的东西:http://forums.oracle.com/forums/thread.jspa?threadID=2198477&tstart=2但我没有任何信息关于采样率(未知采样率)。程序抛出此异常:

java.lang.IllegalArgumentException:不支持行:接口 TargetDataLine 支持格式 PCM_UNSIGNED 44100.0 Hz,8 位,单声道,4 字节/帧,

代码:

package sound;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import javax.sound.sampled.*;

public class AudioCapture02 extends JFrame

  boolean stopCapture = false;
  ByteArrayOutputStream byteArrayOutputStream;
  AudioFormat audioFormat;
  TargetDataLine targetDataLine;
  AudioInputStream audioInputStream;
  SourceDataLine sourceDataLine;

  public AudioCapture02()//constructor
    final JButton captureBtn =
                          new JButton("Capture");
    final JButton stopBtn = new JButton("Stop");
    final JButton playBtn =
                         new JButton("Playback");

    captureBtn.setEnabled(true);
    stopBtn.setEnabled(false);
    playBtn.setEnabled(false);

    //Register anonymous listeners
    captureBtn.addActionListener(
      new ActionListener()
        public void actionPerformed(
                                 ActionEvent e)
          captureBtn.setEnabled(false);
          stopBtn.setEnabled(true);
          playBtn.setEnabled(false);
          //Capture input data from the
          // microphone until the Stop button is
          // clicked.
          captureAudio();
        //end actionPerformed
      //end ActionListener
    );//end addActionListener()
    getContentPane().add(captureBtn);

    stopBtn.addActionListener(
      new ActionListener()
        public void actionPerformed(
                                 ActionEvent e)
          captureBtn.setEnabled(true);
          stopBtn.setEnabled(false);
          playBtn.setEnabled(true);
          //Terminate the capturing of input data
          // from the microphone.
          stopCapture = true;
        //end actionPerformed
      //end ActionListener
    );//end addActionListener()
    getContentPane().add(stopBtn);

    playBtn.addActionListener(
      new ActionListener()
        public void actionPerformed(
                                 ActionEvent e)
          //Play back all of the data that was
          // saved during capture.
          playAudio();
        //end actionPerformed
      //end ActionListener
    );//end addActionListener()
    getContentPane().add(playBtn);

    getContentPane().setLayout(new FlowLayout());
    setTitle("Capture/Playback Demo");
    setDefaultCloseOperation(EXIT_ON_CLOSE);
    setSize(250,70);
    setVisible(true);
  //end constructor

  //This method captures audio input from a
  // microphone and saves it in a
  // ByteArrayOutputStream object.
  private void captureAudio()
    try
      //Get and display a list of
      // available mixers.
      Mixer.Info[] mixerInfo = Audiosystem.getMixerInfo();
      System.out.println("Available mixers:");
      for(int cnt = 0; cnt < mixerInfo.length;
                                          cnt++)
        System.out.println(mixerInfo[cnt].
                                      getName());
      //end for loop

      //Get everything set up for capture
      audioFormat = new AudioFormat(AudioFormat.Encoding.PCM_UNSIGNED, 44100.0F, 8, 1, 4, 44100.0F,
                        false);


      DataLine.Info dataLineInfo =
                            new DataLine.Info(
                            TargetDataLine.class,
                            audioFormat);
      ListMixers lm = new ListMixers();
      lm.listAll(new PrintWriter(System.out));

      System.out.println(" AKTUALNY => "+mixerInfo[0].getName());
      Mixer mixer = AudioSystem.getMixer(mixerInfo[0]);

      //Get a TargetDataLine on the selected
      // mixer.
      targetDataLine = (TargetDataLine)
                     mixer.getLine(dataLineInfo);
      //Prepare the line for use.
      targetDataLine.open(audioFormat);
      targetDataLine.start();

      //Create a thread to capture the microphone
      // data and start it running.  It will run
      // until the Stop button is clicked.
      Thread captureThread = new CaptureThread();
      captureThread.start();
     catch (Exception e) 
      System.out.println(e);
      System.exit(0);
    //end catch
  //end captureAudio method

  //This method plays back the audio data that
  // has been saved in the ByteArrayOutputStream
  private void playAudio() 
    try
      //Get everything set up for playback.
      //Get the previously-saved data into a byte
      // array object.
      byte audioData[] = byteArrayOutputStream.
                                   toByteArray();
      //Get an input stream on the byte array
      // containing the data
      InputStream byteArrayInputStream =
             new ByteArrayInputStream(audioData);
      AudioFormat audioFormat = getAudioFormat();
      audioInputStream = new AudioInputStream(
                    byteArrayInputStream,
                    audioFormat,
                    audioData.length/audioFormat.
                                 getFrameSize());
      DataLine.Info dataLineInfo =
                            new DataLine.Info(
                            SourceDataLine.class,
                            audioFormat);
      sourceDataLine = (SourceDataLine)
               AudioSystem.getLine(dataLineInfo);
      sourceDataLine.open(audioFormat);
      sourceDataLine.start();

      //Create a thread to play back the data and
      // start it  running.  It will run until
      // all the data has been played back.
      Thread playThread = new PlayThread();
      playThread.start();
     catch (Exception e) 
      System.out.println(e);
      System.exit(0);
    //end catch
  //end playAudio

  //This method creates and returns an
  // AudioFormat object for a given set of format
  // parameters.  If these parameters don't work
  // well for you, try some of the other
  // allowable parameter values, which are shown
  // in comments following the declartions.
  private AudioFormat getAudioFormat()
    float sampleRate = 8000.0F;
    //8000,11025,16000,22050,44100
    int sampleSizeInBits = 16;
    //8,16
    int channels = 1;
    //1,2
    boolean signed = true;
    //true,false
    boolean bigEndian = false;
    //true,false
    return new AudioFormat(
                      sampleRate,
                      sampleSizeInBits,
                      channels,
                      signed,
                      bigEndian);
  //end getAudioFormat
//=============================================//

//Inner class to capture data from microphone
class CaptureThread extends Thread
  //An arbitrary-size temporary holding buffer
  byte tempBuffer[] = new byte[10000];
  public void run()
    byteArrayOutputStream =
                     new ByteArrayOutputStream();
    stopCapture = false;
    try//Loop until stopCapture is set by
        // another thread that services the Stop
        // button.
      while(!stopCapture)
        //Read data from the internal buffer of
        // the data line.
        int cnt = targetDataLine.read(tempBuffer,
                              0,
                              tempBuffer.length);
        if(cnt > 0)
          //Save data in output stream object.
          byteArrayOutputStream.write(tempBuffer,
                                      0,
                                      cnt);
        //end if
      //end while
      byteArrayOutputStream.close();
    catch (Exception e) 
      System.out.println(e);
      System.exit(0);
    //end catch
  //end run
//end inner class CaptureThread
//===================================//
//Inner class to play back the data
// that was saved.
class PlayThread extends Thread
  byte tempBuffer[] = new byte[10000];

  public void run()
    try
      int cnt;
      //Keep looping until the input read method
      // returns -1 for empty stream.
      while((cnt = audioInputStream.read(
                      tempBuffer, 0,
                      tempBuffer.length)) != -1)
        if(cnt > 0)
          //Write data to the internal buffer of
          // the data line where it will be
          // delivered to the speaker.
          sourceDataLine.write(tempBuffer,0,cnt);
        //end if
      //end while
      //Block and wait for internal buffer of the
      // data line to empty.
      sourceDataLine.drain();
      sourceDataLine.close();
    catch (Exception e) 
      System.out.println(e);
      System.exit(0);
    //end catch
  //end run
//end inner class PlayThread
//=============================================//
class ListMixers 
    PrintWriter out;

     void listAll(final PrintWriter out) 
        this.out = out;
        Mixer.Info[] aInfos = AudioSystem.getMixerInfo();
        for (int i = 0; i < aInfos.length; i++) 
            try 
                Mixer mixer = AudioSystem.getMixer(aInfos[i]);
                out.println(""+i+": "+aInfos[i].getName()+", "
                        +aInfos[i].getVendor()+", "
                        +aInfos[i].getVersion()+", "
                        +aInfos[i].getDescription());

                printLines(mixer, mixer.getSourceLineInfo());
                printLines(mixer, mixer.getTargetLineInfo());
             catch (Exception e) 
                out.println("Exception: "+e);
            
            out.println();
        
        if (aInfos.length == 0) 
            out.println("[No mixers available]");
        
    

    void printLines(Mixer mixer, Line.Info[] infos) 
        for (int i = 0; i < infos.length; i++) 
            try 
                if (infos[i] instanceof Port.Info) 
                    Port.Info info = (Port.Info) infos[i];

                    out.println("  Port " + info);
                
                if (infos[i] instanceof DataLine.Info) 
                    DataLine.Info info = (DataLine.Info) infos[i];

                    out.println("  Line " + info + " (max. " +
                                mixer.getMaxLines(info) + " simultaneously): ");
                    printFormats(info);
                
                Line line = mixer.getLine(infos[i]);

                if (!(line instanceof Clip)) 
                    try 
                        line.open();
                    
                    catch (LineUnavailableException e) 
                        out.println("LineUnavailableException when trying to open this line");
                    
                
                try 
                    printControls(line.getControls());
                
                finally 
                    if (!(line instanceof Clip)) 
                        line.close();
                    
                
            
            catch (Exception e) 
                out.println("Exception: " + e);
            
            out.println();
        
    

    void printFormats(DataLine.Info info) 
        AudioFormat[] formats = info.getFormats();
        for (int i = 0; i < formats.length; i++) 
            out.println("  "+i+": "+formats[i]
                    +" ("+formats[i].getChannels()+" channels, "
                    +"frameSize="+formats[i].getFrameSize()+", "
                    +(formats[i].isBigEndian()?"big endian":"little endian")
                    +")");
        
        if (formats.length == 0) 
            out.println("  [no formats]");
        
        out.println();
    

    void printControls(Control[] controls) 
        for (int i = 0; i<controls.length; i++) 
            printControl("    ", "Controls["+i+"]: ", controls[i]);
        
        if (controls.length == 0) 
            out.println("    [no controls]");
        
        out.println();
    

    void printControl(String indent, String id, Control control) 
        if (control instanceof BooleanControl) 
            BooleanControl ctrl = (BooleanControl) control;
            out.println(indent+id+"BooleanControl: "+ctrl);
         else if (control instanceof CompoundControl) 
            CompoundControl ctrl = (CompoundControl) control;
            Control[] ctrls = ctrl.getMemberControls();
            out.println(indent+id+"CompoundControl: "+control);
            for (int i=0; i<ctrls.length; i++) 
                printControl(indent+"  ", "MemberControls["+i+"]: ", ctrls[i]);
            
         else if (control instanceof EnumControl) 
            EnumControl ctrl = (EnumControl) control;
            Object[] values = ctrl.getValues();
            Object value = ctrl.getValue();
            out.println(indent+id+"EnumControl: "+control);
            for (int i=0; i<values.length; i++) 
                if (values[i] instanceof Control) 
                    printControl(indent+"  ", "Values["+i+"]: "+((values[i]==value)?"*":""), (Control) values[i]);
                 else 
                    out.println(indent+"  Values["+i+"]: "+((values[i]==value)?"*":"")+values[i]);
                
            
         else if (control instanceof FloatControl) 
            FloatControl ctrl = (FloatControl) control;
            out.println(indent+id+"FloatControl: "+ctrl);
         else 
            out.println(indent+id+"Control: "+control);
        
    

//end outer class AudioCapture02.java

【问题讨论】:

在哪里可以找到解决方案? 【参考方案1】:

您将使用您创建的 AudioFormat 获得 TargetDataLine。这不能保证有效。您必须首先使用AudioSystem.isLineSupported(Info info) 方法查询混音器以检查它是否支持您想要的音频格式。

就我个人而言,我觉得这很麻烦。您需要查询系统上的 Mixer 以确定它们是否支持您想要的 AudioFormat。

下面的函数将获取数据线类支持格式的向量。调用它使用

Vector<AudioFormat> formats = getSupportedFormats(TargetDataLine.class);

Vector<AudioFormat> formats = getSupportedFormats(SourceDataLine.class);

这段代码可能需要一些调试;我不得不删除我的一些特定于应用程序的东西以使其独立...

public Vector<AudioFormat> getSupportedFormats(Class<?> dataLineClass) 
    /*
     * These define our criteria when searching for formats supported
     * by Mixers on the system.
     */
    float sampleRates[] =  (float) 8000.0, (float) 16000.0, (float) 44100.0 ;
    int channels[] =  1, 2 ;
    int bytesPerSample[] =  2 ;

    AudioFormat format;
    DataLine.Info lineInfo;

    SystemAudioProfile profile = new SystemAudioProfile(); // Used for allocating MixerDetails below.
    Vector<AudioFormat> formats = new Vector<AudioFormat>();

    for (Mixer.Info mixerInfo : AudioSystem.getMixerInfo()) 
        for (int a = 0; a < sampleRates.length; a++) 
            for (int b = 0; b < channels.length; b++) 
                for (int c = 0; c < bytesPerSample.length; c++) 
                    format = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
                            sampleRates[a], 8 * bytesPerSample[c], channels[b], bytesPerSample[c],
                            sampleRates[a], false);
                    lineInfo = new DataLine.Info(dataLineClass, format);
                    if (AudioSystem.isLineSupported(lineInfo)) 
                        /*
                         * TODO: To perform an exhaustive search on supported lines, we should open
                         * TODO: each Mixer and get the supported lines. Do this if this approach
                         * TODO: doesn't give decent results. For the moment, we just work with whatever
                         * TODO: the unopened mixers tell us.
                         */
                        if (AudioSystem.getMixer(mixerInfo).isLineSupported(lineInfo)) 
                            formats.add(format);
                        
                    
                
            
        
    
    return formats;

【讨论】:

出了点问题:/ 我无法将 AudioFormat 与混音器匹配,因此我修改了您的函数,因此它只检查首先找到混音器(一般)。函数找到 3 种音频格式,但仍然无法正常工作:/【参考方案2】:

可以直接获取所有支持的Lines及其AudioFormats。我为系统上默认的MixerSourceDataLines 执行此操作,您可以轻松地编辑代码以获得任何Mixer 支持的任何类型的LinesAudioFormats

Mixer mixer = AudioSystem.getMixer(null); // default mixer
mixer.open();

System.out.printf("Supported SourceDataLines of default mixer (%s):\n\n", mixer.getMixerInfo().getName());
for(Line.Info info : mixer.getSourceLineInfo()) 
    if(SourceDataLine.class.isAssignableFrom(info.getLineClass())) 
        SourceDataLine.Info info2 = (SourceDataLine.Info) info;
        System.out.println(info2);
        System.out.printf("  max buffer size: \t%d\n", info2.getMaxBufferSize());
        System.out.printf("  min buffer size: \t%d\n", info2.getMinBufferSize());
        AudioFormat[] formats = info2.getFormats();
        System.out.println("  Supported Audio formats: ");
        for(AudioFormat format : formats) 
            System.out.println("    "+format);
//          System.out.printf("      encoding:           %s\n", format.getEncoding());
//          System.out.printf("      channels:           %d\n", format.getChannels());
//          System.out.printf(format.getFrameRate()==-1?"":"      frame rate [1/s]:   %s\n", format.getFrameRate());
//          System.out.printf("      frame size [bytes]: %d\n", format.getFrameSize());
//          System.out.printf(format.getSampleRate()==-1?"":"      sample rate [1/s]:  %s\n", format.getSampleRate());
//          System.out.printf("      sample size [bit]:  %d\n", format.getSampleSizeInBits());
//          System.out.printf("      big endian:         %b\n", format.isBigEndian());
//          
//          Map<String,Object> prop = format.properties();
//          if(!prop.isEmpty()) 
//              System.out.println("      Properties: ");
//              for(Map.Entry<String, Object> entry : prop.entrySet()) 
//                  System.out.printf("      %s: \t%s\n", entry.getKey(), entry.getValue());
//              
//          
        
        System.out.println();
     else 
        System.out.println(info.toString());
    
    System.out.println();


mixer.close();

我得到这样的输出:

interface SourceDataLine supporting 8 audio formats, and buffers of at least 32 bytes
  max buffer size:  -1
  min buffer size:  32
  Supported Audio formats: 
    PCM_UNSIGNED unknown sample rate, 8 bit, mono, 1 bytes/frame, 
    PCM_SIGNED unknown sample rate, 8 bit, mono, 1 bytes/frame, 
    PCM_SIGNED unknown sample rate, 16 bit, mono, 2 bytes/frame, little-endian
    PCM_SIGNED unknown sample rate, 16 bit, mono, 2 bytes/frame, big-endian
    PCM_UNSIGNED unknown sample rate, 8 bit, stereo, 2 bytes/frame, 
    PCM_SIGNED unknown sample rate, 8 bit, stereo, 2 bytes/frame, 
    PCM_SIGNED unknown sample rate, 16 bit, stereo, 4 bytes/frame, little-endian
    PCM_SIGNED unknown sample rate, 16 bit, stereo, 4 bytes/frame, big-endian


interface Clip supporting 8 audio formats, and buffers of at least 32 bytes

【讨论】:

以上是关于Java - 从混音器录制的主要内容,如果未能解决你的问题,请参考以下文章

音频处理使用 Adobe Audition 录制电脑内部声音 ( 启用电脑立体声混音 | Adobe Audition 中设置音频设备 | Adobe Audition 内录 )

获取另一个 Java 程序的声音

使用 Java Sound API 进行多通道 USB 录音?

Unity录音

如何在 mac os x 上从通过 USB 连接的多轨混音器获取输入

使用音频单元(混音器主机)从 iPod 库而不是预先选择的声音文件中播放。