Lire de l'audio à partir d'un flux en utilisant C #


99

Existe-t-il un moyen en C # de lire de l'audio (par exemple, MP3) directement à partir d'un System.IO.Stream qui, par exemple, a été renvoyé à partir d'un WebRequest sans enregistrer temporairement les données sur le disque?


Solution avec NAudio

Avec l'aide de NAudio 1.3, il est possible de:

  1. Charger un fichier MP3 depuis une URL dans un MemoryStream
  2. Convertissez les données MP3 en données wave après leur chargement complet
  3. La lecture des données d'onde en utilisant NAudio classe WaveOut s »

Cela aurait été bien de pouvoir même lire un fichier MP3 à moitié chargé, mais cela semble impossible en raison de la conception de la bibliothèque NAudio .

Et c'est la fonction qui fera le travail:

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

4
content de voir que ça marche. Ce ne serait pas trop de travail pour le lire correctement pendant le streaming. Le principal problème est que Mp3FileReader s'attend actuellement à connaître la longueur à l'avance. Je vais chercher à ajouter une démo pour la prochaine version de NAudio
Mark Heath

@Mark Heath avez-vous déjà résolu le problème et ajouté la démo dans la version actuelle de NAudio ou est-elle toujours dans votre pipline?
Martin

pas encore peur, bien qu'avec les modifications apportées à NAudio 1.3, il ne faudra pas trop de réglages pour le faire fonctionner.
Mark Heath

Mark: Dois-je modifier dans NAudio pour qu'il fonctionne, parce que je viens de télécharger NAudio1.3 mais il accepte le code ci-dessus sans changement, mais d'autre part en lançant une exception qui dit quelque chose comme "Conversion ACM impossible".
Mubashar

au fait, j'essaie de jouer en suivant translate.google.com/translate_tts?q=I+love+techcrunch
Mubashar

Réponses:


56

Edit: Réponse mise à jour pour refléter les changements dans les versions récentes de NAudio

C'est possible en utilisant la bibliothèque audio open source .NET NAudio que j'ai écrite. Il recherche un codec ACM sur votre PC pour effectuer la conversion. Le Mp3FileReader fourni avec NAudio s'attend actuellement à pouvoir se repositionner dans le flux source (il crée un index des images MP3 à l'avant), il n'est donc pas approprié pour le streaming sur le réseau. Cependant, vous pouvez toujours utiliser les classes MP3Frameet AcmMp3FrameDecompressordans NAudio pour décompresser le MP3 diffusé à la volée.

J'ai publié un article sur mon blog expliquant comment lire un flux MP3 à l'aide de NAudio . Essentiellement, vous avez un thread qui télécharge des images MP3, les décompresse et les stocke dans un fichier BufferedWaveProvider. Un autre thread est ensuite lu en utilisant le BufferedWaveProvidercomme entrée.


3
La bibliothèque NAudio est maintenant livrée avec un exemple d'application appelé Mp3StreamingDemo qui devrait fournir tout ce dont on aura besoin pour diffuser en direct un MP3 à partir du réseau.
Martin

Est-il possible d'utiliser votre bibliothèque pour diffuser en direct micro / ligne en entrée sur un appareil Android?
realtebo

10

La classe SoundPlayer peut le faire. Il semble que tout ce que vous avez à faire est de définir sa propriété Stream sur le flux, puis d'appeler Play.

edit
Je ne pense pas qu'il puisse lire les fichiers MP3 cependant; il semble limité à .wav. Je ne suis pas certain qu'il y ait quelque chose dans le cadre qui puisse lire directement un fichier MP3. Tout ce que je trouve à ce sujet implique l'utilisation d'un contrôle WMP ou l'interaction avec DirectX.


1
J'essaie de trouver une bibliothèque .NET qui fait l'encodage et le décodage MP3 depuis des années maintenant, mais je ne pense pas qu'elle existe.
MusiGenesis

NAudio devrait faire le travail pour vous - au moins pour le décodage (voir premier article)
Martin

4
SoundPlayer a de vilains problèmes avec GC et lira aléatoirement les ordures à moins que vous n'utilisiez la solution de contournement officielle de Microsoft (cliquez sur Solutions de contournement): connect.microsoft.com/VisualStudio/feedback/…
Roman Starkov

SoundPlayer + memoryStream a un bug par conception. lorsque vous jouez la première deuxième ou la troisième fois, cela ajoute un bruit étrange au milieu de votre audio.
bh_earth0

Le lien de contournement ne fonctionne plus, mais il est sur Wayback Machine: web.archive.org/web/20120722231139/http
//connect.microsoft.com

5

La basse peut faire exactement cela. Lecture à partir de l'octet [] en mémoire ou d'un fichier à travers les délégués où vous renvoyez les données, de sorte que vous pouvez jouer dès que vous avez suffisamment de données pour démarrer la lecture.


3

J'ai légèrement modifié la source de démarrage de la rubrique, afin qu'elle puisse maintenant lire un fichier pas entièrement chargé. Le voici (notez que ce n'est qu'un exemple et qu'il s'agit d'un point de départ; vous devez faire une gestion des exceptions et des erreurs ici):

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

Avez-vous testé votre code? Si oui, avec quelle version de NAudio cela a-t-il fonctionné?
Martin

De plus, votre code semble assez sujet aux erreurs de threading. Etes-vous sûr de savoir ce que vous faites?
Martin

1) Oui, je l'ai fait. 2) Avec la dernière version. 3) C'est juste une preuve de concept comme je l'ai dit dans mon message précédent.
ReVolly

Comment définissez-vous la «dernière version»? Est-ce la version 1.3 ou la source à la révision 68454?
Martin

@Martin Désolé, je ne suis pas ici depuis longtemps. Le NAudio.dll que j'ai utilisé a la version 1.3.8.
ReVolly

2

J'ai modifié la source publiée dans la question pour autoriser l'utilisation avec l'API TTS de Google afin de répondre à la question ici :

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

Invoquer avec:

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

Terminer avec:

if (waiting)
    stop.Set();

Notez que j'utilise le ParameterizedThreadDelegatedans le code ci-dessus et que le fil est démarré avec playThread.Start(10000);. Le 10000 représente un maximum de 10 secondes d'audio à lire, il devra donc être modifié si votre flux prend plus de temps à jouer. Cela est nécessaire car la version actuelle de NAudio (v1.5.4.0) semble avoir un problème pour déterminer quand le flux est terminé. Il peut être corrigé dans une version ultérieure ou peut-être y a-t-il une solution de contournement que je n'ai pas pris le temps de trouver.


1

NAudio encapsule l'API WaveOutXXXX. Je n'ai pas regardé la source, mais si NAudio expose la fonction waveOutWrite () d'une manière qui n'arrête pas automatiquement la lecture à chaque appel, alors vous devriez pouvoir faire ce que vous voulez vraiment, c'est-à-dire commencer à jouer le flux audio avant que vous ayez reçu toutes les données.

L'utilisation de la fonction waveOutWrite () vous permet de «lire à l'avance» et de vider de plus petits morceaux d'audio dans la file d'attente de sortie - Windows lira automatiquement les morceaux de manière transparente. Votre code devrait prendre le flux audio compressé et le convertir en petits morceaux d'audio WAV à la volée; cette partie serait vraiment difficile - toutes les bibliothèques et tous les composants que j'ai jamais vus effectuent une conversion MP3 en WAV un fichier entier à la fois. Votre seule chance réaliste est probablement de le faire en utilisant WMA au lieu de MP3, car vous pouvez écrire de simples wrappers C # autour du SDK multimédia.



1

Je ne l'ai pas essayé à partir d'un WebRequest, mais à la fois le Windows Media Player ActiveX et le MediaElement (de WPF composants ) sont capables de lire et de mettre en mémoire tampon les flux MP3.

Je l'utilise pour lire des données provenant d'un flux SHOUTcast et cela a très bien fonctionné. Cependant, je ne suis pas sûr que cela fonctionnera dans le scénario que vous proposez.


0

J'ai toujours utilisé FMOD pour des choses comme celle-ci car il est gratuit pour un usage non commercial et fonctionne bien.

Cela dit, je passerais volontiers à quelque chose de plus petit (le FMOD est ~ 300k) et open-source. Super points bonus s'il est entièrement géré afin que je puisse le compiler / fusionner avec mon .exe et ne pas avoir à faire très attention pour obtenir la portabilité vers d'autres plates-formes ...

(FMOD fait aussi la portabilité, mais vous auriez évidemment besoin de différents binaires pour différentes plates-formes)

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.