.net 使用 C# 从流中播放音频

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/184683/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-03 10:16:53  来源:igfitidea点击:

Play audio from a stream using C#

.netaudiostreammp3naudio

提问by Martin

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

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



Solution with NAudio

NAudio解决方案

With the help of NAudio1.3 it is possible to:

NAudio1.3的帮助下,可以:

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

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

采纳答案by Mark Heath

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

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

It's possible using the NAudioopen 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 MP3Frameand AcmMp3FrameDecompressorclasses in NAudio to decompress streamed MP3 on the fly.

可以使用我编写的NAudio开源 .NET 音频库。它会在您的 PC 上寻找 ACM 编解码器来进行转换。NAudio 提供的 Mp3FileReader 当前期望能够在源流中重新定位(它预先构建 MP3 帧的索引),因此它不适合通过网络进行流式传输。但是,您仍然可以使用NAudio 中的MP3FrameAcmMp3FrameDecompressor类来即时解压缩流式 MP3。

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 BufferedWaveProvideras an input.

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

回答by OwenP

The SoundPlayerclass can do this. It looks like all you have to do is set its Streamproperty to the stream, then call Play.

声音播放类可以做到这一点。看起来您所要做的就是将其Stream属性设置为流,然后调用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 交互。

回答by OwenP

Basscan 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..

巴斯可以做到这一点。从内存中的 Byte[] 播放或通过文件委托您返回数据的位置,因此一旦您有足够的数据开始播放,您就可以播放..

回答by ReVolly

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

我稍微修改了主题启动源,因此它现在可以播放未完全加载的文件。这是(注意,它只是一个示例,是一个起点;您需要在此处进行一些异常和错误处理):

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

回答by M.Babcock

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

我已经调整了问题中发布的源代码,以允许与 Google 的 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 ParameterizedThreadDelegatein 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.

请注意,我ParameterizedThreadDelegate在上面的代码中使用了,并且线程以playThread.Start(10000);. 10000 表示最多可播放 10 秒的音频,因此如果您的流播放时间比播放时间长,则需要对其进行调整。这是必要的,因为当前版本的 NAudio (v1.5.4.0) 似乎在确定流何时完成播放时有问题。它可能会在更高版本中修复,或者可能有一个我没有花时间找到的解决方法。

回答by Ramiro Berrelleza

I haven't tried it from a WebRequest, but both the Windows Media PlayerActiveXand 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 SHOUTcaststream and it worked great. However, I'm not sure if it will work in the scenario you propose.

我用它来播放来自SHOUTcast流的数据,效果很好。但是,我不确定它是否适用于您提出的方案。

回答by MusiGenesis

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 以不会在每次调用时自动停止播放的方式公开 waveOutWrite() 函数,那么您应该能够做您真正想做的事情,即开始播放在您收到所有数据之前的音频流。

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.

使用 waveOutWrite() 函数允许您“提前阅读”并将较小的音频块转储到输出队列中 - Windows 将自动无缝地播放这些块。您的代码必须将压缩的音频流转换为小块的 WAV 音频;这部分真的很困难——我见过的所有库和组件都一次将整个文件进行 MP3 到 WAV 的转换。可能您唯一现实的机会是使用 WMA 而不是 MP3 来执行此操作,因为您可以围绕多媒体 SDK 编写简单的 C# 包装器。

回答by Daniel Mo?mondor

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

我包装了 MP3 解码器库,并将其作为mpg123.net提供给.NET开发人员。

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

包括将 MP3 文件转换为PCM和读取ID3标签的示例。

回答by Roman Starkov

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 也具有可移植性,但您显然需要针对不同平台使用不同的二进制文件)