音频样本生成multithreadingOSX

这个问题是前一个问题的后续问题( 音频制作者与OSX AudioComponent消费者线程和C中的回调线程 ),包括一个测试示例,它的工作和行为符合预期,但并不能完全回答这个问题。 我已经基本上改写了这个问题,并重新编写了示例,因此它只包含普通的C代码。 (我发现前一个例子中几乎没有Objective-C部分的代码只会引起混淆,并使读者分心于问题中必不可少的内容。)

为了利用多个处理器内核以及使CoreAudio拉模型渲染线程尽可能轻量化,LPCM样本的生产者例程显然必须“坐在”不同的线程上,超出实际优先级渲染线程/回调。 它必须将样本提供给循环缓冲区(在此示例中为TPCircularBuffer ),系统将从inNumberFrames的常量中调度数据拉出。

Grand Central Dispatch API提供了一个简单的解决方案,我在一些个人研究(包括试错编码)中推断出了这个解决方案。 这个解决方案很优雅,因为它不会阻止推送和拉动模型之间的任何冲突。 然而,应该处理“子线程”的GCD 到目前为止还没有满足生产者代码的工作线程的特定并行化要求,所以我必须明确地产生一些POSIX线程,具体取决于数量逻辑核心可用。 尽管在加速计算方面结果已经非常显着,但我仍然觉得混合POSIX和GCD有点不合适。 特别是它适用于变量wait_interval ,并且正确地计算它,而不是通过预测渲染线程在下一个循环中需要多少PCM样本。

这是我的测试程序的缩写和简化(伪)代码,在plain-C中。

控制器声明:

#include "TPCircularBuffer.h" #include  #include  #include  #include  #include  typedef struct { TPCircularBuffer buffer; AudioComponentInstance toneUnit; Float64 sampleRate; AudioStreamBasicDescription streamFormat; Float32* f; //array of updated frequencies Float32* a; //array of updated amps Float32* prevf; //array of prev. frequencies Float32* preva; //array of prev. amps Float32* val; int* arg; int* previous_arg; UInt32 frames; int state; Boolean midif; //wip } MyAudioController; MyAudioController gen; dispatch_semaphore_t mSemaphore; Boolean multithreading, NF; typedef struct data{ int tid; int cpuCount; }data; 

控制器管理:

 void setup (void){ // Initialize circular buffer TPCircularBufferInit(&(self->buffer), kBufferLength); // Create the semaphore mSemaphore = dispatch_semaphore_create(0); // Setup audio createToneUnit(&gen); } void dealloc (void) { // Release buffer resources TPCircularBufferCleanup(&buffer); // Clean up semaphore dispatch_release(mSemaphore); // dispose of audio if(gen.toneUnit){ AudioOutputUnitStop(gen.toneUnit); AudioUnitUninitialize(gen.toneUnit); AudioComponentInstanceDispose(gen.toneUnit); } } 

Dispatcher调用(从主线程启动生产者队列):

 void dproducer (Boolean on, Boolean multithreading, Boolean NF) { if (on == true) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ if((multithreading)||(NF)) producerSum(on); else producer(on); }); } return; } 

可线程生成器例程:

 void producerSum(Boolean on) { int rc; int num = getCPUnum(); pthread_t threads[num]; data thread_args[num]; void* resulT; static Float32 frames [FR_MAX]; Float32 wait_interval; int bytesToCopy; Float32 floatmax; while(on){ wait_interval = FACT*(gen.frames)/(gen.sampleRate); Float32 damp = 1./(Float32)(gen.frames); bytesToCopy = gen.frames*sizeof(Float32); memset(frames, 0, FR_MAX*sizeof(Float32)); availableBytes = 0; fbuffW = (Float32**)calloc(num + 1, sizeof(Float32*)); for (int i=0; i<num; ++i) { fbuffW[i] = (Float32*)calloc(gen.frames, sizeof(Float32)); thread_args[i].tid = i; thread_args[i].cpuCount = num; rc = pthread_create(&threads[i], NULL, producerTN, (void *) &thread_args[i]); } for (int i=0; i<num; ++i) rc = pthread_join(threads[i], &resulT); for(UInt32 samp = 0; samp < gen.frames; samp++) for(int i = 0; i < num; i++) frames[samp] += fbuffW[i][samp]; //code for managing producer state and GUI updates { ... } float *head = TPCircularBufferHead(&(gen.buffer), &availableBytes); memcpy(head,(const void*)frames,MIN(bytesToCopy, availableBytes));//copies frames to head TPCircularBufferProduce(&(gen.buffer),MIN(bytesToCopy,availableBytes)); dispatch_semaphore_wait(mSemaphore, dispatch_time(DISPATCH_TIME_NOW, wait_interval * NSEC_PER_SEC)); if(gen.state == stopped){gen.state = idle; on = false;} for(int i = 0; i <= num; i++) free(fbuffW[i]); free(fbuffW); } return; } 

单个生产者线程可能看起来像这样:

 void *producerT (void *TN) { Float32 samples[FR_MAX]; data threadData; threadData = *((data *)TN); int tid = threadData.tid; int step = threadData.cpuCount; int *ret = calloc(1,sizeof(int)); do_something(tid, step, &samples); { … } return (void*)ret; } 

这是渲染回调(CoreAudio实时消费者线程):

 static OSStatus audioRenderCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) { MyAudioController *THIS = (MyAudioController *)inRefCon; // An event happens in the render thread- signal whoever is waiting if (THIS->state == active) dispatch_semaphore_signal(mSemaphore); // Mono audio rendering: we only need one target buffer const int channel = 0; Float32* targetBuffer = (Float32 *)ioData->mBuffers[channel].mData; memset(targetBuffer,0,inNumberFrames*sizeof(Float32)); // Pull samples from circular buffer int32_t availableBytes; Float32 *buffer = TPCircularBufferTail(&THIS->buffer, &availableBytes); //copy circularBuffer content to target buffer int bytesToCopy = ioData->mBuffers[channel].mDataByteSize; memcpy(targetBuffer, buffer, MIN(bytesToCopy, availableBytes)); { … }; TPCircularBufferConsume(&THIS->buffer, availableBytes); THIS->frames = inNumberFrames; return noErr; } 

Grand Central Dispatch已经负责向多个处理器核心和线程的调度操作。 在典型的实时音频渲染或处理中,人们永远不需要等待信号或信号量,因为循环缓冲器消耗率是非常可预测的,并且随着时间的推移漂移非常缓慢。 AVAudioSession API(如果可用)和Audio Unit API和回调允许您设置和确定回调缓冲区大小,从而设置和确定循环缓冲区可以更改的最大速率。 因此,您可以在计时器上调度所有渲染操作,渲染每个计时器周期所需的确切数量,并让缓冲区大小和状态补偿线程调度时间中的任何抖动。

在极长时间运行的音频渲染中,您可能希望测量定时器操作和实时音频消耗(采样率)之间的偏差,并调整渲染的样本数或定时器偏移量。