[英]Metronome. Timer, music and animations
我開發了一個應用程序,其中用戶只有幾個單元,可以在其中放置聲音,然后播放構建的音序。 有一個節拍器,它可以隨着聲音滴答作響。 用戶可以設置節拍器速度,與設置傳遞到下一個單元格的速度相同。 我已經通過帶有處理程序的“計時器”實現了這種機制,該機制突出顯示當前單元並播放聲音。 一切正常。 但是,當我制作一些視圖動畫時,我的計時器絆倒了。 動畫制作完成后,計時器將按預期工作。 我該如何解決這個問題?
我試圖通過NSTimer
, dispatch_after
, performSelector:afterDelay:
, CADisplayLink
和dispatch_source_t
實現計時器。 無論如何,我在動畫過程中都會遇到問題。 我什至嘗試通過CADisplayLink
實現自己的動畫,在其中計算動畫視圖幀,這也無濟於事。
我發現這樣做的唯一100%可靠的方法是通過CoreAudio或AudioToolbox進行設置: https : //developer.apple.com/documentation/audiotoolbox音頻流數據提供者,iOS會定期以固定間隔調用該提供者,以提供音頻系統的音頻樣本。
乍一看可能令人望而生畏,但是一旦完成設置,就可以對音頻生成的內容進行完全而精確的控制。
這是我使用AudioToolbox設置AudioUnit的代碼:
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/來編輯文件並將其保存到RAW PCM mono / stereo數據文件中,也可以使用AVFoundation庫之一來檢索來自任何受支持的音頻文件格式的音頻樣本。 將樣本加載到緩沖區中,跟蹤音頻回調幀之間的中斷位置,並輸入與0交錯的節拍器樣本。
這樣做的好處是,您現在可以依靠iOS的AudioToolbox設置代碼的優先級,以使音頻和視圖動畫都不會相互干擾。
干杯,祝你好運!
我找到了一個解決方案,可以與Apple AVAudioEngine
示例HelloMetronome一起玩。 我了解了主要思想。 我必須安排聲音並處理UI中的回調。 使用任何計時器開始播放聲音和更新UI絕對是錯誤的。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.