使用c#在流中播放音频。

时间:2021-02-25 18:58:24

Is there a way in C# to play audio (for example, MP3) direcly from a System.IO.Stream that for instance was returend from a WebRequest without saving the data temporarily to the disk?

c#中有没有一种方法可以直接从System.IO中播放音频(例如MP3) ?例如,流从WebRequest中返回而不将数据临时保存到磁盘?


Solution with NAudio

With the help of NAudio 1.3 it is possible to:

在NAudio 1.3的帮助下,有可能:

  1. Load an MP3 file from a URL into a MemoryStream
  2. 将MP3文件从URL加载到MemoryStream中
  3. Convert MP3 data into wave data after it was completely loaded
  4. 将MP3数据在完全加载后转换为wave数据
  5. Playback the wave data using NAudio's WaveOut class
  6. 使用NAudio的WaveOut类回放wave数据

It would have been nice to be able to even play a half loaded MP3 file, but this seems to be impossible due to the NAudio library design.

如果能够播放半加载的MP3文件就好了,但是由于NAudio库的设计,这似乎是不可能的。

And this is the function that will do the work:

这个函数就是这样的

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

9 个解决方案

#1


46  

Edit: Answer updated to reflect changes in recent versions of NAudio

编辑:回复更新以反映最近版本的NAudio的变化

It's possible using the NAudio open source .NET audio library I have written. It looks for an ACM codec on your PC to do the conversion. The Mp3FileReader supplied with NAudio currently expects to be able to reposition within the source stream (it builds an index of MP3 frames up front), so it is not appropriate for streaming over the network. However, you can still use the MP3Frame and AcmMp3FrameDecompressor classes in NAudio to decompress streamed MP3 on the fly.

可以使用我编写的NAudio open source . net音频库。它在您的PC上寻找一个ACM编解码器来执行转换。NAudio提供的Mp3FileReader目前期望能够在源流中重新定位(它预先构建了MP3帧的索引),因此不适合通过网络进行流媒体。但是,您仍然可以使用MP3Frame和acmmp3framedispressor类在NAudio上进行解压,使其在飞行时播放。

I have posted an article on my blog explaining how to play back an MP3 stream using NAudio. Essentially you have one thread downloading MP3 frames, decompressing them and storing them in a BufferedWaveProvider. Another thread then plays back using the BufferedWaveProvider as an input.

我在博客上发表了一篇文章,解释如何使用NAudio播放MP3流。本质上,您有一个线程下载MP3帧,将它们解压并将它们存储在BufferedWaveProvider中。另一个线程使用BufferedWaveProvider作为输入进行回放。

#2


8  

The SoundPlayer class can do this. It looks like all you have to do is set its Stream property to the stream, then call Play.

SoundPlayer类可以这样做。看起来您所要做的就是将其流属性设置为流,然后调用Play。

edit
I don't think it can play MP3 files though; it seems limited to .wav. I'm not certain if there's anything in the framework that can play an MP3 file directly. Everything I find about that involves either using a WMP control or interacting with DirectX.

编辑我认为它不能播放MP3文件;它似乎仅限于。wav。我不确定框架中是否有任何东西可以直接播放MP3文件。我发现的一切都涉及到使用WMP控件或与DirectX交互。

#3


5  

Bass can do just this. Play from Byte[] in memory or a through file delegates where you return the data, so with that you can play as soon as you have enough data to start the playback..

Bass可以这么做。在内存中或通过文件委托中播放字节[],在那里您可以返回数据,因此,一旦您有足够的数据开始播放,您就可以开始播放。

#4


3  

I slightly modified the topic starter source, so it can now play a not-fully-loaded file. Here it is (note, that it is just a sample and is a point to start from; you need to do some exception and error handling here):

我稍微修改了主题starter源代码,因此它现在可以运行一个未完全加载的文件。这是(注意,它只是一个样本,是一个起点;您需要在这里做一些异常和错误处理):

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

#5


1  

NAudio wraps the WaveOutXXXX API. I haven't looked at the source, but if NAudio exposes the waveOutWrite() function in a way that doesn't automatically stop playback on each call, then you should be able to do what you really want, which is to start playing the audio stream before you've received all the data.

NAudio封装了WaveOutXXXX API。我还没有查看源文件,但是如果NAudio以一种不会在每次调用时自动停止回放的方式公开waveoutrite()函数,那么您应该能够做您真正想做的事情,即在收到所有数据之前开始播放音频流。

Using the waveOutWrite() function allows you to "read ahead" and dump smaller chunks of audio into the output queue - Windows will automatically play the chunks seamlessly. Your code would have to take the compressed audio stream and convert it to small chunks of WAV audio on the fly; this part would be really difficult - all the libraries and components I've ever seen do MP3-to-WAV conversion an entire file at a time. Probably your only realistic chance is to do this using WMA instead of MP3, because you can write simple C# wrappers around the multimedia SDK.

使用waveoutrite()函数可以“提前阅读”并将较小的音频块转储到输出队列中——窗口将自动无缝地播放这些块。您的代码将不得不接受压缩音频流,并将它转换成动态的小块WAV音频;这部分是非常困难的——我所见过的所有的库和组件都可以一次转换成一个完整的文件。可能您唯一现实的机会是使用WMA而不是MP3来实现这一点,因为您可以围绕多媒体SDK编写简单的c#包装器。

#6


1  

I've tweaked the source posted in the question to allow usage with Google's TTS API in order to answer the question here:

我已经修改了问题中发布的源文件,允许使用谷歌的TTS API来回答这个问题:

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

Invoke with:

调用:

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

Terminate with:

终止:

if (waiting)
    stop.Set();

Notice that I'm using the ParameterizedThreadDelegate in the code above, and the thread is started with playThread.Start(10000);. The 10000 represents a maximum of 10 seconds of audio to be played so it will need to be tweaked if your stream takes longer than that to play. This is necessary because the current version of NAudio (v1.5.4.0) seems to have a problem determining when the stream is done playing. It may be fixed in a later version or perhaps there is a workaround that I didn't take the time to find.

注意,我在上面的代码中使用了参数化的threaddelegate,线程是用playThread.Start(10000)启动的。10000代表最多10秒的音频播放,所以如果你的流播放时间超过10000秒,你需要对它进行调整。这是必要的,因为当前版本的NAudio (v1.5.4.0)似乎在确定流何时结束时存在问题。它可能在以后的版本中得到修正,或者可能有一个我没有花时间去寻找的解决方案。

#7


1  

I wrapped the MP3 decoder library and made it available for .NET developers as mpg123.net.

我包装了MP3解码器库,并使。net开发人员可以使用mpg123.net。

Included are the samples to convert MP3 files to PCM, and read ID3 tags.

其中包括将MP3文件转换为PCM并读取ID3标记的示例。

#8


0  

I've always used FMOD for things like this because it's free for non-commercial use and works well.

我一直用FMOD来做这类事情,因为它是免费的,非商业用途,而且效果很好。

That said, I'd gladly switch to something that's smaller (FMOD is ~300k) and open-source. Super bonus points if it's fully managed so that I can compile / merge it with my .exe and not have to take extra care to get portability to other platforms...

话虽如此,我还是很乐意转向更小的(FMOD ~300k)和开源的。如果它完全被管理好了,我可以把它和我的.exe合并,而不必额外的小心,以获得其他平台的可移植性……

(FMOD does portability too but you'd obviously need different binaries for different platforms)

(FMOD也支持可移植性,但显然不同平台需要不同的二进制文件)

#9


0  

I haven't tried it from a WebRequest, but both the Windows Media Player ActiveX and the MediaElement (from WPF) components are capable of playing and buffering MP3 streams.

我还没有尝试过WebRequest,但是Windows Media Player ActiveX和MediaElement(来自WPF)组件都能够播放和缓冲MP3流。

I use it to play data coming from a SHOUTcast stream and it worked great. However, I'm not sure if it will work in the scenario you propose.

我用它来播放来自SHOUTcast流的数据,效果很好。然而,我不确定它是否能在你提出的方案中发挥作用。

#1


46  

Edit: Answer updated to reflect changes in recent versions of NAudio

编辑:回复更新以反映最近版本的NAudio的变化

It's possible using the NAudio open source .NET audio library I have written. It looks for an ACM codec on your PC to do the conversion. The Mp3FileReader supplied with NAudio currently expects to be able to reposition within the source stream (it builds an index of MP3 frames up front), so it is not appropriate for streaming over the network. However, you can still use the MP3Frame and AcmMp3FrameDecompressor classes in NAudio to decompress streamed MP3 on the fly.

可以使用我编写的NAudio open source . net音频库。它在您的PC上寻找一个ACM编解码器来执行转换。NAudio提供的Mp3FileReader目前期望能够在源流中重新定位(它预先构建了MP3帧的索引),因此不适合通过网络进行流媒体。但是,您仍然可以使用MP3Frame和acmmp3framedispressor类在NAudio上进行解压,使其在飞行时播放。

I have posted an article on my blog explaining how to play back an MP3 stream using NAudio. Essentially you have one thread downloading MP3 frames, decompressing them and storing them in a BufferedWaveProvider. Another thread then plays back using the BufferedWaveProvider as an input.

我在博客上发表了一篇文章,解释如何使用NAudio播放MP3流。本质上,您有一个线程下载MP3帧,将它们解压并将它们存储在BufferedWaveProvider中。另一个线程使用BufferedWaveProvider作为输入进行回放。

#2


8  

The SoundPlayer class can do this. It looks like all you have to do is set its Stream property to the stream, then call Play.

SoundPlayer类可以这样做。看起来您所要做的就是将其流属性设置为流,然后调用Play。

edit
I don't think it can play MP3 files though; it seems limited to .wav. I'm not certain if there's anything in the framework that can play an MP3 file directly. Everything I find about that involves either using a WMP control or interacting with DirectX.

编辑我认为它不能播放MP3文件;它似乎仅限于。wav。我不确定框架中是否有任何东西可以直接播放MP3文件。我发现的一切都涉及到使用WMP控件或与DirectX交互。

#3


5  

Bass can do just this. Play from Byte[] in memory or a through file delegates where you return the data, so with that you can play as soon as you have enough data to start the playback..

Bass可以这么做。在内存中或通过文件委托中播放字节[],在那里您可以返回数据,因此,一旦您有足够的数据开始播放,您就可以开始播放。

#4


3  

I slightly modified the topic starter source, so it can now play a not-fully-loaded file. Here it is (note, that it is just a sample and is a point to start from; you need to do some exception and error handling here):

我稍微修改了主题starter源代码,因此它现在可以运行一个未完全加载的文件。这是(注意,它只是一个样本,是一个起点;您需要在这里做一些异常和错误处理):

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

#5


1  

NAudio wraps the WaveOutXXXX API. I haven't looked at the source, but if NAudio exposes the waveOutWrite() function in a way that doesn't automatically stop playback on each call, then you should be able to do what you really want, which is to start playing the audio stream before you've received all the data.

NAudio封装了WaveOutXXXX API。我还没有查看源文件,但是如果NAudio以一种不会在每次调用时自动停止回放的方式公开waveoutrite()函数,那么您应该能够做您真正想做的事情,即在收到所有数据之前开始播放音频流。

Using the waveOutWrite() function allows you to "read ahead" and dump smaller chunks of audio into the output queue - Windows will automatically play the chunks seamlessly. Your code would have to take the compressed audio stream and convert it to small chunks of WAV audio on the fly; this part would be really difficult - all the libraries and components I've ever seen do MP3-to-WAV conversion an entire file at a time. Probably your only realistic chance is to do this using WMA instead of MP3, because you can write simple C# wrappers around the multimedia SDK.

使用waveoutrite()函数可以“提前阅读”并将较小的音频块转储到输出队列中——窗口将自动无缝地播放这些块。您的代码将不得不接受压缩音频流,并将它转换成动态的小块WAV音频;这部分是非常困难的——我所见过的所有的库和组件都可以一次转换成一个完整的文件。可能您唯一现实的机会是使用WMA而不是MP3来实现这一点,因为您可以围绕多媒体SDK编写简单的c#包装器。

#6


1  

I've tweaked the source posted in the question to allow usage with Google's TTS API in order to answer the question here:

我已经修改了问题中发布的源文件,允许使用谷歌的TTS API来回答这个问题:

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

Invoke with:

调用:

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

Terminate with:

终止:

if (waiting)
    stop.Set();

Notice that I'm using the ParameterizedThreadDelegate in the code above, and the thread is started with playThread.Start(10000);. The 10000 represents a maximum of 10 seconds of audio to be played so it will need to be tweaked if your stream takes longer than that to play. This is necessary because the current version of NAudio (v1.5.4.0) seems to have a problem determining when the stream is done playing. It may be fixed in a later version or perhaps there is a workaround that I didn't take the time to find.

注意,我在上面的代码中使用了参数化的threaddelegate,线程是用playThread.Start(10000)启动的。10000代表最多10秒的音频播放,所以如果你的流播放时间超过10000秒,你需要对它进行调整。这是必要的,因为当前版本的NAudio (v1.5.4.0)似乎在确定流何时结束时存在问题。它可能在以后的版本中得到修正,或者可能有一个我没有花时间去寻找的解决方案。

#7


1  

I wrapped the MP3 decoder library and made it available for .NET developers as mpg123.net.

我包装了MP3解码器库,并使。net开发人员可以使用mpg123.net。

Included are the samples to convert MP3 files to PCM, and read ID3 tags.

其中包括将MP3文件转换为PCM并读取ID3标记的示例。

#8


0  

I've always used FMOD for things like this because it's free for non-commercial use and works well.

我一直用FMOD来做这类事情,因为它是免费的,非商业用途,而且效果很好。

That said, I'd gladly switch to something that's smaller (FMOD is ~300k) and open-source. Super bonus points if it's fully managed so that I can compile / merge it with my .exe and not have to take extra care to get portability to other platforms...

话虽如此,我还是很乐意转向更小的(FMOD ~300k)和开源的。如果它完全被管理好了,我可以把它和我的.exe合并,而不必额外的小心,以获得其他平台的可移植性……

(FMOD does portability too but you'd obviously need different binaries for different platforms)

(FMOD也支持可移植性,但显然不同平台需要不同的二进制文件)

#9


0  

I haven't tried it from a WebRequest, but both the Windows Media Player ActiveX and the MediaElement (from WPF) components are capable of playing and buffering MP3 streams.

我还没有尝试过WebRequest,但是Windows Media Player ActiveX和MediaElement(来自WPF)组件都能够播放和缓冲MP3流。

I use it to play data coming from a SHOUTcast stream and it worked great. However, I'm not sure if it will work in the scenario you propose.

我用它来播放来自SHOUTcast流的数据,效果很好。然而,我不确定它是否能在你提出的方案中发挥作用。