Odtwórz dźwięk ze strumienia przy użyciu C #

99

Czy istnieje sposób w języku C #, aby odtwarzać dźwięk (na przykład MP3) bezpośrednio z System.IO.Stream, który na przykład został odtworzony z żądania WebRequest bez tymczasowego zapisywania danych na dysku?


Rozwiązanie z NAudio

Z pomocą NAudio 1.3 można:

  1. Załaduj plik MP3 z adresu URL do MemoryStream
  2. Konwertuj dane MP3 na dane Wave po całkowitym załadowaniu
  3. Odtwarzanie danych za pomocą fal NAudio klasę „s WaveOut

Byłoby miło móc odtworzyć nawet w połowie załadowany plik MP3, ale wydaje się to niemożliwe ze względu na projekt biblioteki NAudio .

I to jest funkcja, która wykona całą pracę:

    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);
                    }
                }
            }
        }
    }
Jaskółka oknówka
źródło
4
dobrze widzieć, że to działa. Prawidłowe odtwarzanie podczas przesyłania strumieniowego nie wymagałoby zbyt wiele pracy. Głównym problemem jest to, że Mp3FileReader oczekuje obecnie wcześniejszego poznania długości. Przyjrzę się dodaniu demo dla następnej wersji NAudio
Mark Heath
@Mark Heath, czy już rozwiązałeś problem i dodałeś demo w aktualnej wersji NAudio, czy nadal jest w twojej linii?
Martin
Jeszcze się nie boję, chociaż zmiany dokonane w NAudio 1.3 nie będą wymagały zbyt wielu poprawek, aby działało.
Mark Heath
Mark: Czy muszę modyfikować w NAudio, aby działał, ponieważ właśnie pobrałem NAudio1.3, ale akceptuje on powyższy kod bez zmian, ale z drugiej strony rzuca wyjątek, który mówi, że coś w rodzaju „Konwersja ACM nie jest możliwa”.
Mubashar
przy okazji próbuję grać śledząc translate.google.com/translate_tts?q=I+love+techcrunch
Mubashar

Odpowiedzi:

56

Edycja: zaktualizowano odpowiedź, aby odzwierciedlić zmiany w ostatnich wersjach NAudio

Jest to możliwe dzięki otwartej bibliotece audio .NET NAudio, którą napisałem. Szuka kodeka ACM na komputerze, aby przeprowadzić konwersję. Mp3FileReader dostarczony z NAudio oczekuje obecnie, że będzie w stanie zmienić położenie w strumieniu źródłowym (buduje indeks ramek MP3 z góry), więc nie jest odpowiedni do przesyłania strumieniowego przez sieć. Jednak nadal możesz używać klas MP3Framei AcmMp3FrameDecompressorw NAudio do dekompresji strumieniowo odtwarzanych plików MP3 w locie.

Opublikowałem artykuł na swoim blogu wyjaśniający, jak odtwarzać strumień MP3 za pomocą NAudio . Zasadniczo masz jeden wątek, który pobiera ramki MP3, dekompresuje je i zapisuje w pliku BufferedWaveProvider. Następnie inny wątek jest odtwarzany, używając BufferedWaveProviderjako wejścia.

Mark Heath
źródło
3
NAudio biblioteka jest teraz dostarczane z przykładowej aplikacji o nazwie Mp3StreamingDemo który powinien dostarczyć jeden wszystko będzie potrzebne do transmisji na żywo MP3 z sieci.
Martin
Czy można używać swojej biblioteki do transmisji na żywo z wejścia mikrofonowego / liniowego do urządzenia z systemem Android?
realtebo
10

SoundPlayer klasa może to zrobić. Wygląda na to, że wszystko, co musisz zrobić, to ustawić jego właściwość Stream na strumień, a następnie wywołać Play.

edytuj
Nie sądzę jednak, że może odtwarzać pliki MP3; wydaje się ograniczone do .wav. Nie jestem pewien, czy w ramach jest coś, co może bezpośrednio odtwarzać plik MP3. Wszystko, co o tym wiem, obejmuje użycie kontrolki WMP lub interakcję z DirectX.

OwenP
źródło
1
Od lat próbuję znaleźć bibliotekę .NET, która koduje i dekoduje MP3, ale nie sądzę, że istnieje.
MusiGenesis
NAudio powinno wykonać pracę za Ciebie - przynajmniej do dekodowania (patrz pierwszy post)
Martin
4
SoundPlayer ma nieprzyjemne problemy z GC i będzie losowo odtwarzać śmieci, chyba że użyjesz oficjalnego obejścia Microsoft (kliknij Obejście): connect.microsoft.com/VisualStudio/feedback/ ...
Roman Starkov
SoundPlayer + memoryStream ma błąd związany z desingiem. kiedy grasz pierwszą sekundę lub trzeci raz, dodaje to dziwne szumy w środku twojego dźwięku.
bh_earth0
Łącze do obejścia już nie działa, ale znajduje się w Wayback Machine: web.archive.org/web/20120722231139/http://connect.microsoft.com/…
Forestrf
5

Bas może to zrobić. Odtwarzaj z Byte [] w pamięci lub przez delegatów plików, w których zwracasz dane, dzięki czemu możesz odtwarzać, gdy tylko masz wystarczającą ilość danych, aby rozpocząć odtwarzanie.

slugster
źródło
3

Nieznacznie zmodyfikowałem źródło startera tematu, więc może teraz odtwarzać nie w pełni załadowany plik. Oto jest (zauważ, że to tylko przykład i jest to punkt, od którego należy zacząć; musisz tutaj zrobić wyjątek i obsługę błędów):

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
źródło
Czy przetestowałeś swój kod? Jeśli tak, z jaką wersją NAudio to działa?
Martin
Ponadto - Twój kod wygląda na dość podatny na błędy związane z problemami z wątkami. Czy na pewno wiesz, co robisz?
Martin
1) Tak, tak. 2) Z najnowszą wersją. 3) To tylko dowód słuszności koncepcji, jak stwierdziłem w mojej poprzedniej wiadomości.
ReVolly
Jak definiujesz „najnowszą wersję”? Czy jest to wersja 1.3 czy źródło w wersji 68454?
Martin
@Martin Przepraszamy, dawno tu nie byłem. NAudio.dll, którego użyłem, ma wersję 1.3.8.
ReVolly
2

Poprawiłem źródło zamieszczone w pytaniu, aby umożliwić używanie z interfejsem Google TTS API, aby odpowiedzieć na pytanie tutaj :

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

Wywołaj za pomocą:

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

Zakończ za pomocą:

if (waiting)
    stop.Set();

Zwróć uwagę, że używam ParameterizedThreadDelegatew powyższym kodzie, a wątek jest uruchamiany z playThread.Start(10000);. Wartość 10000 oznacza maksymalnie 10 sekund dźwięku do odtworzenia, więc będzie trzeba ją dostosować, jeśli odtwarzanie strumienia trwa dłużej. Jest to konieczne, ponieważ obecna wersja NAudio (v1.5.4.0) wydaje się mieć problem z określeniem, kiedy strumień jest zakończony. Może to zostać naprawione w późniejszej wersji lub może istnieje obejście, którego nie znalazłem w czasie.

M.Babcock
źródło
1

NAudio otacza API WaveOutXXXX. Nie patrzyłem na źródło, ale jeśli NAudio ujawnia funkcję waveOutWrite () w sposób, który nie zatrzymuje automatycznie odtwarzania przy każdym wywołaniu, powinieneś być w stanie zrobić to, co naprawdę chcesz, czyli rozpocząć odtwarzanie strumień audio, zanim otrzymałeś wszystkie dane.

Użycie funkcji waveOutWrite () umożliwia „czytanie z wyprzedzeniem” i zrzucanie mniejszych fragmentów dźwięku do kolejki wyjściowej - system Windows automatycznie bezproblemowo odtworzy fragmenty. Twój kod musiałby pobierać skompresowany strumień audio i konwertować go na małe fragmenty dźwięku WAV w locie; ta część byłaby naprawdę trudna - wszystkie biblioteki i komponenty, które kiedykolwiek widziałem, konwertują cały plik MP3 na WAV na raz. Prawdopodobnie jedyną realną szansą jest zrobienie tego za pomocą WMA zamiast MP3, ponieważ możesz pisać proste opakowania C # wokół multimedialnego SDK.

MusiGenesis
źródło
1

Zapakowałem bibliotekę dekodera MP3 i udostępniłem ją programistom .NET jako mpg123.net .

Dołączone są próbki do konwersji plików MP3 na PCM i odczytu tagów ID3 .

Daniel Mošmondor
źródło
Wspaniały! Nie mogę się doczekać, aby przyjrzeć się temu w ten weekend.
Larry Smithmier
1

Nie próbowałem tego z WebRequest, ale zarówno składniki Windows Media Player ActiveX, jak i MediaElement (z WPF ) są w stanie odtwarzać i buforować strumienie MP3.

Używam go do odtwarzania danych pochodzących ze strumienia SHOUTcast i działało świetnie. Nie jestem jednak pewien, czy zadziała w proponowanym przez Ciebie scenariuszu.

Ramiro Berrelleza
źródło
0

Zawsze używałem FMOD do takich rzeczy, ponieważ jest darmowy do użytku niekomercyjnego i działa dobrze.

To powiedziawszy, chętnie przełączyłbym się na coś mniejszego (FMOD to ~ 300k) i open-source. Super dodatkowe punkty, jeśli jest w pełni zarządzany, dzięki czemu mogę go skompilować / połączyć z moim .exe i nie muszę zwracać większej uwagi, aby uzyskać przenośność na inne platformy ...

(FMOD też jest przenośny, ale oczywiście potrzebujesz różnych plików binarnych dla różnych platform)

Roman Starkov
źródło