How to use the BASS audio library in UWP development

The BASS audio library is a solid library to decode and play many audio formats, supports gapless playback (using the mixer, not used in this example) and equalizer (not used in this example).

The library is available also for Windows Store development.

To reference the BASS audio library in your UWP project remember to add conditional references based on platform in the csproj file

<ItemGroup Condition="'$(Platform)' == 'x86'">
  <Content Include="$(SolutionDir)bass\Windows 10\x86\bass.dll">
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
  </Content>
</ItemGroup>
<ItemGroup Condition="'$(Platform)' == 'x64'">
  <Content Include="$(SolutionDir)bass\Windows 10\x64\bass.dll">
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
  </Content>
</ItemGroup>
<ItemGroup Condition="'$(Platform)' == 'ARM'">
  <Content Include="$(SolutionDir)bass\Windows 10\arm\bass.dll">
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
  </Content>
</ItemGroup>

Let’s see how we can use the library in a background audio player.

First we need the background task. Here we initialize the BASS audio library, and free it when the task ends. Since we are going to use BASS only to decode the audio we can use the “no sound” device.

using System;
using Un4seen.Bass;
using Windows.ApplicationModel.Background;
using Windows.Foundation.Collections;
using Windows.Media.Playback;

namespace BackgroundPlayer
{
  public sealed class Player : IBackgroundTask
  {
    BackgroundTaskDeferral m_Deferral;
    BassMediaSource m_MediaSourceAdapter = null;

    public void Run(IBackgroundTaskInstance taskInstance)
    {
      m_Deferral = taskInstance.GetDeferral();
      taskInstance.Canceled += TaskInstance_Canceled;
      taskInstance.Task.Completed += TaskInstance_Completed;

      BassNet.Registration("Your email", "Your registration key");
      if (!Bass.BASS_Init(0, 44100, BASSInit.BASS_DEVICE_DEFAULT, IntPtr.Zero)) {
        BASSError err = Bass.BASS_ErrorGetCode();
        m_Deferral.Complete();
        return;
      }

      BackgroundMediaPlayer.MessageReceivedFromForeground += OnMessageReceived;
    }

    private void TaskInstance_Canceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
    {
      Bass.BASS_Free();
      m_Deferral.Complete();
    }

    void TaskInstance_Completed(BackgroundTaskRegistration sender, BackgroundTaskCompletedEventArgs args)
    {
      Bass.BASS_Free();
      m_Deferral.Complete();
    }
  }
}

Now we need to play the file when the foreground app asks for it. Communication between the foreground app and the background task is made through messages.

When the background task receives a “file” request message it opens the file using BASS (we’ll see later how) and sets the media source of the bacgkround media player.

private async void OnMessageReceived(object sender, MediaPlayerDataReceivedEventArgs args)
{
  ValueSet msg = args.Data;

  if (msg.ContainsKey("file")) {
    string audioFile = msg["file"] as string;
    if (!string.IsNullOrEmpty(audioFile)) {
      m_MediaSourceAdapter = await BassMediaSource.CreateAsync(audioFile);
      BackgroundMediaPlayer.Current.Source = MediaSource.CreateFromIMediaSource(m_MediaSourceAdapter.GetMediaSource());
      BackgroundMediaPlayer.Current.Play();
    }
  }
}

The BassMediaSource class implements IMediaSource and is the one responsible of opening and playing the file.

The code is quite simple: we need to create the decode stream for the BASS audio library and listen to the MediaSource events (Closed, Starting, SampleRequested).

public async Task InitializeAsync()
{
  StorageFile sFile = await StorageFile.GetFileFromPathAsync(m_FilePath);
  BasicProperties prop = await sFile.GetBasicPropertiesAsync();
  m_FileSize = prop.Size;

  m_BassHandle = Bass.BASS_StreamCreateFile(m_FilePath, 0, (long)m_FileSize, BASSFlag.BASS_STREAM_DECODE);
  if (m_BassHandle == 0) {
    BASSError err = Bass.BASS_ErrorGetCode();
    System.Diagnostics.Debug.WriteLine("InitializeAsync error {0}", err);
  }

  BASS_CHANNELINFO cInfo = Bass.BASS_ChannelGetInfo(m_BassHandle);
  if (cInfo == null) {
    BASSError err = Bass.BASS_ErrorGetCode();
    System.Diagnostics.Debug.WriteLine("InitializeAsync error {0}", err);
  }
  long len = Bass.BASS_ChannelGetLength(m_BassHandle, BASSMode.BASS_POS_BYTES);
  double secs = Bass.BASS_ChannelBytes2Seconds(m_BassHandle, len);
  uint bits = 16;
  if (cInfo.Is32bit)
    bits = 32;
  else if (cInfo.Is8bit)
    bits = 8;

  AudioEncodingProperties pcmprops = AudioEncodingProperties.CreatePcm((uint)cInfo.freq, (uint)cInfo.chans, bits);
  m_MediaStreamSource = new MediaStreamSource(new AudioStreamDescriptor(pcmprops));
  m_MediaStreamSource.CanSeek = true;
  m_MediaStreamSource.BufferTime = TimeSpan.Zero;
  m_MediaStreamSource.Duration = TimeSpan.FromSeconds(secs);
  m_MediaStreamSource.Closed += mss_Closed;
  m_MediaStreamSource.Starting += mss_Starting;
  m_MediaStreamSource.SampleRequested += mss_SampleRequested;
} // InitializeAsync

The SampleRequested event occurs when then audio player needs a decoded sample to play. Here we just need to decode the requested data and return it to the player.

void mss_SampleRequested(MediaStreamSource sender, MediaStreamSourceSampleRequestedEventArgs args)
{
  var deferral = args.Request.GetDeferral();
  System.Diagnostics.Debug.WriteLine("mss_SampleRequested");

  try {
    byte[] buffer = new byte[4096];
    int decoded = Bass.BASS_ChannelGetData(m_BassHandle, buffer, buffer.Length);
    if (decoded == -1) {
      BASSError err = Bass.BASS_ErrorGetCode();
      System.Diagnostics.Debug.WriteLine("mss_SampleRequested error {0}", err);
    } else {
      double secs = Bass.BASS_ChannelBytes2Seconds(m_BassHandle, decoded);

      MediaStreamSample sample = MediaStreamSample.CreateFromBuffer(buffer.AsBuffer(), TimeSpan.FromSeconds(m_SecondsPosition));
      sample.Duration = TimeSpan.FromSeconds(secs);
      m_SecondsPosition += sample.Duration.TotalSeconds;
      args.Request.Sample = sample;
    }
  } catch { }
  deferral.Complete();
} // mss_SampleRequested

The application lets you select a file from your music library (files outside of your music library won’t work because the application does not have access to external folders).

Here’s the full source code of the test program (remember to set your BASS registration info)

Download source code

Have fun! 😉

10 thoughts on “How to use the BASS audio library in UWP development

  1. You used BASSFlag.BASS_STREAM_DECODE to create the stream.
    BASS documentation clearly warns that using BASS_ChannelGetData() with an array of byte – as seen here – should only be used with BASSFlag.BASS_SAMPLE_8BITS.

    I’m new to audio and don’t have a clear understanding of how sound resolution (8, 16, 24, etc. bit) affects sound quality, but using your sample there was a clear wheezing noise around the singer’s voice in multiple mp3 files I listened to.

    I tried other overloads of BASS_ChannelGetData() with an array of integer (representing 16 bits) but couldn’t turn that buffer into an IBuffer necessary to pass to
    MediaStreamSample sample = MediaStreamSample.CreateFromBuffer(buffer.AsBuffer(), TimeSpan.FromSeconds(m_SecondsPosition));

    So what do you think? Where is the sound quality degradation coming from, and is there a way to fix it?

    • Hi, sorry for the late reply.
      Nice find with the BASS_SAMPLE_8BITS, I didn’t notice it in the documentation. 😉
      It’s strange that I never noticed problems due to the lacking flag.
      I din’t use the code in any real project so I don’t really know if the code have problems with specific mp3 files. I tried now with 10 mp3 and never had audio problems. Can you share a file to do some test?

      EDIT: I read now on the BASS forum that you fixed your problem. 🙂

    • I tested it right now on my machine (update to version 1709) and the example works.
      I played 30 seconds of a track (encoded in mp3).
      What problem do you have?

      • I made another test and the application stops working if you set the target version to “Fall creator update”.
        It seems Microsoft broke the legacy background media playback (it is marked as “obsolete” but it is actually not working).
        I tried to use the single process model and this way the app works fine. I sent the updated example to your email address. 😉

  2. hello there!, i’m trying to build this demo app, i get error message

    CS0006 Could not find metadata ‘C:\Users\usuario\Documents\Visual Studio 2017\Projects\BassUWPTest\BackgroundPlayer\bin\ARM\Debug\BackgroundPlayer.winmd’ BassUWPTest C:\Users\usuario\Documents\Visual Studio 2017\Projects\BassUWPTest\BassUWPTest\CSC

      • The record code is just standard bass stuff. RecordStart etc. A wavewriter isn’t needed to test. If your getting data to the RECRROC it should be good to go. Fall Creators Update broke the playing of a silent wav to fool the sys that a file was playing…so you could record.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.