使用 NAudio 在 C# 中播放 ohLibSpotify pcm 数据流

Posted

技术标签:

【中文标题】使用 NAudio 在 C# 中播放 ohLibSpotify pcm 数据流【英文标题】:Playing ohLibSpotify pcm data stream in C# with NAudio 【发布时间】:2014-02-13 23:06:00 【问题描述】:

我正在尝试播放从 ohLibSpotify c# 库 (https://github.com/openhome/ohLibSpotify) 提供的原始 pcm 数据。

我在以下回调中获取数据:

public void MusicDeliveryCallback(SpotifySession session, AudioFormat format, IntPtr frames, int num_frames)

    //EXAMPLE DATA
    //format.channels = 2, format.samplerate = 44100, format.sample_type = Int16NativeEndian
    //frames = ?
    //num_frames = 2048

现在我想用 NAudio (http://naudio.codeplex.com/) 直接播放接收到的数据。使用以下代码 sn-p 我可以从磁盘播放 mp3 文件。是否可以将Spotify接收到的数据直接传递给NAudio并实时播放?

using (var ms = File.OpenRead("test.pcm"))
using (var rdr = new Mp3FileReader(ms))
using (var wavStream = WaveFormatConversionStream.CreatePcmStream(rdr))
using (var baStream = new BlockAlignReductionStream(wavStream))
using (var waveOut = new WaveOut(WaveCallbackInfo.FunctionCallback()))

    waveOut.Init(baStream);
    waveOut.Play();
    while (waveOut.PlaybackState == PlaybackState.Playing)
    
        Thread.Sleep(100);
    


编辑: 我更新了我的代码。该程序不会抛出任何错误,但我也听不到音乐。我的代码有什么问题吗?

这是音乐传递回调:

public void MusicDeliveryCallback(SpotifySession session, AudioFormat format, IntPtr frames, int num_frames)

    //format.channels = 2, format.samplerate = 44100, format.sample_type = Int16NativeEndian
    //frames = ?
    //num_frames = 2048

    byte[] frames_copy = new byte[num_frames];
    Marshal.Copy(frames, frames_copy, 0, num_frames);

    bufferedWaveProvider = new BufferedWaveProvider(new WaveFormat(format.sample_rate, format.channels));
    bufferedWaveProvider.BufferDuration = TimeSpan.FromSeconds(40);            
    bufferedWaveProvider.AddSamples(frames_copy, 0, num_frames);
    bufferedWaveProvider.Read(frames_copy, 0, num_frames);

    if (_waveOutDeviceInitialized == false)
    
        IWavePlayer waveOutDevice = new WaveOut();
        waveOutDevice.Init(bufferedWaveProvider);
        waveOutDevice.Play();
        _waveOutDeviceInitialized = true;
    

这些是 SessionListener 中被覆盖的回调:

public override int MusicDelivery(SpotifySession session, AudioFormat format, IntPtr frames, int num_frames)

    _sessionManager.MusicDeliveryCallback(session, format, frames, num_frames);
    return base.MusicDelivery(session, format, frames, num_frames);


public override void GetAudioBufferStats(SpotifySession session, out AudioBufferStats stats)

    stats.samples = 2048 / 2;   //???
    stats.stutter = 0;          //???

【问题讨论】:

【参考方案1】:

我认为你可以这样做:

    创建一个 BufferedWaveProvider。 将此传递给 waveOut.Init。 在您的 MusicDeliveryCallback 中,使用 Marshal.Copy 从本机缓冲区复制到托管字节数组中。 将此托管字节数组传递给 BufferedWaveProvider 上的 AddSamples。 在您的 GetAudioBufferStats 回调中,将 bufferedWaveProvider.BufferedBytes / 2 用于“samples”并将“stutters”保留为 0。

认为会起作用。它涉及一些不必要的复制,并且不能准确地跟踪口吃,但这是一个很好的起点。我认为实现 IWaveProvider 并自己管理缓冲可能是一种更好(更有效和更可靠)的解决方案。

我编写了 ohLibSpotify 包装库,但我不再为同一家公司工作,因此我不再参与它的开发。您也许可以从这个论坛上的某个人那里获得更多帮助:http://forum.openhome.org/forumdisplay.php?fid=6 就音乐交付而言,ohLibSpotify 旨在尽可能减少开销。它根本不会复制音乐数据,它只是将 libspotify 库本身提供的相同本机指针传递给您,这样您就可以自己将其复制到其最终目的地并避免不必要的层复制。不过,对于简单的使用来说,它确实有点笨拙。

祝你好运!

【讨论】:

感谢您的回答。但我并没有真正理解第 5 步。我应该将这些值设置为“samples”和“stutters”吗? public override void GetAudioBufferStats(SpotifySession session, out AudioBufferStats stats) stats.samples = 2048 / 4; //??? stats.stutter = 0; //??? base.GetAudioBufferStats(session, out stats) 是的,没错,只是你不应该调用基类实现,因为它只会覆盖你刚刚设置的值。 我需要检查 libspotify 文档,但这里的想法是您告诉 Spotify 它需要多快向您发送音频,以便它可以调整其流传输速率以匹配。样本是重要的,如果我没记错的话,它会告诉 libspotify 你的缓冲区有多满。但是自从我这样做以来已经有一段时间了,并且文档不是很好,所以我可能记错了。 实际上,我快速浏览了一下,我混淆了样本和框架。我想你只想除以 2。(每帧包含两个样本,每个样本是 2 个字节。) 我已经在链接的 openhome 论坛中回答了这个问题,但是为了方便阅读这里的任何人,当我说第一个评论是正确的时,我犯了一个错误。您不想为 stats.samples 使用固定值,您需要在调用 GetAudioBufferStats 时通过查询 BufferedWaveProvider.BufferedBytes 来计算它。【参考方案2】:

首先,上面显示的代码 sn-p 比它需要的更复杂。您只需要五个,而不是两个 using 语句。 Mp3FileReader 为您解码为 PCM。其次,在函数回调中使用WaveOutEvent 而不是WaveOut。它更可靠。

using (var rdr = new Mp3FileReader("test.pcm"))
using (var waveOut = new WaveOutEvent())

   //...

要回答您的实际问题,您需要使用BufferedWaveProvider。您创建其中之一,并在 Init 方法中将其传递给您的输出设备。现在,当您收到音频时,将其解压缩为 PCM(如果已压缩)并将其放入BufferedWaveProvider。 NAudioDemo 应用程序包含如何执行此操作的示例,因此请查看 NAudio 源代码以了解其完成方式。

【讨论】:

以上是关于使用 NAudio 在 C# 中播放 ohLibSpotify pcm 数据流的主要内容,如果未能解决你的问题,请参考以下文章

NAudio 使用 C# 播放 x 毫秒的正弦波

C# NAudio:录制的文件无法播放

如何使用 C# NAudio 操作字节?

使用 naudio 播放 .wav 文件,播放在 1 秒后停止

使用 NAudio 从内存流中播放 .wma

如何使用 NAudio 和 ASIO 直接读取输入缓冲区和播放?