🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

Q&A

解決済

5回答

1995閲覧

使い終えたjavax.sound.sampled.Clipを自動でcloseしたい。

pami

総合スコア4

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

1グッド

1クリップ

投稿2021/01/03 13:49

編集2021/01/04 14:36

前提・実現したいこと

javax.sound.sampled.Clipを用いて、2秒程度の音を0.2秒間隔程度で連続して鳴らしたいと考えています。
今までは、音を再生する度に異なるClipを用意していたのですが、Clipは自動でメモリ解放されないことを知り、現在は、音を再生し終えたClipを自動でメモリ解放close()する方法を模索中です。

発生している問題・エラーメッセージ

下記のソースコードのコンストラクタにあるように、2つのデバッグ用コメント

  • System.out.println("timer stopped and clip closed");
  • System.out.println("timer and clip started");

を用意し、プログラムの動作を確認しました。期待していた結果は

  • 音を再生した直後にtimer and clip startedと出力され
  • およそ2秒後にtimer stopped and clip closedと出力される

でしたが、実際に実行すると、

  • 8回ほど連続してtimer and clip startedが出力され
  • その後5秒ほどアプリがフリーズした後
  • 8つのtimer stopped and clip closedがまとめて出力される

となりました。

使い終えたClipのメモリ解放は自動で行われているようでしたが、アプリが時折フリーズしてしまうことに悩まされています。

該当のソースコード

MusicPlayer.java

Java

1import java.util.Timer; 2import java.util.TimerTask; 3import java.io.File; 4import javax.sound.sampled.AudioFormat; 5import javax.sound.sampled.AudioInputStream; 6import javax.sound.sampled.AudioSystem; 7import javax.sound.sampled.DataLine; 8import javax.sound.sampled.Clip; 9 10// Exceptions 11import java.io.IOException; 12import javax.sound.sampled.LineUnavailableException; 13import javax.sound.sampled.UnsupportedAudioFileException; 14 15public class MusicPlayer 16{ 17 private Clip p; 18 private Timer timer; 19 20 public MusicPlayer(String filePath) 21 { 22 p = LoadClipPlayer(filePath); 23 p.start(); 24 25 timer = new Timer(true); 26 TimerTask timerTask = new TimerTask() 27 { 28 public void run() 29 { 30 p.stop(); 31 p.close(); 32 System.out.println("timer stopped and clip closed"); 33 timer.cancel(); 34 } 35 }; 36 timer.schedule(timerTask, (int)(p.getMicrosecondLength() / 1000)); 37 38 System.out.println("timer and clip started"); 39 } 40 41 private static Clip LoadClipPlayer(String filePath) { 42 43 Clip clip = null; 44 45 try { 46 AudioInputStream stream = AudioSystem.getAudioInputStream(new File(filePath)); 47 AudioFormat format = stream.getFormat(); 48 DataLine.Info dataLine = new DataLine.Info(Clip.class, format); 49 clip = (Clip)AudioSystem.getLine(dataLine); 50 clip.open(stream); 51 } 52 catch(UnsupportedAudioFileException e) { 53 e.printStackTrace(); 54 } 55 catch(LineUnavailableException e) { 56 e.printStackTrace(); 57 } 58 catch(IOException e) { 59 e.printStackTrace(); 60 } 61 62 return clip; 63 } 64}

MusicPlayerのインスタンスはMusicPlayerManagerクラスで生成され、このクラスのArrayListに格納・保持されています。

MusicPlayerManager.java

Java

1import java.util.ArrayList; 2import java.util.Queue; 3import java.util.ArrayDeque; 4import javax.sound.sampled.Clip; 5 6public class MusicPlayerManager { 7 8 private static ArrayList<String> filePathList; 9 private static ArrayList<MusicPlayer> musicPlayers; 10 private static RhythmManager rhythmManager; 11 12 // 何番にPlay指示が出たかを一時保管し、リズムに合わせて処理するためのバッファ 13 private static Queue<Integer> orderBuffer; 14 15 public MusicPlayerManager() { 16 17 filePathList = new ArrayList<String>(); 18 musicPlayers = new ArrayList<MusicPlayer>(); 19 rhythmManager = new RhythmManager(150); 20 orderBuffer = new ArrayDeque<>(); 21 } 22 23 public MusicPlayerManager(String[] filePath) { 24 25 this(); 26 for(String filePath_ : filePath) { 27 filePathList.add(filePath_); 28 } 29 } 30 31 public MusicPlayerManager(ArrayList<String> filePath) { 32 33 this(); 34 for(String filePath_ : filePath) { 35 filePathList.add(filePath_); 36 } 37 } 38 39 public void Play(int fileNum) { 40 41 // 処理待機バッファ内のfileNumの重複を避ける 42 if(!orderBuffer.contains(fileNum)) 43 orderBuffer.add(fileNum); 44 } 45 46 public void AddFile(String filePath) { 47 48 filePathList.add(filePath); 49 } 50 51 public int GetFileTotalNum() { 52 53 return filePathList.size(); 54 } 55 56 // RhythmManagerに呼び出されるメソッド 57 // リズムに合わせて一定間隔で呼び出される 58 public static void PlayAllInOrderBuffer() 59 { 60 // キューの先頭を取得し削除しながらループする 61 while(orderBuffer.peek() != null) 62 { 63 int buf = orderBuffer.poll(); 64 musicPlayers.add(new MusicPlayer(filePathList.get(buf))); 65 } 66 } 67}

試したこと

  • timer.schedule(timerTask, (int)(p.getMicrosecondLength() / 1000))の部分をtimer.scheduleAtFixedRate(timerTask, (int)(p.getMicrosecondLength() / 1000), 1000)(第3引数の1000は適当)としてみましたが、動作は大差ありませんでした。
  • javax.swing.TimerとActionListenerを用いて同様のプログラムを作成しましたが、動作は大差ありませんでした。
  • m-oguraさんの助言を受け、try-with-resourcesを用いてMusicPlayer.javaを下のように修正しましたが、タスクマネージャーでOpenJDK Platform binaryアプリ(起動中のアプリ)のメモリを監視したところ、音が鳴るたびにメモリ量は増え続けており、ストリームはclose出来ていないようでした。

Java

1import java.io.File; 2import javax.sound.sampled.AudioFormat; 3import javax.sound.sampled.AudioInputStream; 4import javax.sound.sampled.AudioSystem; 5import javax.sound.sampled.DataLine; 6import javax.sound.sampled.Clip; 7 8// Exceptions 9import java.io.IOException; 10import javax.sound.sampled.LineUnavailableException; 11import javax.sound.sampled.UnsupportedAudioFileException; 12 13public class MusicPlayer 14{ 15 public MusicPlayer(String filePath) 16 { 17 LoadClipPlayer(filePath); 18 } 19 20 private static void LoadClipPlayer(String filePath) { 21 22 try(AudioInputStream stream = AudioSystem.getAudioInputStream(new File(filePath));) 23 { 24 AudioFormat format = stream.getFormat(); 25 DataLine.Info dataLine = new DataLine.Info(Clip.class, format); 26 Clip clip = (Clip)AudioSystem.getLine(dataLine); 27 clip.open(stream); 28 clip.start(); 29 } 30 catch(UnsupportedAudioFileException e) { 31 e.printStackTrace(); 32 } 33 catch(LineUnavailableException e) { 34 e.printStackTrace(); 35 } 36 catch(IOException e) { 37 e.printStackTrace(); 38 } 39 } 40}

補足情報(FW/ツールのバージョンなど)

バージョンは以下の通りです。

>javac --version javac 15 >java --version openjdk 15 2020-09-15 OpenJDK Runtime Environment (build 15+36-1562) OpenJDK 64-Bit Server VM (build 15+36-1562, mixed mode, sharing)

どうぞよろしくお願いいたします。

TN8001👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

TN8001

2021/01/04 08:41

pamiさん 呼び出し側のコードもないと確認できません。 >今までは、音を再生する度に異なるClipを用意していた 同じ音なら巻き戻して再生できます。それとも音の種類が多いですか? >音を再生し終えたClipを自動でメモリ解放close()する方法 LineListenerで終了を検知するのが、ポピュラーなようです。 [java - do I need to close an audio Clip? - Stack Overflow](https://stackoverflow.com/questions/2792977/do-i-need-to-close-an-audio-clip/6707971#6707971
pami

2021/01/04 14:27

呼び出し側のコードを追記いたしました。ご指摘ありがとうございます。 音の種類は今のところ少ないですが、多くなる予定です。 実現したいことは、音声ファイルAが再生し終える前に再度Aを鳴らすことであり、A専用のClipを1つ用意し巻き戻して再生してしまうと、Aが最後まで再生されないうえ、音声が急に止まることによるパルスノイズが発生してしまうと考え、巻き戻しは採用しておりません。 LineListenerによる終了検知は存じておりませんでした。頂いた記事を拝読し、修正に取り掛かろうと思います。ありがとうございます。
guest

回答5

0

自己解決

TN8001さんから頂いたリンクにあるプログラム(下記)を用いると、clipのcloseは時折後回しになるものの、clipの自動close、およびclipによる音の再生は実現できているようなので、ひとまずこれをもって解決と致します。

皆様の貴重なご回答とご意見に感謝申し上げます。

Java

1class MusicPlayer { 2 public static final Vector<Clip> vector = new Vector<Clip>(); 3 static final int vector_size = 5; 4 5 // typically called before calling play() 6 static synchronized void consolidate() { 7 while (vector_size < vector.size()) { 8 Clip myClip = vector.get(0); 9 if (myClip.isRunning()) 10 break; 11 myClip.close(); 12 vector.remove(0); 13 System.out.println("close"); 14 } 15 if (vector_size * 2 < vector.size()) 16 System.out.println("warning: audio consolidation lagging"); 17 } 18 19 public static void play(final File myFile) { 20 try { 21 AudioInputStream myAudioInputStream = AudioSystem.getAudioInputStream(myFile); 22 final Clip myClip = AudioSystem.getClip(); 23 vector.add(myClip); 24 myClip.open(myAudioInputStream); 25 myClip.start(); 26 System.out.println("start"); 27 } catch (Exception myException) { 28 myException.printStackTrace(); 29 } 30 } 31}

引用:java - do I need to close an audio Clip? - Stack Overflow

投稿2021/01/09 13:21

pami

総合スコア4

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

呼び出し側とはおおもとの呼び出し方のことを言ったのですが、どうやら大がかりそうなので最低限のプログラムをこちらで作成しました。

Windows Background.wavに、特に意味はありません(2秒程度の音というだけです)

0.2秒間隔程度というのがこういう意味なのか分かりませんが、同じ音を再生する可能性があるということですので恐らくそうなのだと思いました。

UIスレッドでThread.sleepしてるので、当然UIは固まります(1.6秒ほど)

ボタンを連打すると連打回数分*8は鳴っているようですが、明らかに変な挙動(途中で止まったよう)になりました(UIの固まりも伸びる)


timer stopped and clip closedがまとめて出るのは、こちらも同様でした。
おそらくこれに該当するものと思われます。

各Timerオブジェクトと対応するのは、タイマーのタスクをすべて連続して実行するために使用される、単一のバックグラウンド・スレッドです。タイマー・タスクは迅速に実行される必要があります。タイマー・タスクの完了に時間がかかりすぎると、タイマーのタスク実行スレッドが「占有」されます。これにより後続のタスクの実行が遅れ、違反したタスクの完了時(完了した場合)に、立て続けに「まとめて」実行されることになります。

Timer (Java Platform SE 8 )

Clipcloseは、想像以上に高コストなんではないでしょうか。


メモリに関しては上下しつつ上昇トレンドですが、GCをかければ50MB程度に戻りました。

特に回答にはなっていないのですが^^;
ミニマルなサンプルで、巻き戻しも含めて再検討したらどうでしょうか。

Java

1import java.io.File; 2import java.io.IOException; 3import java.time.Instant; 4import java.util.Arrays; 5import java.util.List; 6import java.util.Timer; 7import java.util.TimerTask; 8import javax.sound.sampled.AudioFormat; 9import javax.sound.sampled.AudioInputStream; 10import javax.sound.sampled.AudioSystem; 11import javax.sound.sampled.Clip; 12import javax.sound.sampled.DataLine; 13import javax.sound.sampled.LineUnavailableException; 14import javax.sound.sampled.UnsupportedAudioFileException; 15import javax.swing.JButton; 16import javax.swing.JFrame; 17import javax.swing.JPanel; 18 19public class Main extends JFrame { 20 public static void main(String[] args) { 21 new Main().setVisible(true); 22 } 23 24 public Main() { 25 setSize(300, 100); 26 setLocationRelativeTo(null); 27 setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 28 29 List<String> sounds = Arrays.asList( 30 "C:\Windows\Media\Windows Background.wav", 31 "C:\Windows\Media\Windows Background.wav", 32 "C:\Windows\Media\Windows Background.wav", 33 "C:\Windows\Media\Windows Background.wav", 34 "C:\Windows\Media\Windows Background.wav", 35 "C:\Windows\Media\Windows Background.wav", 36 "C:\Windows\Media\Windows Background.wav", 37 "C:\Windows\Media\Windows Background.wav"); 38 39 JPanel panel = new JPanel(); 40 add(panel); 41 42 JButton button1 = new JButton("play"); 43 button1.addActionListener(ae -> { 44 for (String path : sounds) { 45 new MusicPlayer(path); 46 try { 47 Thread.sleep(200); 48 } catch (InterruptedException e) { 49 e.printStackTrace(); 50 } 51 } 52 }); 53 panel.add(button1); 54 55 JButton button2 = new JButton("gc"); 56 button2.addActionListener(ae -> System.gc()); 57 panel.add(button2); 58 } 59} 60 61class MusicPlayer { 62 private Clip p; 63 private Timer timer; 64 65 public MusicPlayer(String filePath) { 66 p = LoadClipPlayer(filePath); 67 p.start(); 68 69 timer = new Timer(true); 70 TimerTask timerTask = new TimerTask() { 71 public void run() { 72 p.stop(); 73 p.close(); 74 System.out.println(Instant.now() + " timer stopped and clip closed"); 75 timer.cancel(); 76 } 77 }; 78 timer.schedule(timerTask, (int) (p.getMicrosecondLength() / 1000)); 79 80 System.out.println(Instant.now() + " timer and clip started"); 81 } 82 83 private static Clip LoadClipPlayer(String filePath) { 84 Clip clip = null; 85 try (AudioInputStream stream = AudioSystem.getAudioInputStream(new File(filePath))) { 86 AudioFormat format = stream.getFormat(); 87 DataLine.Info dataLine = new DataLine.Info(Clip.class, format); 88 clip = (Clip) AudioSystem.getLine(dataLine); 89 clip.open(stream); 90 } catch (UnsupportedAudioFileException e) { 91 e.printStackTrace(); 92 } catch (LineUnavailableException e) { 93 e.printStackTrace(); 94 } catch (IOException e) { 95 e.printStackTrace(); 96 } 97 98 return clip; 99 } 100}

投稿2021/01/05 14:11

TN8001

総合スコア9857

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

pami

2021/01/09 13:12

言葉足らずの説明から趣旨をくみ取ってくださり、ありがとうございます。まさに仰ったようなプログラムを作成しています。 clipのcloseが高コストであることは存じ上げませんでした。確かに、頂いたプログラムを実行しても、時折closeのためにプログラムが固まるようでした。 プログラム含め、大変参考になりました。ご回答ありがとうございます。
guest

0

finallyで常にclose()してもダメでしょうか。

Java

1 private static void LoadClipPlayer(String filePath) { 2 AudioInputStream stream = null; 3 try { 4 stream = AudioSystem.getAudioInputStream(new File(filePath)); 5 AudioFormat format = stream.getFormat(); 6 DataLine.Info dataLine = new DataLine.Info(Clip.class, format); 7 Clip clip = (Clip) AudioSystem.getLine(dataLine); 8 clip.open(stream); 9 clip.start(); 10 } catch (UnsupportedAudioFileException e) { 11 e.printStackTrace(); 12 } catch (LineUnavailableException e) { 13 e.printStackTrace(); 14 } catch (IOException e) { 15 e.printStackTrace(); 16 } finally { 17 try { 18 stream.close(); 19 } catch (Exception e) { 20 e.printStackTrace(); 21 } 22 } 23 }

投稿2021/01/05 01:46

退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2021/01/05 03:54

あ・・・いま気付いたのですが、Clipを自動Closeするのなら、Clipをtry-with-resourcesしないとダメかもです。
pami

2021/01/09 13:07 編集

finallyで常にcloseすると、今度は音が一切鳴らなくなってしまったようです。clipをtry-with-resourcesしてみたのですが、こちらも音が一切鳴りませんでした。 私の推察ですが、 ・finallyで常にcloseすると、tryブロックでclipがstartした直後にcloseすることになり、音が鳴らないのではないか ・clipをtry-with-resourcesすると、確かにclipは自動的にcloseできるが、そのtryブロックを抜けるとすぐにcloseしてしまうため、結果として音が全くならないのではないか と考えました。 try-with-resourcesをうまく使えれば、clipの自動closeを実現できそうな気がしました。 ご回答ありがとうございます。
guest

0

やりたいのは↓でしょうか。
try-finallyよりもtry-with-resourcesを使おう

投稿2021/01/04 12:51

退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

pami

2021/01/04 14:34

頂いた記事を拝読し修正してみたのですが、「試したこと」の項目に追記した通り、うまくいかなかったようです。(もしかしたら私が修正したソースコードに誤りがあるかもしれません) また記事にあるように、Clipではなくストリーム再生で実装しようとすると、どうもwriteする際のwhileループによりアプリ全体にラグが発生するようだったため、ストリーム再生による実装は断念したという経緯があります。言葉足らずで申し訳ございません。 再度修正方法を模索してみます。ご回答ありがとうございます。
guest

0

10年以上Javaしてませんが、GCが動くタイミングは制御できなかったような・・・。
ちなみにGCについては、下記の記事が参考になるかもです。
JavaのGCの仕組みを整理する

投稿2021/01/04 03:01

退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

pami

2021/01/04 09:04

ご回答ありがとうございます! タイトルの書き方が悪くて申し訳なかったのですが、実現したいことはGCの制御ではなく、Clipのcloseを勝手に実行してくれる機構でした… 頂いた記事は拝読します。ありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問