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」に等しくなります。

この問題はここと同じだと思います。

SourceDataLineの代わりにClipを使用しようとしましたが、同じ問題が発生しました。

興味深いのは、この問題は元のXugglerプログラムでは発生せず、start()を呼び出してもそれほど悪影響はなかったことです。

4

2 に答える 2

2

メンバー変数 mLine とローカル変数 mLine の両方があります。後者のみが割り当てられます。スコープ外になると、デバッガーに表示されるのは、まだ null のままのメンバー変数です。

于 2012-09-19T22:47:47.367 に答える
0

mLine 変数のスコープが try ブロックのみに限定されているという点で、上記の EJP は正しいです。(意味を理解する前に、自分でデバッガーで実行する必要がありました。その後、構文の強調表示が役立ち、インスタンス変数をローカル変数とは異なる色にしました。) 返される 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!

適切な修正は、try ブロック内に 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();
      return line;
    }
    catch (LineUnavailableException e)
    {
      throw new RuntimeException("could not open audio line");
    }
于 2012-09-25T22:45:45.523 に答える