如何暂停、停止和重置 AUFilePlayer?

Posted

技术标签:

【中文标题】如何暂停、停止和重置 AUFilePlayer?【英文标题】:How do you pause, stop and reset an AUFilePlayer? 【发布时间】:2012-07-18 01:54:34 【问题描述】:

我正在使用 AUFilePlayer 将一些 mp3 加载到混音器单元中处理核心音频,一切都很好,但是我无法暂停音乐或将音乐倒回开始。我尝试启动和停止 AudioGraph,但是一旦停止播放,我就无法重新启动它。我还尝试使用 AudioUnitSetProperty 将文件播放设置为 0

即沿着这些思路:

        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));

有什么建议吗?

【问题讨论】:

我也试过 AudioUnitReset,但也没有成功。 【参考方案1】:

如果其他人正在处理类似的问题,我花了几个小时在谷歌上搜索,这里是我发现的关于如何特别检索和设置播放头的内容。

从 AUFilePlayer 单元获取播放头:

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

timestamp.mSampleTime 是该文件的当前播放头。将mSampleTime 转换为浮点数或双精度数,然后除以文件的采样率以转换为秒。

为了重新启动 AUFilePlayer 的播放头,我有一个更复杂的场景,其中多个 AUFilePlayer 通过一个混音器,可以在不同的时间、多次和不同的循环次数进行调度。这是一个真实的场景,让它们在正确的时间重新启动需要一点代码。

每个 AUFilePlayer 有四种场景及其时间表:

    播放头在开头,所以可以正常调度。

    播放头已超过项目的持续时间,根本不需要安排。

    播放头在项目开始之前,所以开始时间可以上移。

    播放头正在播放一个项目,因此需要调整文件内播放的区域,并且需要单独安排剩余的循环(以便它们完整播放)。

这里有一些代码证明了这一点(一些外部结构来自我自己的代码,而不是Core Audio,但机制应该很清楚):

// 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;

【讨论】:

Smack Jack,您是否遇到过这样的问题:您保存的播放头位置偶尔会比您预期的早 0.5-1 秒?换句话说,你按下了暂停,然后当你取消暂停时,轨道在声音文件/循环中开始的时间比你第一次按下暂停按钮的时间早一点? @Beleg 我没有注意到这一点,但我怀疑如果你不先停止图表,查询一个或多个项目所花费的时间可能会把它扔掉,正如其他人所讨论的那样这里list.apple.com。然而,如果没有其他事情发生,整整一秒钟似乎很可怕。 因为我要先停止 AuGraph,所以肯定有其他事情发生。我在另一个堆栈溢出问题中提出了这个问题,并刚刚创建了一个赏金,希望有人知道答案。 我无法将 cmets 添加到该 Q(没有足够的代表),但如果您有更完整的示例代码来显示问题,我很乐意尝试重现它,看看它的不同之处从我的代码。您能否创建一个精简的示例来展示您所看到的内容? 我怎么寄给你?【参考方案2】:

我想通了。如果你们中的任何人遇到同样的问题,这里是我的解决方案:

    在启动时,我使用文件播放器音频单元数组初始化 AUGraph。我将文件播放器音频单元数组中每个轨道的播放头设置为零。

    在“暂停”时,首先我停止 AuGraph。然后我遍历文件播放器音频单元数组并捕获当前播放头位置。每次按下暂停按钮时,我都会确保将新的当前播放头位置添加到旧播放头位置以获得其真实位置。

    当用户点击播放时,我重新初始化 AuGraph,就像我第一次启动应用程序一样,只是我将播放头设置为用户点击“暂停”时存储的数字而不是告诉它在文件的开头播放。

    如果用户单击停止,我将存储的播放头位置设置为零,然后停止 AuGraph。

【讨论】:

第二点很重要。正是我所缺少的东西导致我提出这个问题。测试时也不要使用重复的器乐歌曲,很难判断你的暂停是否真的有效!

以上是关于如何暂停、停止和重置 AUFilePlayer?的主要内容,如果未能解决你的问题,请参考以下文章

播放和暂停按钮正在工作...但是如何重置按钮以在音频结束后显示播放符号?

暂停和重置游戏 Spritekit / Swift 之间的区别?

当用户尝试全屏播放时,MPMoviePlayerController 停止并重置电影 [iOS]

如何从 django 模板暂停和停止 celery 任务

如何停止或暂停 Pandora 和 Spotify

线程的停止和暂停