使用C#播放流中的音频


99

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


NAudio解决方案

借助NAudio 1.3,可以:

  1. 将URL中的MP3文件加载到MemoryStream中
  2. 完全加载后将MP3数据转换为wave数据
  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
很高兴您能正常使用。要在流式传输期间正确播放它并不需要太多工作。主要问题是Mp3FileReader当前希望提前知道长度。我会考虑加入一个演示为n音讯的下一个版本
马克·希思

@Mark Heath您是否已解决问题并在当前NAudio版本中添加了演示,还是仍在您的指导书中?
马丁

恐怕还没有,尽管在NAudio 1.3中进行了更改,它不需要太多的调整就可以正常工作。
Mark Heath 2010年

马克:我需要在NAudio中进行修改以使其正常工作,因为我刚刚下载了NAudio1.3,但它接受上面的代码而没有更改,但是另一方面,它抛出异常,例如“无法进行ACM转换”。
Mubashar 2011年

Answers:


56

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

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

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


3
n音讯库现在与所谓的一个应用实例航运Mp3StreamingDemo应提供一切人会需要实时流来自网络的MP3。
马丁

是否可以使用您的媒体库在输入到android设备中的直播流麦克风/线路?
realtebo 2015年

10

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

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


1
多年来,我一直试图找到一个进行MP3编码和解码的.NET库,但我认为它不存在。
MusiGenesis

NAudio应该为您完成这项工作-至少要进行解码(请参阅第一篇文章)
Martin

4
声音播放有讨厌的问题与GC,除非你使用微软官方的解决方法将随机播放垃圾(点击解决方法):connect.microsoft.com/VisualStudio/feedback/...
罗马Starkov

SoundPlayer + memoryStream通过设计有错误。当您第一次播放第二次或第三次时,会在音频的中间增加怪异的噪音。
bh_earth0 '16

解决方法链接不再起作用,但是在Wayback Machine上:web.archive.org/web/20120722231139/http
//connect.microsoft.com/…– Forestrf

5

低音可以做到这一点。从内存中的Byte []或通过文件委托在其中返回数据进行播放,因此,只要有足够的数据即可开始播放,就可以播放。


3

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

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)只是我在上一条消息中所述的概念证明。
ReVolly 2011年

您如何定义“最新版本”?是1.3版还是68454版本的源代码?
马丁

@Martin对不起,很久没来了。我使用的NAudio.dll版本为1.3.8。
ReVolly 2011年

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


1

NAudio包装了WaveOutXXXX API。我没有查看源代码,但是如果NAudio以不会自动在每次调用时停止播放的方式公开waveOutWrite()函数,则您应该能够执行自己真正想要的操作,即开始播放音频流,然后再接收所有数据。

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




0

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

就是说,我很乐意切换到较小的程序(FMOD为〜300k)和开源程序。超级奖励积分,如果得到充分管理,以便我可以将其与.exe进行编译/合并,而不必格外小心即可将其移植到其他平台...

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.