I have some small mp3 files that I want to play as notification sounds in my application. Just to be clear, I have already created a "raw" folder under Resources and added my mp3 files there. As a first pass, I was just using the MediaPlayer like this:
MediaPlayer mPlayer;
mPlayer = MediaPlayer.Create(context, Resource.Raw.warning);
mPlayer.Start();
And that worked but it seemed a little clunky when all I want to do is use them as notifications, not for playing audio tracks. I also think there is more overhead with that approach to make sure the player is done playing, disposed, etc...
So, I found the SoundPool and implemented that. There were some early questions, for example, when you load a sound into the SoundPool, it returns an int for the SoundID which you then use later as a reference to the sound you want to play. Therefore, I had to create a separate class to handle those properties as well.
This is what I have so far and it seems to be working but I am wondering if this is the best way still?
First, I created a class to wrap the SoundPool up and it has a class for the SoundID as well.
using Android.Content;
using Android.Media;
namespace PocketPacTracAndroid.Media
{
public class Sounds
{
SoundPool soundPool;
Context context;
int maxStreams = 1;
bool isLoaded = false;
public bool IsLoaded
{
get { return isLoaded; }
}
public Sounds(Context oContext)
{
soundPool = new SoundPool(maxStreams, Stream.Music, 0);
context = oContext;
loadSoundPool();
}
public Sounds(Context oContext, int streams)
{
maxStreams = streams;
soundPool = new SoundPool(maxStreams, Stream.Music, 0);
loadSoundPool();
}
private void loadSoundPool()
{
soundPool.LoadComplete += SoundPool_LoadComplete;
SoundIDs sid = new SoundIDs();
sid.Scan = soundPool.Load(context, Resource.Raw.scan, 1);
sid.PackageAdded = soundPool.Load(context, Resource.Raw.packageAdded, 1);
sid.HubTransfer = soundPool.Load(context, Resource.Raw.hubtransfer, 1);
sid.Alert = soundPool.Load(context, Resource.Raw.alert, 1);
sid.Warning = soundPool.Load(context, Resource.Raw.warning, 1);
sid.ScanChange = soundPool.Load(context, Resource.Raw.scanchange, 1);
}
private void SoundPool_LoadComplete(object sender, SoundPool.LoadCompleteEventArgs e)
{
isLoaded = true;
}
public void playSound(int sid)
{
if (isLoaded)
{
soundPool.Play(sid, 1f, 1f, 1, 0, 1f);
}
}
}
public class SoundIDs
{
int scan;
int packageAdded;
int hubTransfer;
int alert;
int warning;
int scanChange;
public int Scan
{
get { return scan; }
set { scan = value; }
}
public int PackageAdded
{
get { return packageAdded; }
set { packageAdded = value; }
}
public int HubTransfer
{
get { return hubTransfer; }
set { hubTransfer = value; }
}
public int Alert
{
get { return alert; }
set { alert = value; }
}
public int Warning
{
get { return warning; }
set { warning = value; }
}
public int ScanChange
{
get { return scanChange; }
set { scanChange = value; }
}
}
}
Then, from anywhere in my app, I instantiate the classes:
Sounds sounds;
SoundIDs sid;
public override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
context = Context;
getServiceTypes();
sounds = new Sounds(context);
sid = new SoundIDs();
}
and finally, I can call this to play the sound based on my soundID that was returned when I loaded in the files. I also exposed IsLoaded in case I want to check that externally but it is checked internally when playSound is called.
sounds.playSound(sid.Scan);
Does this seem like a good approach? Is it the best? Any suggestions would be appreciated. It does seem to work but I just want to make sure I won't have any resource issues or playback issues if I call the same or different notifications over and over again during the application lifecycle.
Thanks!
Yes SoundPool is the way to go. Just watch out for threads playing different sounds at the same time. Unless you build in a queue, some sounds will block others as there is only one speaker.
Related
I'm programming a small widget that needs to be updated whenever the user changes the ringer volume or the vibrate settings.
Capturing android.media.VIBRATE_SETTING_CHANGED works just fine for the vibrate settings, but I haven't found any way of getting notified when the ringer volume changes and although I could try to capture when the user presses the volume up/volume down physical keys, there are many other options for changing the volume without using these keys.
Do you know if there's any broadcast action defined for this or any way to create one or to solve the problem without it?
There is no broadcast action, but I did find you can hook up a content observer to get notified when the settings change, volume of streams being some of those settings. Register for the android.provider.Settings.System.CONTENT_URI to be notified of all settings changes:
mSettingsContentObserver = new SettingsContentObserver( new Handler() );
this.getApplicationContext().getContentResolver().registerContentObserver(
android.provider.Settings.System.CONTENT_URI, true,
mSettingsContentObserver );
The content observer might look something like this:
public class SettingsContentObserver extends ContentObserver {
public SettingsContentObserver(Handler handler) {
super(handler);
}
#Override
public boolean deliverSelfNotifications() {
return super.deliverSelfNotifications();
}
#Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
Log.v(LOG_TAG, "Settings change detected");
updateStuff();
}
}
And be sure to unregister the content observer at some point.
Nathan's code works but gives two notifications for each change system settings. To avoid that, use the following
public class SettingsContentObserver extends ContentObserver {
int previousVolume;
Context context;
public SettingsContentObserver(Context c, Handler handler) {
super(handler);
context=c;
AudioManager audio = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
previousVolume = audio.getStreamVolume(AudioManager.STREAM_MUSIC);
}
#Override
public boolean deliverSelfNotifications() {
return super.deliverSelfNotifications();
}
#Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
AudioManager audio = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
int currentVolume = audio.getStreamVolume(AudioManager.STREAM_MUSIC);
int delta=previousVolume-currentVolume;
if(delta>0)
{
Logger.d("Decreased");
previousVolume=currentVolume;
}
else if(delta<0)
{
Logger.d("Increased");
previousVolume=currentVolume;
}
}
}
Then in your service onCreate register it with:
mSettingsContentObserver = new SettingsContentObserver(this,new Handler());
getApplicationContext().getContentResolver().registerContentObserver(android.provider.Settings.System.CONTENT_URI, true, mSettingsContentObserver );
Then unregister in onDestroy:
getApplicationContext().getContentResolver().unregisterContentObserver(mSettingsContentObserver);
Yes, you can register a receiver for a volume change(this is kind of a hack, but works), I managed to do it this way (does not involve a ContentObserver):
In manifest xml file:
<receiver android:name="com.example.myproject.receivers.MyReceiver" >
<intent-filter>
<action android:name="android.media.VOLUME_CHANGED_ACTION" />
</intent-filter>
</receiver>
BroadcastReceiver:
public class MyReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals("android.media.VOLUME_CHANGED_ACTION")) {
Log.d("Music Stream", "has changed");
}
}
}
hope it helps!
Based into Nathan's, adi's and swooby's code I created a full working example with some minor improvements.
Looking to the AudioFragment class we can see how easy is to listen for volume changes with our custom ContentObserver.
public class AudioFragment extends Fragment implements OnAudioVolumeChangedListener {
private AudioVolumeObserver mAudioVolumeObserver;
#Override
public void onResume() {
super.onResume();
// initialize audio observer
if (mAudioVolumeObserver == null) {
mAudioVolumeObserver = new AudioVolumeObserver(getActivity());
}
/*
* register audio observer to identify the volume changes
* of audio streams for music playback.
*
* It is also possible to listen for changes in other audio stream types:
* STREAM_RING: phone ring, STREAM_ALARM: alarms, STREAM_SYSTEM: system sounds, etc.
*/
mAudioVolumeObserver.register(AudioManager.STREAM_MUSIC, this);
}
#Override
public void onPause() {
super.onPause();
// release audio observer
if (mAudioVolumeObserver != null) {
mAudioVolumeObserver.unregister();
}
}
#Override
public void onAudioVolumeChanged(int currentVolume, int maxVolume) {
Log.d("Audio", "Volume: " + currentVolume + "/" + maxVolume);
Log.d("Audio", "Volume: " + (int) ((float) currentVolume / maxVolume) * 100 + "%");
}
}
public class AudioVolumeContentObserver extends ContentObserver {
private final OnAudioVolumeChangedListener mListener;
private final AudioManager mAudioManager;
private final int mAudioStreamType;
private int mLastVolume;
public AudioVolumeContentObserver(
#NonNull Handler handler,
#NonNull AudioManager audioManager,
int audioStreamType,
#NonNull OnAudioVolumeChangedListener listener) {
super(handler);
mAudioManager = audioManager;
mAudioStreamType = audioStreamType;
mListener = listener;
mLastVolume = audioManager.getStreamVolume(mAudioStreamType);
}
/**
* Depending on the handler this method may be executed on the UI thread
*/
#Override
public void onChange(boolean selfChange, Uri uri) {
if (mAudioManager != null && mListener != null) {
int maxVolume = mAudioManager.getStreamMaxVolume(mAudioStreamType);
int currentVolume = mAudioManager.getStreamVolume(mAudioStreamType);
if (currentVolume != mLastVolume) {
mLastVolume = currentVolume;
mListener.onAudioVolumeChanged(currentVolume, maxVolume);
}
}
}
#Override
public boolean deliverSelfNotifications() {
return super.deliverSelfNotifications();
}
}
public class AudioVolumeObserver {
private final Context mContext;
private final AudioManager mAudioManager;
private AudioVolumeContentObserver mAudioVolumeContentObserver;
public AudioVolumeObserver(#NonNull Context context) {
mContext = context;
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
}
public void register(int audioStreamType,
#NonNull OnAudioVolumeChangedListener listener) {
Handler handler = new Handler();
// with this handler AudioVolumeContentObserver#onChange()
// will be executed in the main thread
// To execute in another thread you can use a Looper
// +info: https://stackoverflow.com/a/35261443/904907
mAudioVolumeContentObserver = new AudioVolumeContentObserver(
handler,
mAudioManager,
audioStreamType,
listener);
mContext.getContentResolver().registerContentObserver(
android.provider.Settings.System.CONTENT_URI,
true,
mAudioVolumeContentObserver);
}
public void unregister() {
if (mAudioVolumeContentObserver != null) {
mContext.getContentResolver().unregisterContentObserver(mAudioVolumeContentObserver);
mAudioVolumeContentObserver = null;
}
}
}
public interface OnAudioVolumeChangedListener {
void onAudioVolumeChanged(int currentVolume, int maxVolume);
}
Hope it's still useful for someone! :)
Nathan's and adi's code works, but can be cleaned up and self-contained to:
public class AudioStreamVolumeObserver
{
public interface OnAudioStreamVolumeChangedListener
{
void onAudioStreamVolumeChanged(int audioStreamType, int volume);
}
private static class AudioStreamVolumeContentObserver
extends ContentObserver
{
private final AudioManager mAudioManager;
private final int mAudioStreamType;
private final OnAudioStreamVolumeChangedListener mListener;
private int mLastVolume;
public AudioStreamVolumeContentObserver(
#NonNull
Handler handler,
#NonNull
AudioManager audioManager, int audioStreamType,
#NonNull
OnAudioStreamVolumeChangedListener listener)
{
super(handler);
mAudioManager = audioManager;
mAudioStreamType = audioStreamType;
mListener = listener;
mLastVolume = mAudioManager.getStreamVolume(mAudioStreamType);
}
#Override
public void onChange(boolean selfChange)
{
int currentVolume = mAudioManager.getStreamVolume(mAudioStreamType);
if (currentVolume != mLastVolume)
{
mLastVolume = currentVolume;
mListener.onAudioStreamVolumeChanged(mAudioStreamType, currentVolume);
}
}
}
private final Context mContext;
private AudioStreamVolumeContentObserver mAudioStreamVolumeContentObserver;
public AudioStreamVolumeObserver(
#NonNull
Context context)
{
mContext = context;
}
public void start(int audioStreamType,
#NonNull
OnAudioStreamVolumeChangedListener listener)
{
stop();
Handler handler = new Handler();
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
mAudioStreamVolumeContentObserver = new AudioStreamVolumeContentObserver(handler, audioManager, audioStreamType, listener);
mContext.getContentResolver()
.registerContentObserver(System.CONTENT_URI, true, mAudioStreamVolumeContentObserver);
}
public void stop()
{
if (mAudioStreamVolumeContentObserver == null)
{
return;
}
mContext.getContentResolver()
.unregisterContentObserver(mAudioStreamVolumeContentObserver);
mAudioStreamVolumeContentObserver = null;
}
}
If its only ringer mode change you can use Brodcast receiver with "android.media.RINGER_MODE_CHANGED" as the action. It will easy to implement
Hi i tried the code above and it did not work for me. But when i tried to add this line
getActivity().setVolumeControlStream(AudioManager.STREAM_MUSIC);
and put
mSettingsContentObserver = new SettingsContentObserver(this,new Handler());
getApplicationContext().getContentResolver().registerContentObserver(android.provider.Settings.System.CONTENT_URI, true, mSettingsContentObserver );
It works now. My concern is how to hide the volume dialog onchange. See this image.
private const val EXTRA_VOLUME_STREAM_TYPE = "android.media.EXTRA_VOLUME_STREAM_TYPE"
private const val VOLUME_CHANGED_ACTION = "android.media.VOLUME_CHANGED_ACTION"
val filter = IntentFilter(VOLUME_CHANGED_ACTION)
filter.addAction(RINGER_MODE_CHANGED_ACTION)
val receiver = object : BroadcastReceiver() {
override fun onReceive(context1: Context, intent: Intent) {
val stream = intent.getIntExtra(EXTRA_VOLUME_STREAM_TYPE, UNKNOWN)
val mode = intent.getIntExtra(EXTRA_RINGER_MODE, UNKNOWN)
val volumeLevel = audioManager.getStreamVolume(stream)
}
}
100% working way in all cases
public class SettingsContentObserver extends ContentObserver {
SettingsContentObserver(Handler handler) {
super(handler);
}
#Override
public boolean deliverSelfNotifications() {
return super.deliverSelfNotifications();
}
#Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
volumeDialogContract.updateMediaVolume(getMediaVolume());
}
int getMediaVolume() {
return audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
}
void unRegisterVolumeChangeListener() {
volumeDialogContract.getAppContext().getApplicationContext().getContentResolver().
unregisterContentObserver(settingsContentObserver);
}
void registerVolumeChangeListener() {
settingsContentObserver = new VolumeDialogPresenter.SettingsContentObserver(new Handler());
volumeDialogContract.getAppContext().getApplicationContext().getContentResolver().registerContentObserver(
android.provider.Settings.System.CONTENT_URI, true,
settingsContentObserver);
}
Using the CSCore library, I wrote the code for playing an mp3 file in the class BGM in a seperate file called BGM.cs and the method for playback is BGM.Play("file directory");, which is called in the Form. But somehow I can't manage to get any sound out of it. I've already checked volume, codec and output, and I can't think of anything else that might cause this problem.
This is the code of the class file:
public class BGM
{
public static void Play(string file)
{
using (IWaveSource soundSource = GetSoundSource(file))
{
using (ISoundOut soundOut = GetSoundOut())
{
soundOut.Initialize(soundSource);
soundOut.Volume = 0.8f;
soundOut.Play();
}
}
}
private static ISoundOut GetSoundOut()
{
if (WasapiOut.IsSupportedOnCurrentPlatform)
return new WasapiOut();
else
return new DirectSoundOut();
}
private static IWaveSource GetSoundSource(string file)
{
return CodecFactory.Instance.GetCodec(file);
}
There are actually a couple reasons why your mp3 isn't playing.
The first reason is you haven't specified a device for the sound to play on. The code below gets the first device that can render sound, but that won't always be correct if the user has multiple devices attached to their computer. You'll have to handle that appropriately. The device has to be set on the WasapiOut object.
The second reason is your use of using statements in your Play method. While it's always a good idea to clean up objects that implement IDisposable, you can't always do so immediately. In this case, soundOut.Play() is not a blocking method, which meant that control was exiting the method immediately, causing Dispose() to be called on soundOut and soundSource. This meant that the sound would effectively never be played (maybe it would start for a short moment, but not enough to really hear it). Essentially, you need to hold onto the references and only dispose of them once playback is complete.
Have a look at the AudioPlayerSample for an idea on how to implement a complete solution. My code should get you started.
void Main()
{
using(var player = new BGM(#"D:\Test.mp3"))
{
player.Play();
// TODO: Need to wait here in order for playback to complete
// Otherwise, you need to hold onto the player reference and dispose of it later
Console.ReadLine();
}
}
public class BGM : IDisposable
{
private bool _isDisposed = false;
private ISoundOut _soundOut;
private IWaveSource _soundSource;
public BGM(string file)
{
_soundSource = CodecFactory.Instance.GetCodec(file);
_soundOut = GetSoundOut();
_soundOut.Initialize(_soundSource);
}
public void Play()
{
if(_soundOut != null)
{
_soundOut.Volume = 0.8f;
_soundOut.Play();
}
}
public void Stop()
{
if(_soundOut != null)
{
_soundOut.Stop();
}
}
private static ISoundOut GetSoundOut()
{
if (WasapiOut.IsSupportedOnCurrentPlatform)
{
return new WasapiOut
{
Device = GetDevice()
};
}
return new DirectSoundOut();
}
private static IWaveSource GetSoundSource(string file)
{
return CodecFactory.Instance.GetCodec(file);
}
public static MMDevice GetDevice()
{
using(var mmdeviceEnumerator = new MMDeviceEnumerator())
{
using(var mmdeviceCollection = mmdeviceEnumerator.EnumAudioEndpoints(DataFlow.Render, DeviceState.Active))
{
// This uses the first device, but that isn't what you necessarily want
return mmdeviceCollection.First();
}
}
}
protected virtual void Dispose(bool disposing)
{
if (!_isDisposed)
{
if (disposing)
{
if(_soundOut != null)
{
_soundOut.Dispose();
}
if(_soundSource != null)
{
_soundSource.Dispose();
}
}
_isDisposed = true;
}
}
public void Dispose()
{
Dispose(true);
}
}
Recently I wanted to play around with some audio stuff and looking for some audio library I have found CSCore. As I am newbie in coding sound at that abstraction level (quite low to me) I am struggling some issues with playback.
What I aim to achieve is to be able to play the same in memory sound buffer multiple times at the same time (in parallel). How is it intended to be done in CSCore?
At first I was struggling to play anything in parallel (two different sounds). What I finally understood reading CSCore samples and its source at GitHub is that in order to play anything in parallel using single SoundOut I need to create SampleSource or WaveSource that performs sound mixing. So with help of mixer sample that is provided in GitHub I have implemented my own simple sound mixer (that is ISampleSource mixing several ISampleSource-s). Great! It is playing two sounds at the same time from two different sources.
But here is where I got stuck. Loading single sound from disk into memory I want to be able to play it several times, possibly overlapping (parallel playback). Like I have 60 seconds sound that I start playing and after 5 seconds I want to start it another time that those are overlapping in what you can hear.
When I just added the same ISampleSource (my sound I want to play) two times into mixer list then I get strange output (sound is playing faster with glitches). I assume that it is due to reading the same stream (and moving its position forward) once by the first ISampleSource reference in mixer list and once by another one. To sum up, I cannot use the same ISampleSource to play it straight away in parallel as it is stateful.
Ok, so my second approach was to use Position to keep track of when a particular mixer list entry was when it was requested to Read(). I mean, when I add the same ISampleSource twice to mixer list it keeps additional data about Position for each list entry. So having the same ISampleSource given two times to mixer when it is asked to Read it goes through the list of sources to mix and it first sets its position to where it finished last time for this list entry and then it reads this source. So even if I use the same source twice its position is handled separately for each registration in mixer list. Great, I actually got what I expected - the same sound is playing several times in the same time (in parallel) but it is not crystal clear - I got some crackling in output like I would have physical cable defect. The problem is present even for a single sound being mixed and played. I spotted that when I comment out the line that is setting ISampleSource.Position then problem is gone. So I suppose that seeking the stream is a problem. Unfortunately I am not sure why exactly it is a problem. I saw in implementation of SampleSource/WaveSource that it's Position implementation aligns backing stream position with block-size alignment. Maybe that is the reason but I don't know for sure.
My next idea is to implement "SharedMemoryStream" that is something like MemoryStream but to be able to have the same actual memory buffer for several instances of such a stream. Then it would be tracking its position by itself (to avoid seeking that I have problem with) while having only one in-memory representation of loaded sound. I am not sure if it will work as expected (going to try it out soon) but some of my experiments showed me that it gonna be not so memory and CPU efficient - when using WaveFileReader it creates internally some WaveFileChunks that consume quite an amount of memory and time to be constructed. So I still see some other problems when using "SharedMemoryStream" to have separate stream with single buffer as I would need a WaveFileReader per stream and such a WaveFileReader would be needed per Play request (to enqueue ISampleSource in mixer).
Am I doing here something clearly wrong that only a total newbie would do?
PS. Sorry for long elaboration about my approaches and experiments. Just wanted to be clear where I am with my understanding of CSCore and audio processing in general. I am ready to remove unnecessary parts in my question/description.
Update: Added minimal code sample that let me reproduce the problem.
Here is a minimal example of my code that I have issue with.
// Program.cs
using CSCore;
using CSCore.Codecs;
using CSCore.SoundOut;
using CSCore.Streams;
namespace AudioProblem
{
internal static class Program
{
private static void Main(string[] args)
{
var soundOut = new WasapiOut();
var soundMixer = new SoundMixer();
var sound = LoadSound("Heroic Demise (New).mp3");
soundOut.Initialize(soundMixer.ToWaveSource());
soundOut.Play();
soundMixer.AddSound(sound);
// Use the same sample source to have the same sound in play after 5 seconds.
// So two sounds are playing at the same time but are phase shifted by 5 seconds.
//Thread.Sleep(TimeSpan.FromSeconds(5));
//soundMixer.AddSound(sound);
}
private static ISampleSource LoadSound(string filePath)
{
var waveFileReader = CodecFactory.Instance.GetCodec(filePath);
return new CachedSoundSource(waveFileReader).ToSampleSource();
}
}
}
// SoundMixer.cs
using System;
using System.Collections.Generic;
using CSCore;
namespace AudioProblem
{
internal class SoundMixer : ISampleSource
{
private readonly object _lock = new object();
private readonly List<SoundSource> _soundSources = new List<SoundSource>();
private float[] _internalBuffer;
public SoundMixer()
{
var sampleRate = 44100;
var bits = 32;
var channels = 2;
var audioEncoding = AudioEncoding.IeeeFloat;
WaveFormat = new WaveFormat(sampleRate, bits, channels, audioEncoding);
}
public int Read(float[] buffer, int offset, int count)
{
var numberOfSamplesStoredInBuffer = 0;
if (count > 0 && _soundSources.Count > 0)
lock (_lock)
{
Array.Clear(buffer, offset, count);
_internalBuffer = _internalBuffer.CheckBuffer(count);
for (var i = _soundSources.Count - 1; i >= 0; i--)
{
var soundSource = _soundSources[i];
// Here is the magic. Look at Read implementation.
soundSource.Read(_internalBuffer, count);
for (int j = offset, k = 0; k < soundSource.SamplesRead; j++, k++)
{
buffer[j] += _internalBuffer[k];
}
if (soundSource.SamplesRead > numberOfSamplesStoredInBuffer)
numberOfSamplesStoredInBuffer = soundSource.SamplesRead;
if (soundSource.SamplesRead == 0) _soundSources.Remove(soundSource);
}
}
return count;
}
public void Dispose()
{
throw new NotImplementedException();
}
public bool CanSeek => false;
public WaveFormat WaveFormat { get; }
public long Position
{
get => 0;
set => throw new NotSupportedException($"{nameof(SoundMixer)} does not support setting the {nameof(Position)}.");
}
public long Length => 0;
public void AddSound(ISampleSource sound)
{
lock (_lock)
{
_soundSources.Add(new SoundSource(sound));
}
}
private class SoundSource
{
private readonly ISampleSource _sound;
private long _position;
public SoundSource(ISampleSource sound)
{
_sound = sound;
}
public int SamplesRead { get; private set; }
public void Read(float[] buffer, int count)
{
// Set last remembered position (initially 0).
// If this line is commented out, sound in my headphones is clear. But with this line it is crackling.
// If this line is commented out, if two SoundSource use the same ISampleSource output is buggy,
// but if line is present those are playing correctly but with crackling.
_sound.Position = _position;
// Read count of new samples.
SamplesRead = _sound.Read(buffer, 0, count);
// Remember position to be able to continue from where this SoundSource has finished last time.
_position = _sound.Position;
}
}
}
}
Update 2: I have found solution that works for me - see below for details.
It looks like my initial idea about the problem is quite correct, but not 100% confirmed. I have introduced a counter that is executed in Read implementation of SoundSource to count how many samples was read in total to play the whole sound file. And what I got was different values for the case when I just played a stream directly and another case when I saved and restored position in each Read call. For the latter one I counted more samples than actual sound file contained therefore I assume some crackling appeared due to this over-carried samples. I suppose that position on ISampleSource level suffers from this issue as it aligns position to its internal block size, thus this property seems not sufficient to stop and continue at that level of precision.
So I have tried this idea with "SharedMemoryStream" to see if managing saving and restoration of position at a lower level would work. And it seems to do pretty well. Also my initial fear of heavy WaveSource/SampleSource creation using this approach seems not to be actually a problem - I have done some simple tests and it had very low CPU and Memory overhead.
Below is my code implementing this approach, if something is not clear or could be done better, or from the beginning it should be done in other way, please, let me know.
// Program.cs
using System;
using System.IO;
using System.Threading;
using CSCore;
using CSCore.Codecs.WAV;
using CSCore.SoundOut;
namespace AudioProblem
{
internal static class Program
{
private static void Main(string[] args)
{
var soundOut = new WasapiOut();
var soundMixer = new SoundMixer();
var sound = LoadSound("Heroic Demise (New).wav");
soundOut.Initialize(soundMixer.ToWaveSource());
soundOut.Play();
// Play first from shallow copy of shared stream
soundMixer.AddSound(new WaveFileReader(sound.MakeShared()).ToSampleSource());
Thread.Sleep(TimeSpan.FromSeconds(5));
// Play second from another shallow copy of shared stream
soundMixer.AddSound(new WaveFileReader(sound.MakeShared()).ToSampleSource());
Thread.Sleep(TimeSpan.FromSeconds(5));
soundOut.Stop();
}
private static SharedMemoryStream LoadSound(string filePath)
{
return new SharedMemoryStream(File.ReadAllBytes(filePath));
}
}
}
// SoundMixer.cs
using System;
using System.Collections.Generic;
using CSCore;
namespace AudioProblem
{
internal class SoundMixer : ISampleSource
{
private readonly List<SoundSource> _soundSources = new List<SoundSource>();
private readonly object _soundSourcesLock = new object();
private bool _disposed;
private float[] _internalBuffer;
public SoundMixer()
{
var sampleRate = 44100;
var bits = 32;
var channels = 2;
var audioEncoding = AudioEncoding.IeeeFloat;
WaveFormat = new WaveFormat(sampleRate, bits, channels, audioEncoding);
}
public int Read(float[] buffer, int offset, int count)
{
var numberOfSamplesStoredInBuffer = 0;
Array.Clear(buffer, offset, count);
lock (_soundSourcesLock)
{
CheckIfDisposed();
if (count > 0 && _soundSources.Count > 0)
{
_internalBuffer = _internalBuffer.CheckBuffer(count);
for (var i = _soundSources.Count - 1; i >= 0; i--)
{
var soundSource = _soundSources[i];
soundSource.Read(_internalBuffer, count);
for (int j = offset, k = 0; k < soundSource.SamplesRead; j++, k++)
{
buffer[j] += _internalBuffer[k];
}
if (soundSource.SamplesRead > numberOfSamplesStoredInBuffer)
numberOfSamplesStoredInBuffer = soundSource.SamplesRead;
if (soundSource.SamplesRead == 0)
{
_soundSources.Remove(soundSource);
soundSource.Dispose();
}
}
// TODO Normalize!
}
}
return count;
}
public void Dispose()
{
lock (_soundSourcesLock)
{
_disposed = true;
foreach (var soundSource in _soundSources)
{
soundSource.Dispose();
}
_soundSources.Clear();
}
}
public bool CanSeek => !_disposed;
public WaveFormat WaveFormat { get; }
public long Position
{
get
{
CheckIfDisposed();
return 0;
}
set => throw new NotSupportedException($"{nameof(SoundMixer)} does not support seeking.");
}
public long Length
{
get
{
CheckIfDisposed();
return 0;
}
}
public void AddSound(ISampleSource sound)
{
lock (_soundSourcesLock)
{
CheckIfDisposed();
// TODO Check wave format compatibility?
_soundSources.Add(new SoundSource(sound));
}
}
private void CheckIfDisposed()
{
if (_disposed) throw new ObjectDisposedException(nameof(SoundMixer));
}
private class SoundSource : IDisposable
{
private readonly ISampleSource _sound;
public SoundSource(ISampleSource sound)
{
_sound = sound;
}
public int SamplesRead { get; private set; }
public void Dispose()
{
_sound.Dispose();
}
public void Read(float[] buffer, int count)
{
SamplesRead = _sound.Read(buffer, 0, count);
}
}
}
}
// SharedMemoryStream.cs
using System;
using System.IO;
namespace AudioProblem
{
internal sealed class SharedMemoryStream : Stream
{
private readonly object _lock;
private readonly RefCounter _refCounter;
private readonly MemoryStream _sourceMemoryStream;
private bool _disposed;
private long _position;
public SharedMemoryStream(byte[] buffer) : this(new object(), new RefCounter(), new MemoryStream(buffer))
{
}
private SharedMemoryStream(object #lock, RefCounter refCounter, MemoryStream sourceMemoryStream)
{
_lock = #lock;
lock (_lock)
{
_refCounter = refCounter;
_sourceMemoryStream = sourceMemoryStream;
_refCounter.Count++;
}
}
public override bool CanRead
{
get
{
lock (_lock)
{
return !_disposed;
}
}
}
public override bool CanSeek
{
get
{
lock (_lock)
{
return !_disposed;
}
}
}
public override bool CanWrite => false;
public override long Length
{
get
{
lock (_lock)
{
CheckIfDisposed();
return _sourceMemoryStream.Length;
}
}
}
public override long Position
{
get
{
lock (_lock)
{
CheckIfDisposed();
return _position;
}
}
set
{
lock (_lock)
{
CheckIfDisposed();
_position = value;
}
}
}
// Creates another shallow copy of stream that uses the same underlying MemoryStream
public SharedMemoryStream MakeShared()
{
lock (_lock)
{
CheckIfDisposed();
return new SharedMemoryStream(_lock, _refCounter, _sourceMemoryStream);
}
}
public override void Flush()
{
}
public override long Seek(long offset, SeekOrigin origin)
{
lock (_lock)
{
CheckIfDisposed();
_sourceMemoryStream.Position = Position;
var seek = _sourceMemoryStream.Seek(offset, origin);
Position = _sourceMemoryStream.Position;
return seek;
}
}
public override void SetLength(long value)
{
throw new NotSupportedException($"{nameof(SharedMemoryStream)} is read only stream.");
}
// Uses position that is unique for each copy of shared stream
// to read underlying MemoryStream that is common for all shared copies
public override int Read(byte[] buffer, int offset, int count)
{
lock (_lock)
{
CheckIfDisposed();
_sourceMemoryStream.Position = Position;
var read = _sourceMemoryStream.Read(buffer, offset, count);
Position = _sourceMemoryStream.Position;
return read;
}
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotSupportedException($"{nameof(SharedMemoryStream)} is read only stream.");
}
// Reference counting to dispose underlying MemoryStream when all shared copies are disposed
protected override void Dispose(bool disposing)
{
lock (_lock)
{
if (disposing)
{
_disposed = true;
_refCounter.Count--;
if (_refCounter.Count == 0) _sourceMemoryStream?.Dispose();
}
base.Dispose(disposing);
}
}
private void CheckIfDisposed()
{
if (_disposed) throw new ObjectDisposedException(nameof(SharedMemoryStream));
}
private class RefCounter
{
public int Count;
}
}
}
Iv'e created a timer using dispatcher time :
time = new DispatcherTimer();
time.Interval = new TimeSpan(0, 0, 0, 0, 80);
and I use it for the speed of an object.Each tick the objects moves 10 pixels.
I would like to know how to increase the speed of the object without changing the pixels it moves each tick, meaning I want to make the timer itself faster during run time every 10 seconds or so.
Is there a way I can do it?Ive tried making a variable speed=0 and increasing it each time I count 10 and then
time.Interval = new TimeSpan(0, 0, 0, 0, 80-speed);
but The object stayed in the same speed.So do I have to make my own timer class instead of using the built in dispatcher time, if so how do I do that?or is there another solution for this?
I think that DispatcherTimer is not your best ally for this task. The class is by no means designed to execute actions at precise intervals.
I'll try to better explain myself: even if the DispatcherTimer, as its name suggests, dispatches actions timely and with a great precision, the dispatched actions will be queued and then executed when the underlying GUI thread decides to process them.
Normally, a GUI thread has a resolution of around 8ms (it's an approximation, but I don't think we need to measure this right now)... and you are using a starting Interval of 80ms that is going to decrease over time until it probably goes beyond that tolerance limit of 8ms or so. In the meanwhile, you are also repainting your interface (or part of it) over and over and this impacts the performance and the responsiveness of the GUI even more: if the GUI thread is busy repainting and that requires more than the Interval value to be accomplished, the next dispatched action will be processed only once the GUI thread completes the undergoing task.
If you need a more precise scheduling, avoiding hangings / losses of responsiveness / delayed actions, you need to use a timer class that runs in background like System.Threading.Timer (google for SyncronizationContext, that would be helpful) or System.Timers.Timer.
On the top of that, never play with intervals when showing a change in speed. Work with a fixed interval and increase/decrease the movement "size" in pixels. You should be able to calculate the delta without problems. Just to make things clearer: if I want to slow that the speed of an object doubled, I don't half the timer interval that draws the object, but I double the amount of pixels my object traverses at each step.
using System;
using System.Collections.Generic;
using System.Linq;
namespace CQRS_and_EventSourcing
{
internal class Program
{
//CQRS = command query responsibility segregation
//CQS= command query separation
//COMMAND
public class PersonStroge
{
Dictionary<int, Person> people;
}
public class Person
{
public int UniqueId;
public int age;
EventBroker broker;
public Person(EventBroker broker)
{
this.broker = broker;
broker.Commands += BrokerOnCommands;
broker.Queries += BrokeronQueries;
}
private void BrokeronQueries(object sender, Query query)
{
var ac = query as AgeQuery;
if (ac != null && ac.Target == this)
{
ac.Result = age;
}
}
private void BrokerOnCommands(object sender, Command command)
{
var cac = command as ChangeAgeCommand;
if (cac != null && cac.Target == this)
{
if (cac.Register)
broker.AllEvents.Add(new AgeChangedEvent(this, age, cac.Age));
age = cac.Age;
}
}
public bool CanVote => age >= 16;
}
public class EventBroker
{
//1. All events that happened.
public IList<Event> AllEvents = new List<Event>();
//2. Commands
public event EventHandler<Command> Commands;
//3. Query
public event EventHandler<Query> Queries;
public void Command(Command c)
{
Commands?.Invoke(this, c);
}
public T Query<T>(Query q)
{
Queries?.Invoke(this, q);
return (T)q.Result;
}
public void UndoLast()
{
var e = AllEvents.LastOrDefault();
var ac = e as AgeChangedEvent;
if (ac != null)
{
Command(new ChangeAgeCommand(ac.Target, ac.OldValue) { Register = false });
AllEvents.Remove(e);
}
}
}
public class Query
{
public object Result;
}
public class AgeQuery : Query
{
public Person Target;
}
public class Command : EventArgs
{
public bool Register = true;
}
public class ChangeAgeCommand : Command
{
public Person Target;
//public int TargetId;
public int Age;
public ChangeAgeCommand(Person target, int age)
{
Target = target;
Age = age;
}
}
public class Event
{
//backtrack
}
public class AgeChangedEvent : Event
{
public Person Target;
public int OldValue, NewValue;
public AgeChangedEvent(Person target, int oldValue, int newValue)
{
Target = target;
OldValue = oldValue;
NewValue = newValue;
}
public override string ToString()
{
return $"Age changed from {OldValue} to {NewValue}";
}
}
static void Main(string[] args)
{
var eb = new EventBroker();
var p = new Person(eb);
eb.Command(new ChangeAgeCommand(p, 123));
foreach (var e in eb.AllEvents)
{
Console.WriteLine(e);
}
//int age;
//age = eb.Query<int>(new AgeQuery { Target = p });
//Console.WriteLine(age);
//eb.UndoLast();
//foreach (var e in eb.AllEvents)
//{
// Console.WriteLine(e);
//}
//age = eb.Query<int>(new AgeQuery { Target = p });
//Console.WriteLine(age);
Console.ReadKey();
}
}
}
İf you couldnt make look at this repository;
[1]:https://github.com/kYasinAblay/DNesteruk.Additional.Lectures/blob/master/CQRS_and_EventSourcing/Program.cs
My PUMP can (inflate) my BALLOON. No problem! But when I try to use my PUMP to (pop) the BALLOON, it really doesn't work very well. I can keep using my PUMP and, eventually, it will (pop) the balloon, but my arm is getting really tired, and I want to (pop) it now. So, instead, I get my POINTY STICK and (pop)! Of course, my POINT STICK is even less effective at (inflate)ing my BALLOON, than the PUMP is at (pop)ing it.
Class Balloon
{
Private int _volume = 0;
Private bool _popped = false;
Public Balloon() { }
//Restrict calling to only a PUMP object
Internal Inflate()
{
if (_popped) return;
_volume += 1;
if (volume > 10) this.Pop();
}
//Restrict calling to only a POINTY STICK object
Internal Pop()
{
if (!_popped) _popped = true;
}
Public string GirlHappiness
{ get
{
if (!_popped)
{
if (_volume < 3)
return "......";
if (_volume < 6)
return "Ooohhh";
else
return "Ahhhh! Yay!";
}
else
return "WaAaAaAaHhHhHh";
}
}
Public string BoyHappiness
{ get
{
if (!_popped)
{
if (_volume < 3)
return "zzzzzz";
if (_volume < 6)
return "zzzzzz";
else
return "zzzzzz";
}
else
return "Ahahaha YAY!";
}
}
}
So, is there any way to achieve this? I cannot achieve the desired result via separating assemblies, and the other method I've explored, using reflection and tracing the stack, is unreliable outside of debugging. What to do?!
Two ways that pop up in my mind: Use explicit interface implementation or events.
With explicit interfaces, you hide the implementation for those who do not threat the instance 'as is'. Example:
interface IPumpable
{
void Pump();
}
interface IPoppable
{
void Pop();
}
class Balloon :IPumpable, IPoppable
{
private void IPumpable.Pump()
{
throw new NotImplementedException();
}
private void IPoppable.Pop()
{
throw new NotImplementedException();
}
}
public static void PopMethod(IPoppable poppable)
{
poppable.Pop();
}
public static void PumpMethod(IPumpable pumpable)
{
pumpable.Pump();
}
static void Main(string[] args)
{
Balloon balloon = new Balloon();
PumpMethod((IPumpable)balloon);
PopMethod((IPoppable)balloon);
}
Note that the Pump and Pop implementations can be marked private. They are only visible when you threat the balloon as IPumpable or IPoppable respectively.