Tocar áudio de um stream usando C #

99

Existe uma maneira em C # de reproduzir áudio (por exemplo, MP3) diretamente de um System.IO.Stream que, por exemplo, foi retornado de um WebRequest sem salvar os dados temporariamente no disco?


Solução com NAudio

Com a ajuda do NAudio 1.3 é possível:

  1. Carregue um arquivo MP3 de um URL em um MemoryStream
  2. Converta dados de MP3 em dados de onda depois de carregados completamente
  3. Reproduzir os dados de ondas usando NAudio classe WaveOut 's

Seria bom poder tocar até mesmo um arquivo MP3 carregado pela metade, mas isso parece impossível devido ao design da biblioteca do NAudio .

E esta é a função que fará o trabalho:

    public static void PlayMp3FromUrl(string url)
    {
        using (Stream ms = new MemoryStream())
        {
            using (Stream stream = WebRequest.Create(url)
                .GetResponse().GetResponseStream())
            {
                byte[] buffer = new byte[32768];
                int read;
                while ((read = stream.Read(buffer, 0, buffer.Length)) > 0)
                {
                    ms.Write(buffer, 0, read);
                }
            }

            ms.Position = 0;
            using (WaveStream blockAlignedStream =
                new BlockAlignReductionStream(
                    WaveFormatConversionStream.CreatePcmStream(
                        new Mp3FileReader(ms))))
            {
                using (WaveOut waveOut = new WaveOut(WaveCallbackInfo.FunctionCallback()))
                {
                    waveOut.Init(blockAlignedStream);
                    waveOut.Play();                        
                    while (waveOut.PlaybackState == PlaybackState.Playing )                        
                    {
                        System.Threading.Thread.Sleep(100);
                    }
                }
            }
        }
    }
Martin
fonte
4
bom ver que você fez isso funcionar. Não seria muito trabalhoso reproduzi-lo corretamente durante a transmissão. O principal problema é que o Mp3FileReader atualmente espera saber a duração com antecedência. Vou pensar em adicionar uma demonstração para a próxima versão do NAudio
Mark Heath,
@Mark Heath já resolveu o problema e adicionou o demo na versão atual do NAudio ou ainda está no seu pipline?
Martin
receio que ainda não, embora com as alterações feitas no NAudio 1.3 não será necessário muitos ajustes para fazê-lo funcionar.
Mark Heath
Mark: Preciso modificar o NAudio para fazê-lo funcionar, porque acabei de baixar o NAudio1.3, mas ele está aceitando o código acima sem alterações, mas por outro lado lançando uma exceção que diz algo como "Conversão ACM não é possível".
Mubashar,
A propósito, estou tentando jogar seguindo translate.google.com/translate_tts?q=I+love+techcrunch
Mubashar

Respostas:

56

Editar: Resposta atualizada para refletir as mudanças nas versões recentes do NAudio

É possível usar a biblioteca de áudio .NET de código aberto NAudio que escrevi. Ele procura um codec ACM em seu PC para fazer a conversão. O Mp3FileReader fornecido com o NAudio atualmente espera ser capaz de se reposicionar dentro do stream de origem (ele cria um índice de quadros MP3 antecipadamente), portanto, não é apropriado para streaming pela rede. No entanto, você ainda pode usar as classes MP3Framee AcmMp3FrameDecompressorno NAudio para descompactar MP3 transmitido instantaneamente.

Publiquei um artigo no meu blog explicando como reproduzir um fluxo de MP3 usando o NAudio . Basicamente, você tem um thread baixando quadros de MP3, descompactando-os e armazenando-os em um arquivo BufferedWaveProvider. Outro thread é reproduzido usando o BufferedWaveProvidercomo entrada.

Mark Heath
fonte
3
A biblioteca NAudio agora vem com um aplicativo de exemplo chamado Mp3StreamingDemo, que deve fornecer tudo o que é necessário para transmitir ao vivo um MP3 da rede.
Martin
É possível usar sua biblioteca para transmitir ao vivo o microfone / linha na entrada para um dispositivo Android?
realtebo
10

A classe SoundPlayer pode fazer isso. Parece que tudo o que você precisa fazer é definir sua propriedade Stream para o stream e chamar Play.

editar
Eu não acho que ele possa tocar arquivos MP3; parece limitado a .wav. Não tenho certeza se há algo na estrutura que pode reproduzir um arquivo MP3 diretamente. Tudo o que descobri sobre isso envolve o uso de um controle WMP ou a interação com o DirectX.

OwenP
fonte
1
Há anos estou tentando encontrar uma biblioteca .NET que codifique e decodifique MP3, mas não acho que exista.
MusiGenesis
O NAudio deve fazer o trabalho para você - pelo menos para decodificar (veja a primeira postagem)
Martin
4
O SoundPlayer tem problemas graves com GC e reproduz lixo aleatoriamente, a menos que você use a solução alternativa oficial da Microsoft (clique em Soluções alternativas): connect.microsoft.com/VisualStudio/feedback/…
Roman Starkov
SoundPlayer + memoryStream tem bug por desing. quando você toca na primeira, segunda ou terceira vez, adiciona um ruído estranho no meio do áudio.
bh_earth0
O link de solução alternativa não funciona mais, mas está na Wayback Machine: web.archive.org/web/20120722231139/http://connect.microsoft.com/…
Forestrf
5

Bass pode fazer exatamente isso. Jogue a partir do Byte [] na memória ou através de delegados de arquivo onde você retorna os dados, para que possa jogar assim que tiver dados suficientes para iniciar a reprodução.

slugster
fonte
3

Modifiquei ligeiramente a fonte do tópico inicial, para que agora possa reproduzir um arquivo não totalmente carregado. Aqui está (observe que é apenas uma amostra e um ponto de partida; você precisa fazer alguma exceção e tratamento de erros aqui):

private Stream ms = new MemoryStream();
public void PlayMp3FromUrl(string url)
{
    new Thread(delegate(object o)
    {
        var response = WebRequest.Create(url).GetResponse();
        using (var stream = response.GetResponseStream())
        {
            byte[] buffer = new byte[65536]; // 64KB chunks
            int read;
            while ((read = stream.Read(buffer, 0, buffer.Length)) > 0)
            {
                var pos = ms.Position;
                ms.Position = ms.Length;
                ms.Write(buffer, 0, read);
                ms.Position = pos;
            }
        }
    }).Start();

    // Pre-buffering some data to allow NAudio to start playing
    while (ms.Length < 65536*10)
        Thread.Sleep(1000);

    ms.Position = 0;
    using (WaveStream blockAlignedStream = new BlockAlignReductionStream(WaveFormatConversionStream.CreatePcmStream(new Mp3FileReader(ms))))
    {
        using (WaveOut waveOut = new WaveOut(WaveCallbackInfo.FunctionCallback()))
        {
            waveOut.Init(blockAlignedStream);
            waveOut.Play();
            while (waveOut.PlaybackState == PlaybackState.Playing)
            {
                System.Threading.Thread.Sleep(100);
            }
        }
    }
}
ReVolly
fonte
Você testou seu código? Se sim, com qual versão do NAudio funcionou?
Martin
Além disso, seu código parece estar sujeito a erros de threading. Tem certeza que sabe o que está fazendo?
Martin
1) Sim, eu fiz. 2) Com a versão mais recente. 3) É apenas uma prova de conceito, conforme afirmei na mensagem anterior.
ReVolly
Como você define "versão mais recente"? É a versão 1.3 ou a fonte na revisão 68454?
Martin
@Martin Desculpe, não estou aqui há muito tempo. O NAudio.dll que usei tem a versão 1.3.8.
ReVolly de
2

Eu ajustei a fonte postada na pergunta para permitir o uso com a API TTS do Google, a fim de responder à pergunta aqui :

bool waiting = false;
AutoResetEvent stop = new AutoResetEvent(false);
public void PlayMp3FromUrl(string url, int timeout)
{
    using (Stream ms = new MemoryStream())
    {
        using (Stream stream = WebRequest.Create(url)
            .GetResponse().GetResponseStream())
        {
            byte[] buffer = new byte[32768];
            int read;
            while ((read = stream.Read(buffer, 0, buffer.Length)) > 0)
            {
                ms.Write(buffer, 0, read);
            }
        }
        ms.Position = 0;
        using (WaveStream blockAlignedStream =
            new BlockAlignReductionStream(
                WaveFormatConversionStream.CreatePcmStream(
                    new Mp3FileReader(ms))))
        {
            using (WaveOut waveOut = new WaveOut(WaveCallbackInfo.FunctionCallback()))
            {
                waveOut.Init(blockAlignedStream);
                waveOut.PlaybackStopped += (sender, e) =>
                {
                    waveOut.Stop();
                };
                waveOut.Play();
                waiting = true;
                stop.WaitOne(timeout);
                waiting = false;
            }
        }
    }
}

Invoque com:

var playThread = new Thread(timeout => PlayMp3FromUrl("http://translate.google.com/translate_tts?q=" + HttpUtility.UrlEncode(relatedLabel.Text), (int)timeout));
playThread.IsBackground = true;
playThread.Start(10000);

Terminar com:

if (waiting)
    stop.Set();

Observe que estou usando ParameterizedThreadDelegateno código acima e o thread é iniciado com playThread.Start(10000);. O 10000 representa um máximo de 10 segundos de áudio a ser reproduzido, portanto, será necessário ajustá-lo se a transmissão demorar mais do que isso para ser reproduzida. Isso é necessário porque a versão atual do NAudio (v1.5.4.0) parece ter um problema para determinar quando a reprodução do stream termina. Pode ser corrigido em uma versão posterior ou talvez haja uma solução alternativa que eu não tive tempo para encontrar.

M.Babcock
fonte
1

NAudio envolve a API WaveOutXXXX. Eu não olhei para a fonte, mas se o NAudio expõe a função waveOutWrite () de uma forma que não interrompe automaticamente a reprodução em cada chamada, então você deve ser capaz de fazer o que realmente deseja, que é começar a reproduzir o fluxo de áudio antes de você receber todos os dados.

O uso da função waveOutWrite () permite que você "leia adiante" e despeje pedaços menores de áudio na fila de saída - o Windows reproduzirá os pedaços automaticamente. Seu código teria que pegar o fluxo de áudio compactado e convertê-lo em pequenos pedaços de áudio WAV imediatamente; esta parte seria realmente difícil - todas as bibliotecas e componentes que eu já vi fazem a conversão de MP3 em WAV um arquivo inteiro de cada vez. Provavelmente, sua única chance realista é fazer isso usando WMA em vez de MP3, porque você pode escrever invólucros C # simples em torno do SDK de multimídia.

MusiGenesis
fonte
1

Eu envolvi a biblioteca do decodificador de MP3 e a disponibilizei para desenvolvedores .NET como mpg123.net .

Estão incluídos os exemplos para converter arquivos MP3 em PCM e ler tags ID3 .

Daniel Mošmondor
fonte
Ótimo! Estou ansioso para dar uma olhada neste fim de semana.
Larry Smithmier
1

Eu não tentei de um WebRequest, mas os componentes ActiveX do Windows Media Player e MediaElement (do WPF ) são capazes de reproduzir e armazenar em buffer streams MP3.

Eu o uso para reproduzir dados provenientes de um fluxo de SHOUTcast e funcionou muito bem. No entanto, não tenho certeza se funcionará no cenário que você propõe.

Ramiro Berrelleza
fonte
0

Sempre usei FMOD para coisas assim porque é gratuito para uso não comercial e funciona bem.

Dito isso, eu ficaria feliz em mudar para algo menor (FMOD é ~ 300k) e de código aberto. Super pontos de bônus se for totalmente gerenciado para que eu possa compilá-lo / mesclá-lo com meu .exe e não ter que tomar cuidado extra para obter portabilidade para outras plataformas ...

(FMOD também oferece portabilidade, mas você obviamente precisa de binários diferentes para plataformas diferentes)

Roman Starkov
fonte