使用 C# 播放流中的音频

发布于 2024-07-06 05:03:53 字数 1903 浏览 6 评论 0 原文

C# 有没有办法直接从 System.IO.Stream 例如从 WebRequest 返回而不将数据临时保存到磁盘?


NAudio

的帮助下 解决方案" rel="noreferrer">NAudio 1.3 可以:

  1. 将 MP3 文件从 URL 加载到 MemoryStream
  2. 完全加载后将 MP3 数据转换为波形数据
  3. 使用 NAudio 的 WaveOut 类 如果

能够播放半加载的 MP3 文件就太好了,但这似乎是不可能的,因为到 NAudio 库设计。

这是完成这项工作的函数:

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

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?


Solution with NAudio

With the help of NAudio 1.3 it is possible to:

  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

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.

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

垂暮老矣 2024-07-13 05:03:56

Bass 就能做到这一点。 从内存中的 Byte[] 或通过文件代表您返回数据的地方播放,这样您就可以在有足够的数据开始播放时立即播放。

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

烙印 2024-07-13 05:03:56

我已经调整了问题中发布的源代码,以允许使用 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;
            }
        }
    }
}

调用方式:

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

终止方式:

if (waiting)
    stop.Set();

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

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

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.

羁拥 2024-07-13 05:03:56

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

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

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);
            }
        }
    }
}
扬花落满肩 2024-07-13 05:03:56

这不是最优雅的解决方案,但它是一个简单的解决方案。 将网络响应中的流发送到文件,然后使用 ffplay 播放它。 它是跨平台的,无需任何配置即可接受大多数文件格式。 这里有一个 github 要点,展示了一种方法: https://gist.github.com/skittleson /095c0e9a4d56fbc53b80d22029e90c9b

我想弄清楚如何通过直接流式传输到 ffplay 与临时文件来实现此目的。

更新:可以通过流来完成。

await Cli.Wrap("ffplay")
.WithStandardInputPipe(PipeSource.FromStream(stream))
.WithArguments($"-autoexit -nodisp -hide_banner -loglevel error -fs -")
.ExecuteAsync();

This is not the most elegant solution but it's an easy solution. Send the stream from the web response to a file then play it using ffplay. It's cross platform and accepts most file formats without any configuration. Here is a github gist that shows a method to do that: https://gist.github.com/skittleson/095c0e9a4d56fbc53b80d22029e90c9b

I would like to figure out how to do this by streaming directly to ffplay verses a temp file.

UPDATE: It can be done with stream.

await Cli.Wrap("ffplay")
.WithStandardInputPipe(PipeSource.FromStream(stream))
.WithArguments(
quot;-autoexit -nodisp -hide_banner -loglevel error -fs -")
.ExecuteAsync();
格子衫的從容 2024-07-13 05:03:56

我一直使用 FMOD 来做这样的事情,因为它对于非商业用途是免费的并且运行良好。

也就是说,我很乐意切换到更小的(FMOD 约为 300k)和开源的东西。 如果它是完全托管的,那么我可以将其与我的 .exe 进行编译/合并,而不必额外小心即可移植到其他平台,这是超级奖励点...

(FMOD 也具有可移植性,但您显然需要针对不同的二进制文件平台)

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

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 does portability too but you'd obviously need different binaries for different platforms)

眼眸 2024-07-13 05:03:56

我封装了 MP3 解码器库,并将其作为 mpg123.net

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

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

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

凉墨 2024-07-13 05:03:56

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

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

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.

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.

想念有你 2024-07-13 05:03:56

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

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

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.

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.

白日梦 2024-07-13 05:03:55

SoundPlayer 类可以执行此操作。 看起来您所要做的就是将其 Stream 属性设置为流,然后调用 Play

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

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.

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.

不及他 2024-07-13 05:03:53

编辑:更新答案以反映 NAudio 最新版本的更改

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

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

Edit: Answer updated to reflect changes in recent versions of 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.

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文