代码之家  ›  专栏  ›  技术社区  ›  Valentin Shamardin

节拍器。计时器、音乐和动画

  •  1
  • Valentin Shamardin  · 技术社区  · 6 年前

    我开发了一个应用程序,在这个应用程序中,用户只有几个细胞,可以在其中放置声音,然后播放构建的序列。有一个节拍器,它能随着声音节拍。用户可以设置节拍器速度,这与设置传递到下一个单元格的速度相同。 我通过带有处理程序的“计时器”实现了这个机制,它突出显示当前单元格并播放声音。一切正常。但是当我设置一些视图的动画时,我的计时器会出错。动画完成后,计时器按预期工作。 如何解决此问题?

    我试过通过 NSTimer , dispatch_after , performSelector:afterDelay: , CADisplayLink dispatch_source_t . 在任何情况下,我都会在动画中遇到问题。我甚至试图通过 显示链接 ,在我计算动画视图帧的地方,这也没有帮助。

    2 回复  |  直到 6 年前
        1
  •  0
  •   ekscrypto    6 年前

    我找到的唯一100%可靠的方法是通过CoreAudio或AudioToolbox进行设置: https://developer.apple.com/documentation/audiotoolbox 一种音频流数据提供程序,由iOS以固定的时间间隔调用,以向音频系统提供音频样本。

    一开始它可能看起来令人望而生畏,但一旦你设置好了它,你就可以完全精确地控制为音频生成的内容。

    这是我使用音频工具箱设置音频单元时使用的代码:

    static AudioComponentInstance _audioUnit;
    static int _outputAudioBus;
    

    #pragma mark - Audio Unit
    
    +(void)_activateAudioUnit
    {
        [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:nil];
        if([self _createAudioUnitInstance]
           && [self _setupAudioUnitOutput]
           && [self _setupAudioUnitFormat]
           && [self _setupAudioUnitRenderCallback]
           && [self _initializeAudioUnit]
           && [self _startAudioUnit]
           )
        {
            [self _adjustOutputLatency];
    //        NSLog(@"Audio unit initialized");
        }
    }
    
    +(BOOL)_createAudioUnitInstance
    {
        // Describe audio component
        AudioComponentDescription desc;
        desc.componentType = kAudioUnitType_Output;
        desc.componentSubType = kAudioUnitSubType_RemoteIO;
        desc.componentFlags = 0;
        desc.componentFlagsMask = 0;
        desc.componentManufacturer = kAudioUnitManufacturer_Apple;
        AudioComponent inputComponent = AudioComponentFindNext(NULL, &desc);
    
        // Get audio units
        OSStatus status = AudioComponentInstanceNew(inputComponent, &_audioUnit);
        [self _logStatus:status step:@"instantiate"];
        return (status == noErr );
    }
    
    +(BOOL)_setupAudioUnitOutput
    {
        UInt32 flag = 1;
        OSStatus status = AudioUnitSetProperty(_audioUnit,
                                      kAudioOutputUnitProperty_EnableIO,
                                      kAudioUnitScope_Output,
                                      _outputAudioBus,
                                      &flag,
                                      sizeof(flag));
        [self _logStatus:status step:@"set output bus"];
        return (status == noErr );
    }
    
    +(BOOL)_setupAudioUnitFormat
    {
        AudioStreamBasicDescription audioFormat = {0};
        audioFormat.mSampleRate         = 44100.00;
        audioFormat.mFormatID           = kAudioFormatLinearPCM;
        audioFormat.mFormatFlags        = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
        audioFormat.mFramesPerPacket    = 1;
        audioFormat.mChannelsPerFrame   = 2;
        audioFormat.mBitsPerChannel     = 16;
        audioFormat.mBytesPerPacket     = 4;
        audioFormat.mBytesPerFrame      = 4;
    
        OSStatus status = AudioUnitSetProperty(_audioUnit,
                                               kAudioUnitProperty_StreamFormat,
                                               kAudioUnitScope_Input,
                                               _outputAudioBus,
                                               &audioFormat,
                                               sizeof(audioFormat));
        [self _logStatus:status step:@"set audio format"];
        return (status == noErr );
    }
    
    +(BOOL)_setupAudioUnitRenderCallback
    {
        AURenderCallbackStruct audioCallback;
        audioCallback.inputProc = playbackCallback;
        audioCallback.inputProcRefCon = (__bridge void *)(self);
        OSStatus status = AudioUnitSetProperty(_audioUnit,
                                               kAudioUnitProperty_SetRenderCallback,
                                               kAudioUnitScope_Global,
                                               _outputAudioBus,
                                               &audioCallback,
                                               sizeof(audioCallback));
        [self _logStatus:status step:@"set render callback"];
        return (status == noErr);
    }
    
    
    +(BOOL)_initializeAudioUnit
    {
        OSStatus status = AudioUnitInitialize(_audioUnit);
        [self _logStatus:status step:@"initialize"];
        return (status == noErr);
    }
    
    +(void)start
    {
        [self clearFeeds];
        [self _startAudioUnit];
    }
    
    +(void)stop
    {
        [self _stopAudioUnit];
    }
    
    +(BOOL)_startAudioUnit
    {
        OSStatus status = AudioOutputUnitStart(_audioUnit);
        [self _logStatus:status step:@"start"];
        return (status == noErr);
    }
    
    +(BOOL)_stopAudioUnit
    {
        OSStatus status = AudioOutputUnitStop(_audioUnit);
        [self _logStatus:status step:@"stop"];
        return (status == noErr);
    }
    
    +(void)_logStatus:(OSStatus)status step:(NSString *)step
    {
        if( status != noErr )
        {
            NSLog(@"AudioUnit failed to %@, error: %d", step, (int)status);
        }
    }
    

    最后,一旦启动,我注册的音频回调将是提供音频的回调:

    static OSStatus playbackCallback(void *inRefCon,
                                     AudioUnitRenderActionFlags *ioActionFlags,
                                     const AudioTimeStamp *inTimeStamp,
                                     UInt32 inBusNumber,
                                     UInt32 inNumberFrames,
                                     AudioBufferList *ioData) {
    
        @autoreleasepool {
            AudioBuffer *audioBuffer = ioData->mBuffers;
    
            // .. fill in audioBuffer with Metronome sample data, fill the in-between ticks with 0s
        }
        return noErr;
    }
    

    您可以使用声音编辑器,如Audacity: https://www.audacityteam.org/download/mac/ 要编辑您的文件并将其保存到原始的PCM单声道/立体声数据文件中,或者您可以使用AVFoundation库之一从任何支持的音频文件格式中检索音频样本。将您的示例加载到缓冲区中,跟踪您在音频回调帧之间的中断位置,并将节拍器示例与0交错输入。

    这样做的好处是,您现在可以依靠iOS的音频工具箱来确定代码的优先级,这样音频动画和视图动画就不会相互干扰。

    干杯,祝你好运!

        2
  •  0
  •   Valentin Shamardin    6 年前

    我找到了一个解决方案,玩苹果 AVAudioEngine example HelloMetronome . 我明白主要的想法。 我得安排声音 并处理用户界面中的回调。使用任何计时器开始播放声音和更新用户界面都是绝对错误的。