【问题标题】:Playing ohLibSpotify pcm data stream in C# with NAudio使用 NAudio 在 C# 中播放 ohLibSpotify pcm 数据流
【发布时间】: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;          //???
}

【问题讨论】:

    标签: c# .net-4.0 naudio libspotify


    【解决方案1】:

    我认为你可以这样做:

    1. 创建一个 BufferedWaveProvider。
    2. 将此传递给 waveOut.Init。
    3. 在您的 MusicDeliveryCallback 中,使用 Marshal.Copy 从本机缓冲区复制到托管字节数组中。
    4. 将此托管字节数组传递给 BufferedWaveProvider 上的 AddSamples。
    5. 在您的 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 源代码以了解其完成方式。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-05-23
      • 1970-01-01
      • 1970-01-01
      • 2020-09-13
      • 1970-01-01
      • 2011-08-18
      • 2012-07-14
      • 1970-01-01
      相关资源
      最近更新 更多