使用 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 数据流的主要内容,如果未能解决你的问题,请参考以下文章