Laden von Sound in XNA ohne die Inhaltspipeline

7

Ich arbeite an einer Anwendung vom Typ "Game Maker" für Windows, bei der der Benutzer seine eigenen Assets importiert, um sie im Spiel zu verwenden. Ich muss in der Lage sein, diesen Inhalt zur Laufzeit auf der Motorseite zu laden.

Ich möchte jedoch nicht, dass der Benutzer mehr als die XNA-Laufzeit installieren muss, sodass das Aufrufen der Inhaltspipeline zur Laufzeit nicht möglich ist.

Für Bilder geht es mir gut mit Texture2D.FromStream.

Ich habe auch festgestellt, dass XNA 4.0 der SoundEffect-Klasse eine FromStream-Methode hinzugefügt hat, aber nur PCM-Wave-Dateien akzeptiert.

Ich möchte jedoch mehr als nur Wave-Dateien unterstützen, zumindest MP3.

Irgendwelche Empfehlungen? Vielleicht eine C # -Bibliothek, die die Dekodierung in das PCM-Wellenformat durchführen würde.

David Gouveia
quelle
Es ist erwähnenswert, dass die XNA-Laufzeit selbst keine Möglichkeit hat, MP3s für Soundeffekte zu dekodieren. XNA Game Studio übernimmt die Konvertierung in PCM (unkomprimiert) oder ein komprimiertes Format (nicht MP3, aber ich vergesse genau, welches Format gerade vorliegt) zur Erstellung von Inhalten. (Musik hingegen wird an den Media Player übergeben, der MP3s abspielen kann.)
Andrew Russell

Antworten:

3

Ich hatte die FromStream-Methode nicht bemerkt, das ist gut zu merken. Ich bin jetzt neugierig auf die Verwendung von FromStream vs DynamicSoundEffectInstance, da Sie theoretisch mit beiden Methoden viel von der gleichen Arbeit erledigen könnten.

Google hat diese Bibliothek aufgedeckt : http://robburke.net/mle/mp3sharp/ Und Sie können möglicherweise auch lame.exe aus Ihrer App heraus aufrufen, um den Sound zu dekodieren.

Ich wäre jedoch vorsichtig mit MP3, es ist eine legale Dose Würmer. Adobe zahlt Fraunhofer eine hohe Gebühr für das Recht, es in Flash zu verwenden. Ogg Vorbis ist ein überlegenes Format, das mit kostenloser Open-Source-Software leicht aus MP3 konvertiert werden kann, und es scheint viel mehr C # /. Net-Decoder-Bibliotheken dafür zu geben.

michael.bartnett
quelle
Vielen Dank! Ich war mir der rechtlichen Auswirkungen der Verwendung von MP3 vage bewusst, aber das hat es für mich geklärt. Ich werde diesen Bibliotheken einen Versuch geben, um zu sehen, ob sie wirklich von MP3 in OGG (was beim Importieren der Assets durch den Benutzer erfolgt) und dann von OGG in PCM (zur Laufzeit / Ladezeit) dekodieren können. Ich werde mit meinen Ergebnissen berichten.
David Gouveia
Die Ogg-Bibliotheken können das nicht, aber Sie können mit Lame dekodieren und dann in Ogg Vorbis konvertieren. Schließen Sie LAME einfach nicht ein und tun Sie wie Audacity: "Sie benötigen einen MP3-Decoder. Es ist uns egal, wo Sie ihn bekommen. Wir kennen möglicherweise einen Ort, an dem Sie ihn bekommen können, aber es liegt in Ihrer Verantwortung, ihn zu erwerben." Und Haftungsausschluss: Ich bin kein Anwalt.
michael.bartnett
Ich habe gerade bemerkt, dass SoundEffect.FromStream eine PCM-Wave- Datei (einschließlich Header) und ein sehr spezifisches Format erwartet . Ich habe die OGG-Datei mit einer der oben genannten Bibliotheken dekodiert, aber ich kann FromStream scheinbar nicht dazu bringen, sie zu akzeptieren, ohne eine Ausnahme auszulösen. Ich frage mich, ob ich stattdessen eine externe Sound-API wie FMOD verwenden soll.
David Gouveia
Das könnte sich lohnen, wenn Sie sich nicht für Windows Phone oder XBLIG interessieren. Sie können auch DynamicSoundEffectInstance ausprobieren. Es wird nur ein Ereignis ausgelöst, das jedes Mal nach Samples fragt, wenn es zur Neige geht.
michael.bartnett
1
Ich habe DynamicSoundEffectInstance bereits verwendet, um einige andere Dinge zu erstellen (wie dies und das ). :) Aber es erfordert auch, dass Sie Ihre Samples in einem ganz bestimmten Format (verschachtelte 16-Bit-Samples) einreichen, von dem ich nicht weiß, ob dies das Format meines dekodierten OGG-Streams ist. Da ich nur Windows-Unterstützung benötige, bin ich den FMOD-Weg gegangen. Ich werde meine Lösung in einer Minute mit FMOD veröffentlichen.
David Gouveia
7

Ich habe beschlossen, dieses Problem heute noch einmal zu versuchen, und es endlich geschafft, eine OGG-Datei zur Laufzeit in ein SoundEffectObjekt zu laden . Folgendes habe ich getan! Laden Sie zuerst die folgende Bibliothek herunter, die eine Klasse enthält, die OGG-Dateien dekodieren kann:

Voraussetzung - Bibliothek herunterladen

Die Bibliothek hat bereits ein Beispiel, verwendet DynamicSoundEffectInstanceund streamt jedoch das Audio. Aber ich wollte alles auf einmal in ein normales SoundEffectObjekt laden, damit der Prozess etwas anders war.

Schritt 1 - Datei dekodieren

Erstellen Sie zuerst eine Instanz von OggDecoderund initialisieren Sie sie mit Ihrer Datei:

decoder = new OggDecoder();
decoder.Initialize(TitleContainer.OpenStream(@"sound.ogg"));

Schritt 2 - Dekodierte Daten abrufen

Lesen Sie alle Daten in einen Puffer. Dies sind die decodierten PCM-Rohdaten der Datei:

byte[] data = decoder.SelectMany(chunk => chunk.Bytes.Take(chunk.Length)).ToArray();

Schritt 3 - Erstellen Sie SoundEffect aus einem Stream mit dem vollständigen Wave-Datei-Header

Der SoundEffecterforderliche Stream muss jedoch nicht nur die Rohdaten enthalten, sondern auch den vollständigen Header der Wave-Datei. Mit dieser Hilfsmethode können Sie den Header und die Daten schreiben:

private static void WriteWave(BinaryWriter writer, int channels, int rate, byte[] data)
{
    writer.Write(new char[4] { 'R', 'I', 'F', 'F' });
    writer.Write((int)(36 + data.Length));
    writer.Write(new char[4] { 'W', 'A', 'V', 'E' });

    writer.Write(new char[4] { 'f', 'm', 't', ' ' });
    writer.Write((int)16);
    writer.Write((short)1);
    writer.Write((short)channels);
    writer.Write((int)rate);
    writer.Write((int)(rate * ((16 * channels) / 8)));
    writer.Write((short)((16 * channels) / 8));
    writer.Write((short)16);

    writer.Write(new char[4] { 'd', 'a', 't', 'a' });
    writer.Write((int)data.Length);
    writer.Write(data);
}

Verwenden Sie diese Methode, um die Daten in einen Stream zu schreiben, dem Folgendes zugeführt werden kann SoundEffect.FromStream:

using (MemoryStream stream = new MemoryStream())
using(BinaryWriter writer = new BinaryWriter(stream))
{
    WriteWave(writer, decoder.Stereo ? 2 : 1, decoder.SampleRate, data);
    stream.Position = 0;
    soundEffect = SoundEffect.FromStream(stream);
}

Schritt 4 - Verwenden Sie den SoundEffect wie gewohnt

Ihre OGG-Datei ist jetzt geladen und kann wie jede andere Datei verwendet werden, SoundEffectdie über die Inhaltspipeline geladen wird:

soundEffect.Play();
David Gouveia
quelle
Perfekt! Das Beispiel der Site funktioniert nicht richtig. Ich habe ein Lied mit 3:30 Minuten ausprobiert und es wird nicht die gesamte Datei abgespielt. Aber Ihre Lösung hat perfekt funktioniert! Es ist langsamer zu laden als das ursprüngliche Beispiel, aber es kann toleriert werden.
Emir Lima
Der Link oggsharp besagt, dass er veraltet ist und stattdessen "NVorbis verwenden". Hat das jemand versucht? Es wäre auch gut, wenn Sie explizit die "Verwendungen" (Referenzen) auflisten würden, die erforderlich sind, damit es funktioniert.
DrZ214
@EmirLima können Sie bestätigen, ob Sie dieselbe Version von oggsharp wie im Link verwendet haben? Oder ob es die neue NVorbis-Version war?
DrZ214
@ DrZ214 Ich habe die gleiche Version der Seite verwendet. Aber die Lösung des David Gouveia funktioniert sehr gut (Instanziieren eines SoundEffect mit dem dekodierten Ogg).
Emir Lima
@EmirLima Ja, genau das möchte ich auch: SoundEffect mit einer .ogg-Musikdatei geladen. Woher hast du die OggSharp-DLLs? Unter oggsharp.codeplex.com geben die einzigen Download-Schaltflächen, die ich finden kann, eine Zip-Datei mit einem Beispiel .exe, keine DLLs: /
DrZ214
2

Wenn Sie nur auf Windows abzielen, ist es für mich am einfachsten, die XNA Audio-API vollständig zu umgehen und etwas anderes zu verwenden.

Ich fand die FMOD-API großartig dafür und sie wird sogar bereits mit einem C # -Wrapper geliefert. Ich habe meinen eigenen Wrapper um ihren hinzugefügt und hier ist das absolute Minimum, das Sie benötigen, um einen Sound aus einer Datei zu laden und abzuspielen:

Der Wrapper:

namespace YourNamespace
{
    using System;
    using FMOD;

    public class SoundSystem
    {
        public SoundSystem()
        {
            RESULT result = Factory.System_Create(ref _system);
            if(result != RESULT.OK) 
                throw new Exception("Create SoundSystem Failed");

            uint version = 0;
            result = System.getVersion(ref version);
            if (result != RESULT.OK || version < VERSION.number)
                throw new Exception("Create SoundSystem Failed");

            result = System.init(32, INITFLAGS.NORMAL, (IntPtr)null);
            if (result != RESULT.OK)
                throw new Exception("Create SoundSystem Failed");
        }

        public System System 
        {
            get { return _system; }
        }

        private readonly System _system;
    }

    public class Sound
    {
        public Sound(SoundSystem system, string path)
        {
            _system = system;
            RESULT result = system.System.createSound(path, MODE.HARDWARE, ref _sound);
            if (result != RESULT.OK)
                throw new Exception("Create Sound Failed");
        }

        public void Play()
        {
            Channel channel = null;
            RESULT result = _system.System.playSound(CHANNELINDEX.FREE, _sound, false, ref channel);
            if (result != RESULT.OK)
                throw new Exception("Play Sound Failed");
        }

        private readonly SoundSystem _system;
        private readonly FMOD.Sound _sound;
    }
}

Und wie man es benutzt:

SoundSystem system = new SoundSystem();
Sound sound = new Sound(system, "song.mp3");
sound.Play();

Natürlich ist dieser Wrapper nur das absolute Minimum, sollte aber einfach zu erweitern sein, um alle anderen Funktionen verfügbar zu machen, die Sie benötigen.

David Gouveia
quelle