Reproduce audio de una transmisión usando C #

¿Hay alguna forma en C # de reproducir audio (por ejemplo, MP3) directamente desde un System.IO.Stream que, por ejemplo, fue retomado de una WebRequest sin guardar temporalmente los datos en el disco?


Solución con NAudio

Con la ayuda de NAudio 1.3 es posible:

  1. Cargue un archivo MP3 de una URL en un MemoryStream
  2. Convierta datos MP3 en datos de onda después de que se cargó por completo
  3. Reproduzca los datos de onda con la clase WaveOut de NAudio

Hubiera sido bueno poder incluso reproducir un archivo MP3 medio cargado, pero esto parece ser imposible debido al diseño de la biblioteca NAudio .

Y esta es la función que hará el trabajo:

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

Editar: respuesta actualizada para reflejar los cambios en las versiones recientes de NAudio

Es posible usar la biblioteca de audio .NET de código abierto de NAudio que he escrito. Busca un códec ACM en tu PC para realizar la conversión. El Mp3FileReader suministrado con NAudio actualmente espera poder reposicionarse dentro de la secuencia de origen (construye un índice de fotogtwigs MP3 por adelantado), por lo que no es apropiado para la transmisión a través de la red. Sin embargo, aún puede usar las clases MP3Frame y AcmMp3FrameDecompressor en NAudio para descomprimir MP3 transmitido sobre la marcha.

He publicado un artículo en mi blog explicando cómo reproducir una transmisión de MP3 usando NAudio . Básicamente, tienes un hilo descargando cuadros MP3, descomprimiéndolos y almacenándolos en un BufferedWaveProvider . Luego, se reproduce otro subproceso utilizando BufferedWaveProvider como entrada.

La clase SoundPlayer puede hacer esto. Parece que todo lo que tiene que hacer es establecer su propiedad Stream en la transmisión y luego llamar a Play .

editar
No creo que pueda reproducir archivos MP3; parece limitado a .wav. No estoy seguro de si hay algo en el marco que pueda reproducir un archivo MP3 directamente. Todo lo que encuentro sobre eso implica usar un control WMP o interactuar con DirectX.

Bass puede hacer esto. Reproduzca desde Byte [] en la memoria o desde un archivo delegado en el que devuelve los datos, para que pueda reproducirlo tan pronto como tenga datos suficientes para comenzar la reproducción.

Modifiqué ligeramente la fuente de inicio del tema, por lo que ahora puede reproducir un archivo no completamente cargado. Aquí está (nótese que es solo una muestra y es un punto desde el que comenzar, debe hacer alguna excepción y manejo de errores aquí):

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

NAudio ajusta la API de WaveOutXXXX. No he visto la fuente, pero si NAudio expone la función waveOutWrite () de una manera que no detiene automáticamente la reproducción en cada llamada, entonces debería poder hacer lo que realmente desea, que es comenzar a reproducir el transmisión de audio antes de que haya recibido todos los datos.

El uso de la función waveOutWrite () le permite “leer por adelantado” y volcar trozos de audio más pequeños en la cola de salida: Windows reproducirá automáticamente los fragmentos sin problemas. Su código tendría que tomar la transmisión de audio comprimida y convertirla en pequeños fragmentos de audio WAV sobre la marcha; Esta parte sería realmente difícil: todas las bibliotecas y componentes que he visto convierten MP3 a WAV en un archivo completo a la vez. Probablemente su única posibilidad realista es hacer esto usando WMA en lugar de MP3, porque puede escribir envoltorios simples de C # alrededor del SDK multimedia.

He modificado la fuente publicada en la pregunta para permitir el uso con la API TTS de Google para responder la pregunta aquí :

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

Invocar con:

 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 con:

 if (waiting) stop.Set(); 

Observe que estoy usando ParameterizedThreadDelegate en el código anterior, y el hilo se inicia con playThread.Start(10000); . El 10000 representa un máximo de 10 segundos de audio para ser reproducido, por lo que tendrá que modificarse si su transmisión demora más que eso en reproducirse. Esto es necesario porque la versión actual de NAudio (v1.5.4.0) parece tener un problema para determinar cuándo se ejecuta la transmisión. Se puede arreglar en una versión posterior o tal vez hay una solución alternativa que no tomé el tiempo de encontrar.

Envolví la biblioteca del decodificador de MP3 y la hice disponible para desarrolladores de .NET como mpg123.net .

Se incluyen las muestras para convertir archivos MP3 a PCM y leer tags ID3 .

Siempre he usado FMOD para cosas como esta porque es gratis para uso no comercial y funciona bien.

Dicho esto, me gustaría cambiar a algo más pequeño (FMOD es ~ 300k) y de código abierto. Súper puntos de bonificación si está completamente administrado para que pueda comstackr / fusionar con mi .exe y no tener que tener especial cuidado para obtener la portabilidad a otras plataformas …

(FMOD también es portátil, pero obviamente necesitarías diferentes binarios para diferentes plataformas)

No lo he intentado desde WebRequest, pero los componentes de Windows Media Player ActiveX y MediaElement (de WPF ) son capaces de reproducir y almacenar en buffer las transmisiones de MP3.

Lo uso para reproducir datos provenientes de una transmisión SHOUTcast y funcionó muy bien. Sin embargo, no estoy seguro si funcionará en el escenario que propones.