1

这是有症状的代码:

      /*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.zove.xuggleraudio;

import com.xuggle.xuggler.IAudioSamples;
import com.xuggle.xuggler.IContainer;
import com.xuggle.xuggler.IStream;
import com.xuggle.xuggler.IStreamCoder;
import com.xuggle.xuggler.ICodec;
import com.xuggle.xuggler.IPacket;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.Line;
import javax.sound.sampled.LineUnavailableException;

import javax.sound.sampled.SourceDataLine;

/**
 * Class that represents an audio player with the expected
 * controls (start, stop, pause, resume).
 * @author Mciavilli
 */
public class Audio 
{
    //The name of the file to be played
    private final String filename;
    //Our connection to the mixer
    private final SourceDataLine mLine;
    //The index of the audio stream inside the file
    private final int audioId;
    //Xuggler media container
    private final IContainer container;
    //The stream decoder
    private final IStreamCoder streamCoder;

    /*
     * Constructor that takes a String argument
     */
    public Audio(String filename)
    {
        this.filename = filename;
        //Create Xuggler container object
        this.container = IContainer.make();
        //Open the container
        if(container.open(filename, IContainer.Type.READ, null) < 0)
            throw new IllegalArgumentException("Invalid file name: " + this.filename);
        //find the audio stream within contained streams
        this.audioId = getAudioId(container);
        //get the audio stream
        IStream stream = container.getStream(audioId);
        //get the stream decoder
        this.streamCoder = stream.getStreamCoder();
        //open the stream decoder
        if (this.streamCoder.open() < 0)
            throw new RuntimeException("could not open audio decoder for container: "
              + filename);
        //Get a pipe to the sound mixer
        this.mLine = readySoundSystem(streamCoder);
    }

    private int getAudioId(IContainer container)
    {
        //see how many streams are there
        int numStreams = container.getNumStreams();
        int audioId = -1;
        for(int i = 0; i < numStreams ; i++)
        {
            IStream stream = container.getStream(i);
            IStreamCoder streamCoder = stream.getStreamCoder();
            if(streamCoder.getCodecType() == ICodec.Type.CODEC_TYPE_AUDIO)
                audioId = i;
                break;
        }//end for statement
        //No audio stream found
        if(audioId == -1)
            throw new RuntimeException("Failed to find an audio stream in:" +
                    this.filename);

        return audioId;
    }//end method getAudioId

    private SourceDataLine readySoundSystem(IStreamCoder aAudioCoder)
    {
        AudioFormat audioFormat = new AudioFormat(aAudioCoder.getSampleRate(),
            (int)IAudioSamples.findSampleBitDepth(aAudioCoder.getSampleFormat()),
            aAudioCoder.getChannels(),
            true, /* xuggler defaults to signed 16 bit samples */
            false);
        DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat);
        try
        {
          SourceDataLine mLine = (SourceDataLine) AudioSystem.getLine(info);
          /**
           * if that succeeded, try opening the line.
           */
          mLine.open(audioFormat);
          /**
           * And if that succeed, start the line.
           */
          mLine.start();
        }
        catch (LineUnavailableException e)
        {
          throw new RuntimeException("could not open audio line");
        }
        return mLine;
      }//end method readySoundSystem
    /*
     * starts playing the file.
     * returns true if successful.
     * should be called only once per Audio object
     */ 
    public boolean start()
    {
        if(!mLine.isActive())
        {
            IPacket packet = IPacket.make();
            while(container.readNextPacket(packet) >= 0)
            {
              /*
               * Now we have a packet, let's see if it belongs to our audio stream
               */
              if (packet.getStreamIndex() == this.audioId)
              {
                /*
                 * We allocate a set of samples with the same number of channels as the
                 * coder tells us is in this buffer.
                 * 
                 * We also pass in a buffer size (1024 in our example), although Xuggler
                 * will probably allocate more space than just the 1024 (it's not important why).
                 */
                IAudioSamples samples = IAudioSamples.make(1024, this.streamCoder.getChannels());

                /*
                 * A packet can actually contain multiple sets of samples (or frames of samples
                 * in audio-decoding speak).  So, we may need to call decode audio multiple
                 * times at different offsets in the packet's data.  We capture that here.
                 */
                int offset = 0;

                /*
                 * Keep going until we've processed all data
                 */
                while(offset < packet.getSize())
                {
                  int bytesDecoded = this.streamCoder.decodeAudio(samples, packet, offset);
                  if (bytesDecoded < 0)
                    throw new RuntimeException("got error decoding audio in: " + filename);
                  offset += bytesDecoded;
                  /*
                   * Some decoder will consume data in a packet, but will not be able to construct
                   * a full set of samples yet.  Therefore you should always check if you
                   * got a complete set of samples from the decoder
                   */
                  if (samples.isComplete())
                  {
                    playSound(samples);
                  }
                }//end inner while block
              }//end inner if block
              else
              {
                /*
                 * This packet isn't part of our audio stream, so we just silently drop it.
                 */
                do {} while(false);
              }//end else block
            }//end outer while block
            //success!
            return true;
     }//end outer if block
        //The sound is already playing
        return false;
    }//end method start

    private void playSound(IAudioSamples aSamples)
      {
        /**
         * We're just going to dump all the samples into the line.
         */
        byte[] rawBytes = aSamples.getData().getByteArray(0, aSamples.getSize());
        this.mLine.write(rawBytes, 0, aSamples.getSize());
      }//end method playJavaSound

    /*
     * stops the playback
     * returns true if suucessful
     */
    public boolean stop()
    {
        if(mLine.isActive())
        {
            this.mLine.stop();
            return true;
        }
        return false;
    }

    public static void main(String args[]) throws InterruptedException 
    {
        if(args.length != 1)
            throw new IllegalArgumentException("illegal arguments passed");
        Audio audio = new Audio(args[0]);
        audio.start();
        Thread.sleep(10 * 1000);
        audio.stop();
    }

}//end class Audio

导致问题的行是第 104 行:

mLine.start();

当我检查调试器时,mLine 对象(一个 SourceDataLine 对象)很好,直到执行此行,这导致 mLine 等于“null”。

我认为这个问题和这里一样。

我还尝试使用 Clip 而不是 SourceDataLine 最终遇到了同样的问题。

有趣的是,这个问题在最初的 Xuggler 程序中并没有发生,调用 start() 并没有那么糟糕的效果。

4

2 回答 2

2

您同时拥有一个成员变量 mLine 和一个局部变量 mLine。只有后者被分配。当它超出范围时,您在调试器中看到的是成员变量,它仍然为空。

于 2012-09-19T22:47:47.367 回答
0

EJP 在上面是正确的,因为您的 mLine 变量仅适用于 try 块。(在它对我有意义之前,我必须自己在调试器中运行它。语法高亮帮助之后,为我的实例变量着色与局部变量不同。)您返回的 mLine 实际上是实例变量。

    try
    {
      SourceDataLine mLine = (SourceDataLine) AudioSystem.getLine(info);
      /**
       * if that succeeded, try opening the line.
       */
      mLine.open(audioFormat);
      /**
       * And if that succeed, start the line.
       */
      mLine.start();
    }
    catch (LineUnavailableException e)
    {
      throw new RuntimeException("could not open audio line");
    }
    return mLine;

本质上是一样的:

    try
    {
      SourceDataLine mLine = (SourceDataLine) AudioSystem.getLine(info);
      /**
       * if that succeeded, try opening the line.
       */
      mLine.open(audioFormat);
      /**
       * And if that succeed, start the line.
       */
      mLine.start();
    }
    catch (LineUnavailableException e)
    {
      throw new RuntimeException("could not open audio line");
    }
    return this.mLine;

因为返回的 mLine 位于声明它的 try 块之外。实际上,出于这个原因,将实例变量的名称重新用作局部变量是不安全的。如果您尝试使用 Eclipse 或 Idea 等 IDE 中的重构工具将变量重命名为“line”,那么它只会重命名 try 范围内的引用,从而进一步突出您的错误。

    try
    {
      SourceDataLine line = (SourceDataLine) AudioSystem.getLine(info);
      /**
       * if that succeeded, try opening the line.
       */
      line.open(audioFormat);
      /**
       * And if that succeed, start the line.
       */
      line.start();
    }
    catch (LineUnavailableException e)
    {
      throw new RuntimeException("could not open audio line");
    }
    return mLine;

如果您随后尝试手动重新键入 return 语句中的引用,您将收到编译错误。

    try
    {
      SourceDataLine line = (SourceDataLine) AudioSystem.getLine(info);
      /**
       * if that succeeded, try opening the line.
       */
      line.open(audioFormat);
      /**
       * And if that succeed, start the line.
       */
      line.start();
    }
    catch (LineUnavailableException e)
    {
      throw new RuntimeException("could not open audio line");
    }
    return line; //Will not compile!

正确的解决方法是将 return 语句放入 try 块中:

    try
    {
      SourceDataLine line = (SourceDataLine) AudioSystem.getLine(info);
      /**
       * if that succeeded, try opening the line.
       */
      line.open(audioFormat);
      /**
       * And if that succeed, start the line.
       */
      line.start();
      return line;
    }
    catch (LineUnavailableException e)
    {
      throw new RuntimeException("could not open audio line");
    }
于 2012-09-25T22:45:45.523 回答