OpenHome Forum
Streaming with ohLibSpotify and NAudio - Printable Version

+- OpenHome Forum (http://forum.openhome.org)
+-- Forum: OpenHome (/forumdisplay.php?fid=1)
+--- Forum: LibSpotify (/forumdisplay.php?fid=6)
+--- Thread: Streaming with ohLibSpotify and NAudio (/showthread.php?tid=1202)



Streaming with ohLibSpotify and NAudio - freakimkaefig - 28-01-2014 08:18 PM

Hi,
i'm trying to stream music from ohLibSpotify and play it with NAudio. I also posted a question in StackOverflow.

At first i create a new PlaylistContainer at the LoggedIn callback and open a existing Playlist in the ContainerLoaded callback. After that i call an implemented Play Method with the first Track of the Playlist:
Code:
public void Play(Track track)
{
    session.PlayerLoad(track);
    session.PlayerPlay(true);
}

I implemented the MusicDelivery callback from ohLibSpotify:
Code:
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;
    }
}

And overwrote the stats in GetAudioBufferStats:
Code:
public override void GetAudioBufferStats(SpotifySession session, out AudioBufferStats stats)
{
    stats.samples = 2048 / 2;   //???
    stats.stutter = 0;          //???
}

The program doesn't produce any errors, but i also can't hear music from the speakers. Can anybody help me with my problem?


RE: Streaming with ohLibSpotify and NAudio - graham - 29-01-2014 11:20 AM

Hi freakimkaefig,

The original developer of ohLibSpotify has recently left the project, but I will continue to support and develop it. That said support is going to be a little slow for a few months. I will eventually get around to your question but I thought it was only reasonable to inform you of the current situation.

Graham


RE: Streaming with ohLibSpotify and NAudio - andreww - 05-02-2014 05:48 PM

Hi,

During playback, your MusicDeliveryCallback will be called over and over again. You need to create a single BufferedAudioProvider and feed the audio to it each time your MusicDeliveryCallback is invoked. Your example here will play silence because:

1. It creates a new BufferedWaveProvider on every invocation, but only the first one is ever attached to the WaveOut device.
2. As far as I can tell, you write samples into the BufferedWaveProvider and then immediately read them out again. You should be writing them in and letting the wave device read them out.

In addition, I should note that num_frames tells you the number of frames, not the number of bytes. When you declare arrays and use Marshal.Copy, you need to multiply this number by the number of channels and the bytes per sample.

I'm sorry I don't have more time to look into this, but as noted above, I've moved on from the project and have a new employer. Hopefully this information should point you in the right direction. Good luck.

Regards,

Andrew (Weeble).


RE: Streaming with ohLibSpotify and NAudio - freakimkaefig - 05-02-2014 08:10 PM

Hi,
thanks for helping me with my problem.

I think I'm getting closer. I updated my code based on your suggestions:

Code:
public void Play(Track track)
{
    _bufferedWaveProvider = new BufferedWaveProvider(new WaveFormat());
    _bufferedWaveProvider.BufferDuration = TimeSpan.FromSeconds(120);

    _session.PlayerLoad(track);
    _session.PlayerPlay(true);

    IWavePlayer waveOutDevice = new WaveOut();
    waveOutDevice.Init(_bufferedWaveProvider);
    waveOutDevice.Play();
}

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

    var size = num_frames * format.channels * 4;
    byte[] _copiedFrames = new byte[size];
    Marshal.Copy(frames, _copiedFrames, 0, size);   //Copy Pointer Bytes to _copiedFrames
    _bufferedWaveProvider.AddSamples(_copiedFrames, 0, size);
}

I'm getting sound output with this, but it's a strange noise with stutters. I think some values should be adjusted but don't know which ones. I tried to decrease the value of size which leads to no sound. I also tried to increase the value of size which leads to even faster stutters and noise.


RE: Streaming with ohLibSpotify and NAudio - andreww - 06-02-2014 03:19 PM

From a quick glance:

1. Use size = num_frames * format.channels * 2. (16-bit samples mean that you have two bytes per sample.)

2. Set stats.samples = _bufferedWaveProvider.BufferedBytes /2. See my answer to your StackOverflow question. You need to tell libspotify how many samples are in your buffer. If you keep telling libspotify a constant number it might assume you aren't playing audio and stop giving you any until you tell it your buffer is running out. (That said, I'm not sure what heuristics it actually uses. I seem to recall that if you feed it bad data it tends to ignore you and just play on regardless.)

3. Your MusicDeliveryCallback is supposed to return an int, telling libspotify how many frames you consumed. I'm not sure how you've managing to use a method that returns void - perhaps you could show more of the code? In this example, you should return num_frames. (Note that AddSamples will by default discard samples if you try to over-fill your buffer. Ideally you would avoid that by not consuming all the bytes from libspotify. If you return a value less than num_frames it will send you the unconsumed frames again on the next call.)

I *think* that should work, but I'm not sure based on what you've described. Is it possible libspotify is just providing you with silence? Is the track valid and loaded? Does the track start with a lot of silence or is it just really quiet? Perhaps you should try printing out the contents of the array. Is it all 0s? Can you verify that your callback is being invoked regularly?


RE: Streaming with ohLibSpotify and NAudio - freakimkaefig - 06-02-2014 05:32 PM

Hi,

thanks so much. I managed to play the song.

Here's my code if someone gets the same problem:
Code:
public override int MusicDelivery(SpotifySession session, AudioFormat format, IntPtr frames, int num_frames)
{
    _sessionManager.MusicDeliveryCallback(session, format, frames, num_frames);
    return num_frames;
}
The MusicDelivery callback now returns the number of frames.

Code:
public override void GetAudioBufferStats(SpotifySession session, out AudioBufferStats stats)
{
   base.GetAudioBufferStats(session, out stats);
}
The current AudioBufferStats gets updated in each iteration of _sessionManager.MusicDeliveryCallback.

The code in the _sessionManager is:
Code:
public void Play(Track track)
{
   _bufferedWaveProvider = new BufferedWaveProvider(new WaveFormat());
   _bufferedWaveProvider.BufferDuration = TimeSpan.FromSeconds(120);
   _audioBufferStats = new AudioBufferStats();

   _session.PlayerLoad(track);
   _session.PlayerPlay(true);

   IWavePlayer waveOutDevice = new WaveOut();
   waveOutDevice.Init(_bufferedWaveProvider);
   waveOutDevice.Play();
}

public void MusicDeliveryCallback(SpotifySession session, AudioFormat format, IntPtr frames, int num_frames)
{
   var size = num_frames * format.channels * 2;
   _copiedFrames = new byte[size];
   Marshal.Copy(frames, _copiedFrames, 0, size);   //Copy Pointer Bytes to _copiedFrames
   _bufferedWaveProvider.AddSamples(_copiedFrames, 0, size);    //adding bytes from _copiedFrames as samples
   SetCurrentAudioBufferStats(_bufferedWaveProvider.BufferedBytes / 2, 0);
}

private void SetCurrentAudioBufferStats(int samples, int stutter)
{
   _audioBufferStats.samples = samples;
   _audioBufferStats.stutter = stutter;
   SessionListener.GetAudioBufferStats(_session, out _audioBufferStats);
}


The song is playing, but with some stutters. Do you know, how to improve the code, to handle these stutters?

Thanks again Andrew, you saved my day!


RE: Streaming with ohLibSpotify and NAudio - andreww - 06-02-2014 06:32 PM

It's pretty hard to guess, but here are some ideas:

1. Allocate the byte array once up-front and then re-use it. Lots of high-frequency allocations like this triggers lots of garbage collection, and that can stall your managed code long enough to cause stutters. You'll need to make it big enough, and then correctly handle both when spotify gives you too little data (copy only what it gives you into the array, then call AddSamples with the size you copied) or too much (copy only what fits in the array, call AddSamples with that size, then return the number of frames you actually copied).

2. Detect your stutters and report them to libspotify. In principle it should compensate by streaming further ahead. This might be tricky without writing your own WaveProvider, since none of your code is running at the time the stutter happens, but you can detect it after-the-fact - when you enter GetAudioBufferStats or MusicDelivery, if the BufferedWaveProvider.BufferedBytes is 0, there has been a stutter. Check the libspotify docs. I think it wants you to set stutters to the number of underruns since it last called GetAudioBufferStats.

3. The implementation you've posted for GetAudioBufferStats doesn't look quite right. When libspotify calls this, it wants to know about the state of your buffer right now, not the state it was in when it last called MusicDelivery. Look at it like this - you have a buffer of audio data. It is constantly draining away as you play the audio. Periodically libspotify calls MusicDelivery to 'top up' your buffer. In between that, it calls GetAudioBufferStats, possibly several times, to check up on the state of the buffer, to figure out how soon it should call MusicDelivery again (and to arrange for data to stream across the Internet at the correct rate to achieve that). You have two problems: one is that you are querying the buffer state during MusicDelivery, and that value will be out of date by the time GetAudioBufferStats is called. The other is that your implementation of SetAudioBufferStats throws away the value anyway, and your implementation of GetAudioBufferStats just calls the base class implementation, which does nothing useful. You might find it helpful to read up on C# "out" parameters - I think you might have misunderstood what they do.