102

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


NAudio解决方案

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

9 回答 9

58

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

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

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

于 2008-10-08T21:44:46.523 回答
10

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

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

于 2008-10-08T20:42:06.133 回答
5

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

于 2008-10-22T10:59:39.223 回答
2

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

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);
            }
        }
    }
}
于 2011-02-22T16:50:36.623 回答
2

我已经调整了问题中发布的来源,以允许使用 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) 似乎在确定流何时完成播放时存在问题。它可能会在以后的版本中得到修复,或者可能有一个我没有花时间找到的解决方法。

于 2012-02-11T23:52:20.863 回答
1

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

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

于 2008-10-19T22:25:02.227 回答
1

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

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

于 2008-11-12T20:18:25.783 回答
1

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

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

于 2010-08-24T19:42:51.923 回答
0

我一直将 FMOD 用于此类事情,因为它可以免费用于非商业用途并且运行良好。

也就是说,我很乐意改用更小(FMOD 约为 300k)和开源的东西。如果它是完全管理的,那么我可以将它与我的 .exe 编译/合并,而不必特别注意获得对其他平台的可移植性......

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

于 2009-11-07T09:16:25.057 回答