Re: Playback time at AVAudioEngineConfigurationChangeNotification?
Re: Playback time at AVAudioEngineConfigurationChangeNotification?
- Subject: Re: Playback time at AVAudioEngineConfigurationChangeNotification?
- From: Dave O'Neill <email@hidden>
- Date: Mon, 15 Apr 2019 09:20:43 -0700
Correct me if I'm wrong, but I believe only AUGraph is deprecated, not
AudioUnits.
AVAudioEngine is a high level abstraction, but it still gives you access to
all of the low level functionality of AudioUnits by wrapping the C API with
a object oriented interface that uses C-blocks instead of callbacks.
The AVAudioSession NSNotifications provide no extra timing information than
the AVAudioEngine notification does, but they aren't mutually exclusive so
you might try them to see if you can get a valid lastRenderTime before the
engine is stopped.
To get this playback information using AudioUnits, you could use a
RenderNotify callback to cache your last played sample position, then
compare that with your kAudioUnitSubType_AudioFilePlayer's start time to
get elapsed samples. However, you can do all of this using AVAudioEngine
and it's analogous methods.
This example example assumes a running audio engine (pre-interruption) and
a valid secondsToTicks conversion:
/*------------------------------------------------------------------------------------------------*/
AVAudioTime *lastRenderTime = playerNode.lastRenderTime;
float paddingToStartAfterBegginingOfNextRenderCycle =
AVAudioSession.sharedInstance.IOBufferDuration * 2;
uint64_t futurePlaybackStartHostTime = lastRenderTime.hostTime +
paddingToStartAfterBegginingOfNextRenderCycle * secondsToTicks;
AVAudioTime *playerStartTime = [AVAudioTime
timeWithHostTime:futurePlaybackStartHostTime];
playerStartTime = [playerStartTime
extrapolateTimeFromAnchor:lastRenderTime]; // This fills out
playerStartTime's sample time.
[playerNode playAtTime:playerStartTime];
__block int64_t lastPlayedSample = INT64_MAX;
NSInteger token = [playerNode.AUAudioUnit
tokenByAddingRenderObserver:^(AudioUnitRenderActionFlags actionFlags,
const AudioTimeStamp * _Nonnull timestamp,
AUAudioFrameCount frameCount,
NSInteger outputBusNumber) {
lastPlayedSample = round(timestamp->mSampleTime + frameCount);
}];
[NSTimer
scheduledTimerWithTimeInterval:AVAudioSession.sharedInstance.IOBufferDuration
repeats:true block:^(NSTimer *timer) {
int64_t elapsedSamples = MAX(0, round(lastPlayedSample -
playerStartTime.sampleTime));
printf("Playback sample position is %lli\n", elapsedSamples);
}];
/*------------------------------------------------------------------------------------------------*/
Good luck!
Dave O'Neill
On Mon, Apr 15, 2019 at 4:56 AM Tamás Zahola <email@hidden> wrote:
> Thanks for the insight Arshia!
>
> I guess I’ll have to fallback to using AUGraph and AudioUnits, and ignore
> the flashing deprecation warnings prompting me to use AVAudioEngine...
>
> It’s unfortunate that AVAudioEngine is being touted as a replacement for
> these APIs while still lacking support for such basic use cases...
>
> Considering that AVAudioEngine was introduced in iOS 8, it would be nice
> to have some clarification on Apple’s stance with these audio APIs, because
> I find it hard to believe that I’m the first one encountering this problem.
>
> Should we consider AVAudioEngine a “beginners” API, and are AUGraph +
> AudioUnits still the preferred way to do audio processing?
>
> Thanks,
> Tamás Zahola
>
> > On 2019. Apr 15., at 10:20, Arshia Cont <email@hidden>
> wrote:
> >
> > Tamás,
> >
> > I gave up AVAudioEngine for the same reasons a while back. I achieve
> similar results by counting on AVAudioSession RouteChange notifications
> (which I believe is what AVAudioEngine uses anyway) and low level
> AudioUnits to synchronize buffers during interruptions.
> >
> > My understanding is that AVAUdioEngine is a wrapper on top of good-old
> Avaudiosession and audiounits! My main issue with not wanting to go further
> on AVAUdioEngine was its poor performance on real-time tap nodes while
> classical AUnits are controllable to the slightest detail.
> >
> > If anyone can shed light on these observations it would be great.
> >
> > Arshia Cont
> > www.antescofo.com
> >
> > Sent from my iPhone
> >
> >> On 15 Apr 2019, at 00:10, Tamás Zahola <email@hidden> wrote:
> >>
> >> Hi,
> >>
> >> I’m writing an iOS audio player utilizing AVAudioEngine with an
> AVAudioPlayerNode and some effect nodes.
> >>
> >> I want the audio to continue as seamlessly as possible on an audio
> route change, in other words: when playing audio through the external
> speakers and we plug in a headphone, then the audio in the headphones
> should continue exactly where the speakers have left off.
> >>
> >> According to the docs, AVAudioEngine is stopped and connections are
> severed when such an audio route change occurs; thus the audio graph
> connections have to be re-established and playback has to be started afresh
> (buffers has to be enqueued again, erc.). When this happens a notification
> is posted (AVAudioEngineConfigurationChangeNotification).
> >>
> >> In response to this notification, I wanted to simply re-enqueue the
> previously enqueued audio buffers, possibly skipping a bunch of samples
> from the start of the buffer that was playing at the time of the
> interruption, so that those parts that made it through the speaker won’t be
> played again in the headphones.
> >>
> >> But there’s an issue here: by the time this notification is posted, the
> engine’s internal state seems to be torn down (the nodes are stopped and
> their `lastRenderTime` is nil), so I can’t figure out exactly where the
> playback was interrupted...
> >>
> >> Have I missed an API that would let me query the playback time after
> such an interruption?
> >>
> >> What is the recommended approach for handling these route/configuration
> changes seamlessly?
> >>
> >> Calculating playback time from “wall time” (i.e. mach_absolute_time)
> feels a bit icky to me when working with a high-level API like
> AVAudioEngine...
> >>
> >> Best regards,
> >> Tamás Zahola
> >> _______________________________________________
> >> Do not post admin requests to the list. They will be ignored.
> >> Coreaudio-api mailing list (email@hidden)
> >> Help/Unsubscribe/Update your Subscription:
> >>
> >>
> >> This email sent to email@hidden
> _______________________________________________
> Do not post admin requests to the list. They will be ignored.
> Coreaudio-api mailing list (email@hidden)
> Help/Unsubscribe/Update your Subscription:
>
> This email sent to email@hidden
>
_______________________________________________
Do not post admin requests to the list. They will be ignored.
Coreaudio-api mailing list (email@hidden)
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden