Reproducir audio de una transmisión con C #

99

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


Solución con NAudio

Con la ayuda de NAudio 1.3 es posible:

  1. Cargue un archivo MP3 desde una URL en un MemoryStream
  2. Convierta datos MP3 en datos de ondas después de que se hayan cargado por completo
  3. La reproducción de los datos de onda utilizando NAudio clase WaveOut 's

Hubiera sido bueno poder incluso reproducir un archivo MP3 medio cargado, pero esto parece 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);
                    }
                }
            }
        }
    }
Martín
fuente
4
Es bueno ver que lo tienes funcionando. No sería demasiado trabajo que se reproduzca correctamente durante la transmisión. El problema principal es que Mp3FileReader actualmente espera conocer la duración de antemano. Buscaré agregar una demostración para la próxima versión de NAudio
Mark Heath
@Mark Heath, ¿ya resolvió el problema y agregó la demostración en la versión actual de NAudio o todavía está en su línea principal?
Martin
Temo que todavía no, aunque con los cambios realizados en NAudio 1.3 no será necesario realizar demasiados ajustes para que funcione.
Mark Heath
Mark: ¿Necesito modificar NAudio para que funcione? Porque acabo de descargar NAudio1.3 pero está aceptando el código anterior sin cambios, pero por otro lado arroja una excepción que dice algo como "Conversión ACM no es posible".
Mubashar
por cierto, estoy tratando de jugar siguiendo translate.google.com/translate_tts?q=I+love+techcrunch
Mubashar

Respuestas:

56

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

Es posible usar la biblioteca de audio .NET de código abierto NAudio que he escrito. Busca un códec ACM en su PC para realizar la conversión. El Mp3FileReader suministrado con NAudio actualmente espera poder reposicionarse dentro del flujo de origen (crea un índice de fotogramas MP3 al principio), por lo que no es apropiado para transmitir a través de la red. Sin embargo, aún puede usar las clases MP3Framey AcmMp3FrameDecompressoren NAudio para descomprimir MP3 transmitido sobre la marcha.

He publicado un artículo en mi blog que explica cómo reproducir un flujo de MP3 con NAudio . Básicamente, tiene un hilo descargando marcos MP3, descomprimiéndolos y almacenándolos en un archivo BufferedWaveProvider. Luego, se reproduce otro hilo utilizando BufferedWaveProvidercomo entrada.

Mark Heath
fuente
3
La biblioteca NAudio ahora se envía con una aplicación de ejemplo llamada Mp3StreamingDemo que debería proporcionar todo lo que uno necesita para transmitir en vivo un MP3 desde la red.
Martin
¿Es posible usar su biblioteca para transmitir en vivo la entrada de micrófono / línea a un dispositivo Android?
realtebo
10

La clase SoundPlayer puede hacer esto. Parece que todo lo que tienes que hacer es establecer su propiedad Stream en la secuencia y luego llamar Play.

editar
Aunque 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.

OwenP
fuente
1
He estado tratando de encontrar una biblioteca .NET que codifique y decodifique MP3 durante años, pero no creo que exista.
MusiGenesis
NAudio debería hacer el trabajo por usted, al menos para la decodificación (vea la primera publicación)
Martin
4
SoundPlayer tiene problemas desagradables con GC y reproducirá basura aleatoriamente a menos que use la solución oficial de Microsoft (haga clic en Soluciones alternativas): connect.microsoft.com/VisualStudio/feedback/…
Roman Starkov
SoundPlayer + memoryStream tiene un error al diseñar. cuando reproduce la primera segunda o tercera vez, agrega un ruido extraño en el medio de su audio.
bh_earth0
El enlace de solución alternativa ya no funciona, pero está en Wayback Machine: web.archive.org/web/20120722231139/http://connect.microsoft.com/…
Forestrf
5

Bass puede hacer precisamente esto. Reproducir desde Byte [] en la memoria o mediante delegados de archivo donde devuelve los datos, de modo que pueda reproducir tan pronto como tenga suficientes datos para iniciar la reproducción.

babosa
fuente
3

Modifiqué ligeramente la fuente de inicio del tema, por lo que ahora puede reproducir un archivo que no está completamente cargado. Aquí está (tenga en cuenta 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);
            }
        }
    }
}
ReVolly
fuente
¿Probaste tu código? Si es así, ¿con qué versión de NAudio funcionó?
Martin
Además, su código parece que es bastante propenso a errores de problemas de subprocesos. ¿Estás seguro de que sabes lo que estás haciendo?
Martin
1) Sí, lo hice. 2) Con la última versión. 3) Es solo una prueba de concepto, como dije en mi mensaje anterior.
ReVolly
¿Cómo se define la "última versión"? ¿Es la versión 1.3 o la fuente en la revisión 68454?
Martin
@Martin Lo siento, no he estado aquí por mucho tiempo. El NAudio.dll que utilicé tiene la versión 1.3.8.
ReVolly
2

Modifiqué la fuente publicada en la pregunta para permitir el uso con la API de 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 ParameterizedThreadDelegateen el código anterior y el hilo se inicia con playThread.Start(10000);. El 10000 representa un máximo de 10 segundos de audio que se reproducirán, por lo que será necesario modificarlo si la transmisión tarda más en reproducirse. Esto es necesario porque la versión actual de NAudio (v1.5.4.0) parece tener problemas para determinar cuándo termina la reproducción de la transmisión. Puede que se solucione en una versión posterior o tal vez haya una solución que no me tomé el tiempo de encontrar.

M.Babcock
fuente
1

NAudio envuelve la API WaveOutXXXX. No he mirado 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 quiere, que es comenzar a reproducir el flujo de audio antes de recibir todos los datos.

El uso de la función waveOutWrite () le permite "leer con anticipación" y volcar trozos más pequeños de audio en la cola de salida; Windows reproducirá automáticamente los trozos sin problemas. Su código tendría que tomar el flujo de audio comprimido y convertirlo 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 hacen conversión de MP3 a WAV un archivo completo a la vez. Probablemente su única oportunidad realista sea hacer esto usando WMA en lugar de MP3, porque puede escribir envoltorios simples de C # alrededor del SDK multimedia.

MusiGenesis
fuente
1

Envolví la biblioteca de decodificadores MP3 y la puse a disposición de los desarrolladores de .NET como mpg123.net .

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

Daniel Mošmondor
fuente
¡Excelente! Espero poder verlo este fin de semana.
Larry Smithmier
1

No lo he probado desde una WebRequest, pero tanto Windows Media Player ActiveX como MediaElement (de WPF componentes ) son capaces de reproducir y almacenar en búfer transmisiones MP3.

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

Ramiro Berrelleza
fuente
0

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

Dicho esto, con mucho gusto cambiaría 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 compilarlo / fusionarlo con mi .exe y no tener que tener mucho cuidado para obtener portabilidad a otras plataformas ...

(FMOD también tiene portabilidad, pero obviamente necesitaría diferentes binarios para diferentes plataformas)

Roman Starkov
fuente