7

SoundManager簡単に音を管理するためのクラスがあります。基本的に:

public class SoundManager {
    public static class Sound {
        private Clip clip; // for internal use

        public void stop() {...}
        public void start() {...}
        public void volume(float) {...}
        // etc.
    }

    public Sound get(String filename) {
        // Gets a Sound for the given clip
    }

    // moar stuff
}

これのほとんどの用途は次のとおりです。

sounds.get("zap.wav").start();

私が理解しているように、これは新しく作成されたサウンドへの参照をメモリに保持するべきではなく、ガベージコレクションをかなり迅速に行う必要があります。ただし、短いサウンド ファイル (108 KB、なんと 00:00:00 秒、実際には約 0.8 秒でクロックイン) では、次のようになるまでに約 2100 回の呼び出ししかできませんOutOfMemoryError

# Java ランタイム環境が続行するためのメモリが不足しています。
# ネイティブ メモリ割り当て (malloc) は、C:\BUILD_AREA\jdk6_34\hotspot\src\share\vm\prims\jni.cpp の jbyte に 3874172 バイトを割り当てることができませんでした
# 詳細情報を含むエラー レポート ファイルは次のように保存されます:
# [path ]

コンストラクターに以下を追加してprivate static final Vector<WeakReference<Sound>>、クラスにa を実装しようとしました。SoundManager.Sound

// Add to the sound list.
allSounds.add(new WeakReference<SoundManager.Sound>(this));
System.out.println(allSounds.size());

これにより、プログラムの最後まで反復処理を行い、すべてのサウンドを停止することもできます (アプレットでは、これは常に自動的に行われるとは限りません)。

ただし、同じことが発生する前に、まだ約 10 回の呼び出ししか取得できませんOutOfMemoryError

問題がある場合は、ファイル名ごとにファイルの内容を としてキャッシュしていますbyte[]が、これはファイルごとに 1 回しか行われないため、蓄積されません。

では、なぜこれらの参照が保持されているのでしょうか? また、ヒープ サイズを増やすだけでそれを停止するにはどうすればよいでしょうか?


編集:「詳細情報を含むエラー レポート」には、32 行目に次の内容が含まれています。

Java frames: (J=compiled Java code, j=interpreted, Vv=VM code)
J  com.sun.media.sound.DirectAudioDevice.nWrite(J[BIIIFF)I
J  com.sun.media.sound.DirectAudioDevice$DirectDL.write([BII)I
j  com.sun.media.sound.DirectAudioDevice$DirectClip.run()V+163
j  java.lang.Thread.run()V+11
v  ~StubRoutines::call_stub

これは、この問題が完全に制御不能であることを意味しますか? javasound には「クールダウン」する時間が必要ですか? デバッグの目的で、これらの音を 300/秒で吐き出しています。


私のJavaSoundの使用に関する詳細を編集してください。

を初めて呼び出すとsounds.get("zap.wav")、「zap.wav」が以前にロードされていないことがわかります。ファイルを a に書き込み、保存しますbyte[]。その後、以前にキャッシュされていたかのように処理されます。

最初とその後のすべての時間 (キャッシング後) で、メソッドはbyte[]メモリに保存された を取得し、新しい を作成し、そのストリームByteArrayInputStreamで使用します。AudioSystem.getAudioInputStream(bais)これらのストリームがメモリを保持している可能性はありますか? Sound(したがってClip)が収集されると、ストリームも閉じられると思います。


getリクエストごとにメソッドを編集します。これはpublic Sound get(String name)

  • byteCacheですHashMap<String, byte[]>
  • clazzですClass<?>

byteCacheHashMap<String, byte[]>ありclazzClass<?>

try {
    // Create a clip.
    Clip clip = AudioSystem.getClip();

    // Find the full name.
    final String fullPath = prefix + name;

    // See what we have already.
    byte[] theseBytes = byteCache.get(fullPath);

    // Have we found the bytes yet?
    if (theseBytes == null) {
        // Nope. Read it in.
        InputStream is = clazz.getResourceAsStream(fullPath);

        // Credit for this goes to Evgeniy Dorofeev:
        // http://stackoverflow.com/a/15725969/732016

        // Output to a temporary stream.
        ByteArrayOutputStream baos = new ByteArrayOutputStream();

        // Loop.
        for (int b; (b = is.read()) != -1;) {
            // Write it.
            baos.write(b);
        }

        // Close the input stream now.
        is.close();

        // Create a byte array.
        theseBytes = baos.toByteArray();

        // Put in map for later reference.
        byteCache.put(fullPath, theseBytes);
    }

    // Get a BAIS.
    ByteArrayInputStream bais = new ByteArrayInputStream(theseBytes);

    // Convert to an audio stream.
    AudioInputStream ais = AudioSystem.getAudioInputStream(bais);

    // Open the clip.
    clip.open(ais);

    // Create a new Sound and return it.
    return new Sound(clip);
} catch (Exception e) {
    // If they're watching, let them know.
    e.printStackTrace();

    // Nothing to do here.
    return null;
}

ヒープ プロファイリング後に編集します。

クラッシュの約 5 秒前にヒープダンプされました。これは次のことを示しています。

ヒープダンプチャート

問題の疑い #1:

「」によってロードされた「com.sun.media.sound.DirectAudioDevice$DirectClip」の 2,062 インスタンスは、230,207,264 (93.19%) バイトを占有します。

キーワード com.sun.media.sound.DirectAudioDevice$DirectClip

これらのClipオブジェクトはオブジェクトによって強く参照されSoundますが、Soundオブジェクトは で弱く参照されるだけVector<WeakReference<Sound>>です。

Clipまた、各オブジェクトに のコピーが含まれていることもわかりますbyte[]


フィルのコメントごとに編集:

私はこれを変更しました:

// Convert to an audio stream.
AudioInputStream ais = AudioSystem.getAudioInputStream(bais);

// Open the clip.
clip.open(ais);

これに:

// Convert to an audio stream.
AudioInputStream ais = AudioSystem.getAudioInputStream(bais);

// Close the stream to prevent a memory leak.
ais.close();

// Open the clip.
clip.open(ais);
clip.close();

これでエラーは修正されますが、サウンドはまったく再生されません。

省略clip.close()してもエラーが発生します。エラーがまだ発生ais.close()した後に移動すると。clip.open

LineListenerまた、作成時にクリップに を追加しようとしました。

@Override
public void update(LineEvent le) {
    if (le.getType() == LineEvent.Type.STOP) {
        if (le.getLine() instanceof Clip) {
            System.out.println("draining");
            ((Clip)le.getLine()).drain();
        }
    }
}

クリップが終了または停止するたびに (つまり、開始後 1 秒あたり 30 回以上)、「排水中」というメッセージが表示されますが、それでも同じエラーが発生します。に置き換えdrainflushも効果はありません。を使用closeすると、後で行を開くことができなくなります ( をリッスンしSTARTて呼び出しopenている場合でもstart)。

4

3 に答える 3

2

問題は、オーディオ ストリームを明示的に閉じていないことだと思います。それらを閉じるためにガベージコレクターに頼るべきではありません。

割り当ては、通常の Java 割り当てではなく、ネイティブ割り当てで失敗しているようです。この場合、「OOME をスローする前に GC が実行される」という通常の動作が適用されると思われます。

いずれにせよ、ストリームを明示的に閉じることをお勧めします (finallyまたは Java 7tryとリソースを使用)。これは、外部リソースまたはオフヒープ メモリ バッファーを含むあらゆる種類のストリームに適用されます。

于 2013-04-01T05:11:09.053 に答える
1

これが完全にずれている場合は申し訳ありませんが、コードを何気なく読んだだけでは識別できない 2 つの基本事項を確認したいと思います。

1) 音声ファイルの長さは? 特定のファイル数、ミリ秒単位の長さ、サンプル レート、およびエンコード (例: 16 ビット、ステレオ) が与えられると、予想される消費メモリ量を計算できるはずです。その金額は?

2) クリップでよくある間違いは、既存のクリップを再利用する代わりに、再生するたびに再作成してしまうことです。コメントに「sounds.get("zap.wav").start()」という行があり、この根本的なエラーを犯しているのではないかと思います。クリップは 1 回だけ作成し、もう一度再生したい場合はフレーム位置を 0 にリセットしてください。猛烈な速度でクリップを再作成している場合、再生ごとに PCM データの独自のコピーを持つ追加のオブジェクトが作成されるため、メモリがすぐにいっぱいになります。

また、あるコメンターが述べているように、さまざまなストリームを閉じることが重要です。そうしないと、メモリ リークが発生します。

于 2013-04-01T18:22:14.847 に答える
0

別のアプローチ: Java の Clip の使用を放棄し、独自のものを作成します。これは私がしました。データをメモリに格納するオブジェクトと、2 つの異なるタイプの再生用にストレージに「カーソル」を提供する 2 つのオブジェクトを作成しました。1 つはループ再生用で、もう 1 つはオーバーラップ再生用です。どちらも異なる再生速度で実行するように設定できるため、再生を高速化または低速化することで異なる効果を得ることができます。どちらも出力を私が書いたミキサーに送り、そこでデータは単一の SourceDataLine に統合されます。

コードはここに示され、最初の投稿にリンクされている jar に含まれています: http://www.java-gaming.org/topics/simple-audio-mixer-2nd-pass/27943/view.html

もう少し作業を再開して、おそらく GitHub に載せるのを楽しみにしています。

また、TinySound は非常に有能なサウンド マネージャーです。ここでそれについて学ぶことができます。このアプローチは私が行っているものと非常に似ており、単一の出力にミキシングします。TinySound は Ogg などのサポートを提供します。可変速再生には対応していないと思います。

http://www.java-gaming.org/topics/need-a-really-simple-library-for-playing-sounds-and-music-try-tinysound/25974/view.html

于 2013-04-03T19:56:07.827 に答える