adrianstevens / Xamarin-Plugins

Cross-platform Plugins for Xamarin, Xamarin.Forms and Windows
https://www.nuget.org/packages/Xam.Plugin.SimpleAudioPlayer/
MIT License
133 stars 53 forks source link

(Android) Use SoundPool for low-latency sounds #38

Open BioTurboNick opened 5 years ago

BioTurboNick commented 5 years ago

I had a severe issue with latency and bunched up sounds when using this plugin on Android for sound effects.

I found SoundPool, and simply by changing the backing Android class to SoundPool instead of MediaPlayer, I was able to eliminate this latency.

I'm not submitting a pull request because my solution is hacky. However, this is the construction, if you wish to use it as a starting point;

public SimpleAudioPlayerPoolImplementation()
{
    var audioAttributes = new Android.Media.AudioAttributes.Builder()
        .SetContentType(Android.Media.AudioContentType.Sonification)
        .SetUsage(Android.Media.AudioUsageKind.AssistanceSonification)
        .SetLegacyStreamType(Android.Media.Stream.Music)
        .Build();
    pool = new Android.Media.SoundPool.Builder()
        .SetAudioAttributes(audioAttributes)
        .Build();

    SetVolume(_volume, _balance);
}

Loading methods are similar, but assign priority and save a soundId b/c meant for multiple sounds. Play method requires volume and looping set and all playback methods require the soundId. No Completed event available.

Probably not the most efficient since it is meant to load and play multiple small files, but it worked with just one file per pool.

adrianstevens commented 5 years ago

This is great - I'll do some additional research, we could potentially swap out MediaPlayer or make this an option.

BioTurboNick commented 5 years ago

I did notice something weird recently when using this technique, just to warn you: if there's background audio playing, there's a delay and the sounds may not play through headphones. I may have misconfigured something though.

BioTurboNick commented 5 years ago

Update: the issues I described above were due to setting the Android.Media.AudioFlags.AudibilityEnforced flag. Removing it allowed headphone playback and did not interfere with playback of background audio. Original post's code updated.

BioTurboNick commented 4 years ago

In this vein, I wanted to present my variant that acts like Android's SoundPool. iOS has no issues with latency, but Android does. This standardizes the usage of the two.

I've also adapted the multi targeting project approach used by InAppBilling for simpler code.

A lot of capabilities stripped down, some could be added back.

A further note: I have still needed to create several sound pools and rotate them in a Queue if I want to be sure two of the same sound can play simultaneously. (Though I could also have loaded the same sound twice and addressed it with different identifiers.)

I discovered this was all necessary when I blew out Android's thread engine trying to spin up a hundred SoundPool objects simultaneously with one sound each. 😬

It's possible that people who use your SoundPlayer will prefer the SoundPool approach when using sound effects vs. playing music or other long-running sounds.

I don't think it's worth me maintaining a whole separate project.

SimpleAudioPool.zip

A note: There's an off-by-one error in iOS implementation that you'll need to fix for it to work. Increment returned indexes in it by 1 and decrement passed indexes by 1. Also add a using statement to each of the Load methods.