How to force audioplayer timer callbacks to use a 'drop' BusyMode rather than 'queue'

3 vues (au cours des 30 derniers jours)
Brian Kardon
Brian Kardon le 1 Fév 2024
Commenté : Brian Kardon le 14 Fév 2024
I am attempting to get audio and video to play together in sync in a MATLAB figure. I am using the audioplayer class to play the audio, and the audioplayer TimerFcn to trigger the video/GUI update. However, sometimes the video/GUI update code takes longer than the period between video frames. Therefore, to keep up with the audio, I need the audioplayer to drop some TimerFcn calls.
The Timer class provides a "BusyMode" property which governs how to handle cases where TimerFcn calls collide. Unfortunately, as far as I can tell, the timer functionality built into the audioplayer object does not provide access to a property like that. The result is that the video/GUI updates lag the audio under some circumstances.
Here is some minimal working example code to illustrate the problem, which generates periodic audible clicks, and for each click, writes to the console.
% Set up audio parameters
Fs = 22050;
duration = 10;
% Click interval is time in seconds between audio clicks
clickInterval = 0.5;
% Set up audio data
t = linspace(0, duration, duration*Fs);
y = double(mod(t, clickInterval) < 0.01);
plot(t, y);
% Set up audio player
ap = audioplayer(y, Fs);
ap.UserData.clickCount = 0;
ap.UserData.clickInterval = clickInterval;
ap.TimerPeriod = 0.5;
ap.TimerFcn = @TimerFcn;
% Start audio player
ap.play()
function TimerFcn(src, ~)
% Issue a notification to the console that TimerFcn has been called
src.UserData.clickCount = src.UserData.clickCount + 1;
fprintf('click: %d\n', src.UserData.clickCount);
% Pause for longer than the click interval - to keep up with the audio,
% callbacks will need to be dropped.
extraDelay = 0.3;
pause(src.UserData.clickInterval + extraDelay);
end
As you can see by running the above, unless you set the extraDelay variable to something less than or equal to zero, the TimerFcn callbacks will lag the audio clicks. Ideally I'd see skipped numbers in the TimerFcn output, indicating that the timer is dropping calls to TimerFcn to keep pace with the audio like a timer in BusyMode='drop', but instead I see consecutive numbers in the TimerFcn output which are lagging the audio, indicating it is behaving like a timer in BusyMode='queue'.
TL;DR: Is there any way to alter the audioplayer timer functionality as though it were a timer in BusyMode='drop' instead of BusyMode='queue' as appears to be the current behavior?

Réponses (1)

Avadhoot
Avadhoot le 13 Fév 2024
Hi Brian,
I understand that you are trying to modify the audioplayer timer so that it acts like a timer with "BusyMode" set to 'drop'. You have rightly concluded that the "audioplayer" class does not directly let you set the "BusyMode" property for the timer. However, you can modify your "TimerFcn" such that it behaves like a timer with BusyMode='drop'. The modified code for "TimerFcn" is as follows:
function TimerFcn(src, ~)
persistent lastExecTime;
if isempty(lastExecTime)
lastExecTime = tic; % Initialize the timer
end
currentTime = toc(lastExecTime);
clickIntervalWithDelay = src.UserData.clickInterval + extraDelay;
% Check if the elapsed time since the last execution is less than the
% desired interval plus the extra delay. If so, skip the current callback.
if currentTime < clickIntervalWithDelay
return;
end
% If the function is executing, reset the last execution time.
lastExecTime = tic;
% Issue a notification to the console that TimerFcn has been called
src.UserData.clickCount = src.UserData.clickCount + 1;
fprintf('click: %d\n', src.UserData.clickCount);
% Simulate the processing delay
pause(extraDelay);
end
The notable changes made in the code are as follows:
  1. Used a persistent variable called "lastExecTime" to keep track of the time when last callback was executed.
  2. If the elapsed time is less than the sum of "clickInterval" and "extraDelay", the function returns immediately. So, the callback is dropped.
  3. If enough time has passed then the "lastExecTime" is reset.
This way you can simulate the functionality of the dropped callback. You need to keep a few things in consideration before executing this piece of code. If the "TimerFcn" is called repeatedly in rapid succession, then it may cause the "lastExecTime" to not update properly.
Also, using "pause" function within the callback can make the UI unresponsive. Consider using non-blocking methods to handle GUI updates like asynchronous operation or using a dedicated thread.
I hope it helps.
  2 commentaires
Brian Kardon
Brian Kardon le 14 Fév 2024
Thank you! This looks promising, although I do plan to call it for each frame of video, so ~30 fps, so we'll see if the persistent variable updates fast enough. I'll try it and report back.
Brian Kardon
Brian Kardon le 14 Fév 2024
Thanks again for taking the time to answer. Actually as I was starting to implement this, it made me realize there is another way which is more natural for my particular application, which is like so:
Within the TimerFcn callback, I check the audioplayer.CurrentSample to see how far along the audio is, then skip the video display to whatever frame matches that audio sample, using the audio sample rate and video frame rates to convert. This seems to work nicely.

Connectez-vous pour commenter.

Produits


Version

R2022b

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!

Translated by