Carregando som no XNA sem o Pipeline de conteúdo

7

Estou trabalhando em um tipo de aplicativo "Game Maker" para Windows em que o usuário importa seus próprios ativos para serem usados ​​no jogo. Preciso carregar esse conteúdo em tempo de execução no lado do mecanismo.

No entanto, não quero que o usuário precise instalar nada além do tempo de execução do XNA, portanto, a chamada do pipeline de conteúdo no tempo de execução acabou.

Para imagens, estou indo bem usando o Texture2D.FromStream.

Também notei que o XNA 4.0 adicionou um método FromStream à classe SoundEffect, mas ele aceita apenas arquivos wave PCM.

Eu gostaria de suportar mais do que arquivos wave, pelo menos MP3.

Alguma recomendação? Talvez alguma biblioteca C # que decodificasse para o formato de onda PCM.

David Gouveia
fonte
Vale ressaltar que o tempo de execução do XNA não tem como decodificar MP3s para efeitos sonoros. O XNA Game Studio lida com a conversão para PCM (não compactado) ou um formato compactado (não MP3, mas esqueço exatamente qual é o formato agora) no momento da criação do conteúdo. (Música, por outro lado, fica entregue a Media Player, que pode jogar MP3s.)
Andrew Russell

Respostas:

3

Eu não tinha notado o método FromStream, é bom lembrar. Agora estou curioso para usar o FromStream vs DynamicSoundEffectInstance, já que você teoricamente poderia realizar muito do mesmo trabalho com os dois métodos.

O Google criou esta biblioteca: http://robburke.net/mle/mp3sharp/ E você também pode chamar lame.exe de dentro do seu aplicativo para decodificar o som.

Eu seria cauteloso com MP3, porém, é uma lata legal de worms. A Adobe paga uma taxa alta à Fraunhofer pelo direito de usá-la no Flash. Ogg Vorbis é um formato superior, facilmente convertido de mp3 usando software livre e de código aberto, e parece haver muito mais bibliotecas de decodificadores C # / .Net para isso.

michael.bartnett
fonte
Obrigado! Eu estava vagamente ciente das implicações legais do uso do MP3, mas isso me esclareceu. Vou tentar essas bibliotecas para ver se elas realmente podem decodificar do MP3 para o OGG (o que será feito quando o usuário importa os ativos) e depois do OGG para o PCM (em tempo de execução / carregamento). Vou relatar meus resultados.
David Gouveia
As bibliotecas ogg não serão capazes de, mas você pode decodificar com o lame e depois converter para Ogg Vorbis. Apenas não inclua LAME e faça o que o Audacity faz: "Você precisa de um decodificador de mp3, não nos importamos onde obtê-lo, podemos conhecer um lugar para obtê-lo, mas é sua responsabilidade adquiri-lo". E aviso: não sou advogado.
Michael.bartnett
Acabei de notar que o SoundEffect.FromStream espera um arquivo de onda PCM (incluindo cabeçalhos) e um formato muito específico. Decodifiquei o arquivo OGG usando uma dessas bibliotecas acima, mas não consigo fazer com que o FromStream o aceite sem gerar uma exceção. Estou começando a me perguntar se devo usar uma API de som externa, como FMOD.
David Gouveia
Isso pode valer a pena se você não se importar em segmentar o Windows Phone ou o XBLIG. Você também pode experimentar o DynamicSoundEffectInstance, apenas lança um evento solicitando amostras sempre que fica baixo.
michael.bartnett
11
Na verdade, eu usei DynamicSoundEffectInstance para fazer outras coisas antes (como essa e essa ). :) Mas também exige que você envie suas amostras em um formato muito específico (amostras intercaladas de 16 bits), que não sei se esse é o formato do meu fluxo OGG decodificado. Como eu só preciso do suporte do Windows, segui a rota FMOD. Vou postar minha solução usando FMOD em um minuto.
David Gouveia
7

Decidi tentar novamente esse problema hoje e finalmente consegui carregar um arquivo OGG em tempo de execução em um SoundEffectobjeto. Aqui está o que eu fiz! Primeiro baixe a biblioteca abaixo, que contém uma classe capaz de decodificar arquivos OGG:

Pré-requisito - Download da biblioteca

A biblioteca já tem um exemplo, mas usa DynamicSoundEffectInstancee transmite o áudio. Mas eu queria carregar tudo de uma vez em um SoundEffectobjeto comum, para que o processo fosse um pouco diferente.

Etapa 1 - decodificar arquivo

Primeiro crie uma instância OggDecodere inicialize-a com seu arquivo:

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

Etapa 2 - Obter dados decodificados

Leia todos os dados em um buffer. Estes são os dados PCM brutos decodificados do arquivo:

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

Etapa 3 - Crie o SoundEffect a partir do fluxo que contém o cabeçalho completo do arquivo wave

No entanto, o fluxo que SoundEffectrequer deve conter não apenas os dados brutos, mas também o cabeçalho completo do arquivo wave. Você pode usar este método auxiliar para escrever o cabeçalho mais os dados:

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);
}

Use esse método para gravar os dados em um fluxo que pode ser alimentado para 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);
}

Etapa 4 - Use o SoundEffect normalmente

Seu arquivo OGG agora está carregado e pode ser usado como qualquer outro SoundEffectcarregado pelo pipeline de conteúdo:

soundEffect.Play();
David Gouveia
fonte
Perfeito! O exemplo do site não funciona corretamente. Tentei uma música com 3:30 min e ela não reproduz o arquivo inteiro. Mas sua solução funcionou perfeitamente! É mais lento para carregar do que o exemplo orinal, mas pode ser tolerado.
Emir Lima
O link oggsharp diz que está obsoleto e "use NVorbis". Alguém já tentou isso? Além disso, seria bom se você listasse explicitamente os "usos" (referências) necessários para fazê-lo funcionar.
DrZ214
@EmirLima, você pode confirmar se usou a mesma versão do oggsharp como no link? Ou se era a nova versão do NVorbis?
DrZ214
@ DrZ214 Eu usei a mesma versão do site. Mas a solução do David Gouveia funciona muito bem (instanciando um SoundEffect com o Ogg decodificado).
Emir Lima
@EmirLima Sim, é exatamente isso que eu também quero: o SoundEffect carregado com um arquivo de música .ogg. Onde você conseguiu as dlls do OggSharp? No oggsharp.codeplex.com, os únicos botões de download que posso encontrar fornecem um arquivo zip com um exemplo .exe, sem dlls: /
DrZ214
2

Se você está apenas visando o Windows, descobri que a maneira mais fácil é ignorar completamente a API do XNA Audio e usar outra coisa.

Eu achei a API FMOD excelente para isso, e ela já vem com um wrapper C #. Adicionei meu próprio invólucro ao redor deles e aqui está o mínimo necessário para carregar um som de um arquivo e reproduzi-lo:

O invólucro:

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;
    }
}

E como usá-lo:

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

É claro que o invólucro é apenas o mínimo, mas deve ser simples de expandir para expor outros recursos necessários.

David Gouveia
fonte