This project has moved and is read-only. For the latest updates, please go here.

Windows 8 background audio [solved]

Mar 25, 2014 at 1:00 PM
Hello

I'm implementing NAudio to a music player app. Previously the app has had MediaElement control which handles audio playing but now because of new requirements like fading next sound in and previous out, I'm trying to use NAudio for this, and have been successful for most parts.

But what I haven't been able to do is play audio in background similar to how MediaElement works when AudioCategory is set to AudioCategory.BackgroundCapableMedia. The way that works is that if I switch active app to something else, audio still continues to play. The way my NAudio implementation works at the moment is so that the music fades out immediately.

Is such a thing possible with current build of NAudio for Windows Store apps? And if not, is it something that comes in one of the next versions of NAudio? Is it even possible through WASAPI? The only way I see it could work at the moment is that a right parameter is passed using PropVariant class but I don't have a clue what that might.

Regards,
Janne
Mar 25, 2014 at 1:03 PM
hi, I'm afraid I don't have knowledge of how to do background audio in Windows Store apps. You'd probably need to do some digging around on MSDN to see if you can find what you're supposed to do with WASAPI (find the PropVariant), and then we can get that added into NAudio as an option.
Mar 25, 2014 at 4:37 PM
I'll take another look at this tomorrow but in case you got time + interest, I think I came pretty close to the solution.

I found this nice WASAPI C++ sample which is capable of background audio:
http://code.msdn.microsoft.com/windowsapps/Windows-Audio-Session-22dcab6b

I figured out the code required should be something like this. However, I wasn't able to get it work:

Replace Activate method inside WasapiOutRT.cs with these:
    //    typedef struct AudioClientProperties
    //{
    //UINT32 cbSize;
    //BOOL bIsOffload;
    //AUDIO_STREAM_CATEGORY eCategory;
    //AUDCLNT_STREAMOPTIONS Options;
    //}     AudioClientProperties;

    [StructLayout(LayoutKind.Sequential)]
    public struct AudioClientProperties
    {
        public UInt32 cbSize;
        public int bIsOffload;
        public AUDIO_STREAM_CATEGORY eCategory;
        public AUDCLNT_STREAMOPTIONS Options;
    }

    //-------------------------------------------------------------------------
    // Description: Audio stream categories
    //
    // BackgroundCapableMedia  - Music, Streaming audio
    // ForegroundOnlyMedia     - Video with audio
    // Communications          - VOIP, chat, phone call
    // Alerts                  - Alarm, Ring tones
    // SoundEffects            - Sound effects, clicks, dings
    // GameEffects             - Game sound effects
    // GameMedia               - Background audio for games
    // Other                   - All other streams (default)
    //
    public enum AUDIO_STREAM_CATEGORY
    {
        AudioCategory_Other = 0,
        AudioCategory_ForegroundOnlyMedia,
        AudioCategory_BackgroundCapableMedia,
        AudioCategory_Communications,
        AudioCategory_Alerts,
        AudioCategory_SoundEffects,
        AudioCategory_GameEffects,
        AudioCategory_GameMedia,
    }

    public enum AUDCLNT_STREAMOPTIONS
    {
        AUDCLNT_STREAMOPTIONS_NONE = 0,
        AUDCLNT_STREAMOPTIONS_RAW = 0x1
    }


    private async Task Activate()
    {
        var icbh = new ActivateAudioInterfaceCompletionHandler(
            ac2 =>
            {
                var prop = new AudioClientProperties();
                prop.cbSize = (uint)Marshal.SizeOf(prop);
                prop.bIsOffload = 0;
                prop.eCategory = AUDIO_STREAM_CATEGORY.AudioCategory_BackgroundCapableMedia;
                prop.Options = AUDCLNT_STREAMOPTIONS.AUDCLNT_STREAMOPTIONS_NONE;
                IntPtr p = Marshal.AllocHGlobal(Marshal.SizeOf(prop));
                Marshal.StructureToPtr(prop, p, false);
                ac2.SetClientProperties(p);

                /*var wfx = new WaveFormat(44100, 16, 2);
            int hr = ac2.Initialize(AudioClientShareMode.Shared,
                           AudioClientStreamFlags.EventCallback | AudioClientStreamFlags.NoPersist,
                           10000000, 0, wfx, IntPtr.Zero);*/
            });
        var IID_IAudioClient2 = new Guid("726778CD-F60A-4eda-82DE-E47610CD78AA");
        IActivateAudioInterfaceAsyncOperation activationOperation;
        NativeMethods.ActivateAudioInterfaceAsync(device, IID_IAudioClient2, IntPtr.Zero, icbh, out activationOperation);
        var audioClient2 = await icbh;
        this.audioClient = new AudioClient((IAudioClient)audioClient2);
    }

I changed NAudioWin8Demo's target to Windows 8.1 and added Background audio capability in appxmanifest but with no luck.

I'll get back to this tomorrow and try to find out what I am missing or doing wrong.
  • Janne
Mar 26, 2014 at 9:49 AM
It seemed that the above code actually was working but for some reason, not in the NAudioWin8Demo that I tried it in. When I created new test project, it started working. And in NAudioWin8Demo it started working if I set AudioStreamCategory to Communications.

Here is how the code looks after cleaning, in case you want to implement it too. Feel free to make changes if these changes fight against the architectural ideology behind NAudio:

(All changes are just into Win8 project, nothing was added to NAudio core project)

Add the following enums and struct to CoreAudioApi folder
namespace NAudio.CoreAudioApi
{
    /// <summary>
    /// Specifies the category of an audio stream.
    /// </summary>
    public enum AudioStreamCategory
    {
        /// <summary>
        /// Other audio stream.
        /// </summary>
        Other = 0,
        /// <summary>
        /// Media that will only stream when the app is in the foreground.
        /// </summary>
        ForegroundOnlyMedia,
        /// <summary>
        /// Media that can be streamed when the app is in the background.
        /// </summary>
        BackgroundCapableMedia,
        /// <summary>
        /// Real-time communications, such as VOIP or chat.
        /// </summary>
        Communications,
        /// <summary>
        /// Alert sounds.
        /// </summary>
        Alerts,
        /// <summary>
        /// Sound effects.
        /// </summary>
        SoundEffects,
        /// <summary>
        /// Game sound effects.
        /// </summary>
        GameEffects,
        /// <summary>
        /// Background audio for games.
        /// </summary>
        GameMedia,
    }
}

namespace NAudio.CoreAudioApi
{
    /// <summary>
    /// Defines values that describe the characteristics of an audio stream.
    /// </summary>
    public enum AudioClientStreamOptions
    {
        /// <summary>
        /// No stream options.
        /// </summary>
        None = 0,
        /// <summary>
        /// The audio stream is a 'raw' stream that bypasses all signal processing except for endpoint specific, always-on processing in the APO, driver, and hardware.
        /// </summary>
        Raw = 0x1
    }
}

namespace NAudio.CoreAudioApi
{
    /// <summary>
    /// The AudioClientProperties structure is used to set the parameters that describe the properties of the client's audio stream.
    /// </summary>
    /// <remarks>http://msdn.microsoft.com/en-us/library/windows/desktop/hh968105(v=vs.85).aspx</remarks>
    [StructLayout(LayoutKind.Sequential)]
    public struct AudioClientProperties
    {
        /// <summary>
        /// The size of the buffer for the audio stream.
        /// </summary>
        public UInt32 cbSize;
        /// <summary>
        /// Boolean value to indicate whether or not the audio stream is hardware-offloaded
        /// </summary>
        public int bIsOffload;
        /// <summary>
        /// An enumeration that is used to specify the category of the audio stream.
        /// </summary>
        public AudioStreamCategory eCategory;
        /// <summary>
        /// A bit-field describing the characteristics of the stream. Supported in Windows 8.1 and later.
        /// </summary>
        public AudioClientStreamOptions Options;
    }
}
Add this to WaveOutputs/IWavePlayer
        
        /// <summary>
        /// Sets the parameters that describe the properties of the client's audio stream.
        /// </summary>
        /// <param name="useHardwareOffload">Boolean value to indicate whether or not the audio stream is hardware-offloaded.</param>
        /// <param name="category">An enumeration that is used to specify the category of the audio stream.</param>
        /// <param name="options">A bit-field describing the characteristics of the stream. Supported in Windows 8.1 and later.</param>
        void SetClientProperties(bool useHardwareOffload, AudioStreamCategory category, AudioClientStreamOptions options);
Changes to WasapiOutRT:
        /// <summary>
        /// Properties of the client's audio stream.
        /// </summary>
        private AudioClientProperties? audioClientProperties = null;

        /// <summary>
        /// Sets the parameters that describe the properties of the client's audio stream.
        /// </summary>
        /// <param name="useHardwareOffload">Boolean value to indicate whether or not the audio stream is hardware-offloaded.</param>
        /// <param name="category">An enumeration that is used to specify the category of the audio stream.</param>
        /// <param name="options">A bit-field describing the characteristics of the stream. Supported in Windows 8.1 and later.</param>
        public void SetClientProperties(bool useHardwareOffload, AudioStreamCategory category, AudioClientStreamOptions options)
        {
            var audioClientProperties = new AudioClientProperties();
            audioClientProperties.cbSize = (uint)Marshal.SizeOf<AudioClientProperties>();
            audioClientProperties.bIsOffload = Convert.ToInt32(useHardwareOffload);
            audioClientProperties.eCategory = category;
            audioClientProperties.Options = options;
            this.audioClientProperties = audioClientProperties;
        }


        private async Task Activate()
        {
            var icbh = new ActivateAudioInterfaceCompletionHandler(
                ac2 =>
                {
                    if (this.audioClientProperties != null)
                    {
                        IntPtr p = Marshal.AllocHGlobal(Marshal.SizeOf(this.audioClientProperties.Value));
                        Marshal.StructureToPtr(this.audioClientProperties.Value, p, false);
                        ac2.SetClientProperties(p);
                    }

                    /*var wfx = new WaveFormat(44100, 16, 2);
                int hr = ac2.Initialize(AudioClientShareMode.Shared,
                               AudioClientStreamFlags.EventCallback | AudioClientStreamFlags.NoPersist,
                               10000000, 0, wfx, IntPtr.Zero);*/
                });
            var IID_IAudioClient2 = new Guid("726778CD-F60A-4eda-82DE-E47610CD78AA");
            IActivateAudioInterfaceAsyncOperation activationOperation;
            NativeMethods.ActivateAudioInterfaceAsync(device, IID_IAudioClient2, IntPtr.Zero, icbh, out activationOperation);
            var audioClient2 = await icbh;
            this.audioClient = new AudioClient((IAudioClient)audioClient2);
        }
When testing, call SetClientProperties before Init(). Remember to set background capability in appxmanifest.

I haven't tested the changes much yet and all testing I have done is with Windows 8.1. And AudioClientProperties.Options is only supported in Windows 8.1 as the comments state so I'm not sure what happens when trying to set it in Windows 8.0.

Regards,
Janne
Mar 26, 2014 at 10:11 AM
thanks for this, I'll look to getting this into NAudio when I next do some work on the Windows Store part
Apr 10, 2014 at 10:37 PM
OK, I've put this in although there are a few small changes I've made. This doesn't belong in IWavePlayer as it is only relevant to WASAPI.
Also, I'd ideally like to avoid the Marshal.StructureToPtr. Is there any chance you could try testibg this with a different signature in IAudioClient2?
void SetClientProperties([In, MarshalAs(UnmanagedType.LPStruct)] AudioClientProperties pProperties);
Apr 26, 2014 at 9:47 PM
Thank you a lot for this solution Jannera. For me too it works on my project, if I set AudioStreamCategory to Communications and not if I set to BackgroundCapableMedia. Have you find for which reasons since last time ?