Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you pause, stop and reset an AUFilePlayer?

I am working with core audio using an AUFilePlayer to load a few mp3s into a mixer unit, everything plays great however I am unable to pause the music or rewind the music back to the start. I tried Starting and stopping the AudioGraph, but once the playback is stopped, I can't get it to restart. I also tried using AudioUnitSetProperty to set the file playback to 0

i.e something along these lines:

        ScheduledAudioFileRegion rgn;
        memset (&rgn.mTimeStamp, 0, sizeof(rgn.mTimeStamp));
        rgn.mTimeStamp.mFlags = kAudioTimeStampSampleTimeValid;
        rgn.mTimeStamp.mSampleTime = 0;
        rgn.mCompletionProc = NULL;
        rgn.mCompletionProcUserData = NULL;
        rgn.mAudioFile = inputFile;
        rgn.mLoopCount = 1;
        rgn.mStartFrame = 0;

        rgn.mFramesToPlay = nPackets * inputFormat.mFramesPerPacket;

        AudioUnitSetProperty(fileUnit, kAudioUnitProperty_ScheduledFileRegion, 
                             kAudioUnitScope_Global, 0,&rgn, sizeof(rgn));

Any suggestions?

like image 593
Beleg Avatar asked Jan 16 '23 02:01

Beleg


1 Answers

In case anyone else is dealing with a similar issue, which cost me several hours searching on google, here's what I've discovered on how to specifically retrieve and set the playhead.

To get the playhead from an AUFilePlayer unit:

AudioTimeStamp timestamp;
UInt32 size = sizeof(timestamp);
err = AudioUnitGetProperty(unit, kAudioUnitProperty_CurrentPlayTime, kAudioUnitScope_Global, 0, &timestamp, &size);

The timestamp.mSampleTime is the current playhead for that file. Cast mSampleTime to a float or a double and divide by the file's sample rate to convert to seconds.

For restarting the AUFilePlayer's playhead, I had a more complex scenario where multiple AUFilePlayers pass through a mixer and can be scheduled at different times, multiple times, and with varying loop counts. This is a real-world scenario, and getting them all to restart at the correct time took a little bit of code.

There are four scenarios for each AUFilePlayer and it's schedule:

  1. The playhead is at the beginning, so can be scheduled normally.

  2. The playhead is past the item's duration, and doesn't need to be scheduled at all.

  3. The playhead is before the item has started, so the start time can be moved up.

  4. The playhead is in the middle of playing an item, so the region playing within the file needs to be adjusted, and remaining loops need to be scheduled separately (so they play in full).

Here is some code which demonstrates this (some external structures are from my own code and not Core Audio, but the mechanism should be clear):

// Add each region
for(int iItem = 0; iItem < schedule.items.count; iItem++) {
    AEFileScheduleItem *scheduleItem = [schedule.items objectAtIndex:iItem];

    // Setup the region
    ScheduledAudioFileRegion region;
    [file setRegion:&region schedule:scheduleItem];

    // Compute where we are at in it
    float playheadTime = schedule.playhead / file.sampleRate;
    float endOfItem = scheduleItem.startTime + (file.duration*(1+scheduleItem.loopCount));

    // There are four scenarios:

    // 1. The playhead is -1
    //    In this case, we're all done
    if(schedule.playhead == -1) {
    }

    // 2. The playhead is past the item start time and duration*loopCount
    //    In this case, just ignore it and move on
    else if(playheadTime > endOfItem) {
        continue;
    }

    // 3. The playhead is less than or equal to the start time
    //    In this case, simply subtract the playhead from the start time
    else if(playheadTime <= scheduleItem.startTime) {
        region.mTimeStamp.mSampleTime -= schedule.playhead;
    }

    // 4. The playhead is in the middle of the file duration*loopCount
    //    In this case, set the start time to zero, adjust the loopCount
    //    startFrame and framesToPlay
    else {

        // First remove the start time
        region.mStartFrame = 0;
        double offsetTime = playheadTime - scheduleItem.startTime;

        // Next, take out any expired loops
        int loopsExpired = floor(offsetTime/file.duration);
        int fullLoops = region.mLoopCount - loopsExpired;
        region.mLoopCount = 0;
        offsetTime -= (loopsExpired * file.duration);

        // Then, adjust this segment of a loop accordingly
        region.mStartFrame = offsetTime * file.sampleRate;
        region.mFramesToPlay = region.mFramesToPlay - region.mStartFrame;

        // Finally, schedule any remaining loops separately
        if(fullLoops > 0) {
            ScheduledAudioFileRegion loops;
            [file setRegion:&loops schedule:scheduleItem];
            loops.mStartFrame = region.mFramesToPlay;
            loops.mLoopCount = fullLoops-1;
            if(![super.errors check:AudioUnitSetProperty(unit, kAudioUnitProperty_ScheduledFileRegion, kAudioUnitScope_Global, 0, &region, sizeof(region))
                           location:@"AudioUnitSetProperty(ScheduledFileRegion)"])
                return false;
        }
    }

    // Set the region
    if(![super.errors check:AudioUnitSetProperty(unit, kAudioUnitProperty_ScheduledFileRegion, kAudioUnitScope_Global, 0, &region, sizeof(region))
                   location:@"AudioUnitSetProperty(ScheduledFileRegion)"])
        return false;
}
like image 66
Smack Jack Avatar answered Jan 30 '23 23:01

Smack Jack