【问题标题】:Play audio from a stream using C#使用 C# 从流中播放音频
【发布时间】:2010-09-16 03:04:51
【问题描述】:

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

【问题讨论】:

  • 很高兴看到你成功了。在流式传输时正确播放它不会有太多工作。主要问题是 Mp3FileReader 目前希望提前知道长度。我会考虑为下一个版本的 NAudio 添加一个演示
  • @Mark Heath 您是否已经解决了问题并在当前 NAudio 版本中添加了演示,还是仍在您的管道中?
  • 恐怕还没有,尽管在 NAudio 1.3 中进行了更改,它不需要太多调整即可使其正常工作。
  • 马克:我是否需要在 NAudio 中进行修改才能使其正常工作,因为我刚刚下载了 NAudio1.3,但它接受了上面的代码而不做任何更改,但另一方面抛出了类似于“无法进行 ACM 转换”。
  • 顺便说一句,我正在尝试关注translate.google.com/translate_tts?q=I+love+techcrunch

标签: .net audio stream mp3 naudio


【解决方案1】:

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

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

【讨论】:

  • 多年来我一直在努力寻找一个可以进行 MP3 编码和解码的 .NET 库,但我认为它不存在。
  • NAudio 应该为您完成这项工作 - 至少在解码方面(见第一篇文章)
  • SoundPlayer 有严重的 GC 问题,会随机播放垃圾,除非你使用微软官方的解决方法(点击解决方法):connect.microsoft.com/VisualStudio/feedback/…
  • SoundPlayer + memoryStream 有设计错误。当您第一次播放第二次或第三次时,它会在您的音频中间添加奇怪的噪音。
  • 解决方法链接不再起作用,但它在 Wayback Machine 上:web.archive.org/web/20120722231139/http://connect.microsoft.com/…
【解决方案2】:

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

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

我在我的博客上发表了一篇文章,解释了how to play back an MP3 stream using NAudio。本质上,您有一个线程下载 MP3 帧,将它们解压缩并将它们存储在 BufferedWaveProvider 中。然后另一个线程使用BufferedWaveProvider 作为输入进行回放。

【讨论】:

  • NAudio 库现在附带一个名为 Mp3StreamingDemo 的示例应用程序,它应该提供从网络直播 MP3 所需的一切。
  • 是否可以使用您的库将输入的麦克风/线路直播到 android 设备?
【解决方案3】:

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

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

【讨论】:

    【解决方案4】:

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

    【讨论】:

      【解决方案5】:

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

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

      【讨论】:

        【解决方案6】:

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

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

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

        【讨论】:

          【解决方案7】:

          我打包了 MP3 解码器库,并以 mpg123.net 的形式提供给 .NET 开发人员。

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

          【讨论】:

          • 太棒了!我期待着这个周末去看看。
          【解决方案8】:

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

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

          【讨论】:

          • 你测试你的代码了吗?如果是这样,它适用于哪个版本的 NAudio?
          • 此外 - 您的代码看起来确实很容易出现线程问题。你确定你知道自己在做什么吗?
          • 1) 是的,我做到了。 2) 使用最新版本。 3) 正如我在之前的消息中所说,这只是一个概念证明。
          • 你如何定义“最新版本”?是版本 1.3 还是版本 68454 的源代码?
          • @Martin 抱歉,好久没来了。我使用的 NAudio.dll 版本为 1.3.8。
          【解决方案9】:

          为了回答问题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;
                      }
                  }
              }
          }
          

          调用:

          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) 在确定流何时完成播放时似乎存在问题。它可能会在以后的版本中得到修复,或者可能有我没有花时间找到的解决方法。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-09-20
            • 2012-05-22
            • 1970-01-01
            • 2023-03-23
            • 1970-01-01
            • 2017-06-20
            相关资源
            最近更新 更多