Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/macos/8.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C 具有核心音频的实时正弦音调生成_C_Macos_Audio_Real Time_Core Audio - Fatal编程技术网

C 具有核心音频的实时正弦音调生成

C 具有核心音频的实时正弦音调生成,c,macos,audio,real-time,core-audio,C,Macos,Audio,Real Time,Core Audio,我想创建一个使用苹果核心音频框架的实时正弦发生器。我想做它的低水平,这样我就可以学习和理解的基本原则 我知道使用PortAudio或Jack可能会更容易,我会在某个时候使用它们,但我想先让它们起作用,这样我才能有信心理解基本原理 我在这个主题上搜索了好几天,但似乎没有人使用核心音频创建过实时波形发生器,试图在使用C而不是Swift或Objective-C时保持低延迟 为此,我使用了一个我不久前建立的项目。它最初被设计成一个游戏。因此,在应用程序启动后,它将进入一个运行循环。我认为这非常适合,因为

我想创建一个使用苹果核心音频框架的实时正弦发生器。我想做它的低水平,这样我就可以学习和理解的基本原则

我知道使用PortAudio或Jack可能会更容易,我会在某个时候使用它们,但我想先让它们起作用,这样我才能有信心理解基本原理

我在这个主题上搜索了好几天,但似乎没有人使用核心音频创建过实时波形发生器,试图在使用C而不是Swift或Objective-C时保持低延迟

为此,我使用了一个我不久前建立的项目。它最初被设计成一个游戏。因此,在应用程序启动后,它将进入一个运行循环。我认为这非常适合,因为我可以使用主循环将样本复制到音频缓冲区,并处理渲染和输入处理

到目前为止,我得到的声音。有时它会工作一段时间,然后开始出现故障,有时它会立即出现故障

这是我的密码。我试图简化,如果只介绍重要的部分

我有很多问题。它们位于这篇文章的底部

应用程序主循环运行。这是在创建窗口并初始化缓冲区和内存后开始的:

    while (OSXIsGameRunning())
    {
       OSXProcessPendingMessages(&GameData);            

       [GlobalGLContext makeCurrentContext];

       CGRect WindowFrame = [window frame];
       CGRect ContentViewFrame = [[window contentView] frame];

       CGPoint MouseLocationInScreen = [NSEvent mouseLocation];
       BOOL MouseInWindowFlag = NSPointInRect(MouseLocationInScreen, WindowFrame);
       CGPoint MouseLocationInView = {};

       if (MouseInWindowFlag)
       {
          NSRect RectInWindow = [window convertRectFromScreen:NSMakeRect(MouseLocationInScreen.x,                                                                        MouseLocationInScreen.y,                                                                 1,                                                                         1)];
          NSPoint PointInWindow = RectInWindow.origin;
          MouseLocationInView= [[window contentView] convertPoint:PointInWindow fromView:nil];
       }
       u32 MouseButtonMask = [NSEvent pressedMouseButtons];

       OSXProcessFrameAndRunGameLogic(&GameData, ContentViewFrame,
                                           MouseInWindowFlag, MouseLocationInView,
                                           MouseButtonMask);

#if ENGINE_USE_VSYNC
       [GlobalGLContext flushBuffer];
#else        
       glFlush();
#endif

     }
通过使用VSYNC,我可以将循环速度降低到60 FPS。时机不是太紧,但相当稳定。我也有一些代码,以节流手动使用马赫计时,这是更不精确的。为了便于阅读,我把它漏掉了。 不使用VSYNC或使用马赫计时每秒进行60次迭代也会造成音频故障

计时日志:

CyclesElapsed: 8154360866, TimeElapsed: 0.016624, FPS: 60.155666
CyclesElapsed: 8174382119, TimeElapsed: 0.020021, FPS: 49.946926
CyclesElapsed: 8189041370, TimeElapsed: 0.014659, FPS: 68.216309
CyclesElapsed: 8204363633, TimeElapsed: 0.015322, FPS: 65.264511
CyclesElapsed: 8221230959, TimeElapsed: 0.016867, FPS: 59.286217
CyclesElapsed: 8237971921, TimeElapsed: 0.016741, FPS: 59.733719
CyclesElapsed: 8254861722, TimeElapsed: 0.016890, FPS: 59.207333
CyclesElapsed: 8271667520, TimeElapsed: 0.016806, FPS: 59.503273
CyclesElapsed: 8292434135, TimeElapsed: 0.020767, FPS: 48.154209
这里重要的是函数
OSXProcessFrameAndRunGameLogic
。每秒调用它60次,并向它传递一个包含基本信息的结构,如渲染缓冲区、键盘状态和声音缓冲区,如下所示:

    typedef struct osx_sound_output
    {
       game_sound_output_buffer SoundBuffer;
       u32 SoundBufferSize;
       s16* CoreAudioBuffer;
       s16* ReadCursor;
       s16* WriteCursor;

       AudioStreamBasicDescription AudioDescriptor;
       AudioUnit AudioUnit;  
    } osx_sound_output;
void OSXInitCoreAudio(osx_sound_output* SoundOutput)
{
    AudioComponentDescription acd;
    acd.componentType         = kAudioUnitType_Output;
    acd.componentSubType      = kAudioUnitSubType_DefaultOutput;
    acd.componentManufacturer = kAudioUnitManufacturer_Apple;

    AudioComponent outputComponent = AudioComponentFindNext(NULL, &acd);

    AudioComponentInstanceNew(outputComponent, &SoundOutput->AudioUnit);
    AudioUnitInitialize(SoundOutput->AudioUnit);

    // uint16
    //AudioStreamBasicDescription asbd;
    SoundOutput->AudioDescriptor.mSampleRate       = SoundOutput->SoundBuffer.SamplesPerSecond;
    SoundOutput->AudioDescriptor.mFormatID         = kAudioFormatLinearPCM;
    SoundOutput->AudioDescriptor.mFormatFlags      = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsNonInterleaved | kAudioFormatFlagIsPacked;
    SoundOutput->AudioDescriptor.mFramesPerPacket  = 1;
    SoundOutput->AudioDescriptor.mChannelsPerFrame = 2; // Stereo
    SoundOutput->AudioDescriptor.mBitsPerChannel   = sizeof(int16) * 8;
    SoundOutput->AudioDescriptor.mBytesPerFrame    = sizeof(int16); // don't multiply by channel count with non-interleaved!
    SoundOutput->AudioDescriptor.mBytesPerPacket   = SoundOutput->AudioDescriptor.mFramesPerPacket * SoundOutput->AudioDescriptor.mBytesPerFrame;



    AudioUnitSetProperty(SoundOutput->AudioUnit,
                         kAudioUnitProperty_StreamFormat,
                         kAudioUnitScope_Input,
                         0,
                         &SoundOutput->AudioDescriptor,
                         sizeof(SoundOutput->AudioDescriptor));

    AURenderCallbackStruct cb;
    cb.inputProc = OSXAudioUnitCallback;
    cb.inputProcRefCon = SoundOutput;

    AudioUnitSetProperty(SoundOutput->AudioUnit,
                         kAudioUnitProperty_SetRenderCallback,
                         kAudioUnitScope_Global,
                         0,
                         &cb,
                         sizeof(cb));

    AudioOutputUnitStart(SoundOutput->AudioUnit);
}
其中
game\u sound\u output\u buffer
为:

    typedef struct game_sound_output_buffer
    {
       real32 tSine;
       int SamplesPerSecond;
       int SampleCount;
       int16 *Samples;
    } game_sound_output_buffer;
这些在应用程序进入其运行循环之前设置。 声音缓冲区本身的大小是
SamplesPerSecond*sizeof(uint16)*2
其中
SamplesPerSecond=48000

因此,OSXProcessFrameAndRunGameLogic的内部是声音生成:

void OSXProcessFrameAndRunGameLogic(osx_game_data *GameData, CGRect WindowFrame,
                                    b32 MouseInWindowFlag, CGPoint MouseLocation,
                                    int MouseButtonMask)
{
    GameData->SoundOutput.SoundBuffer.SampleCount = GameData->SoundOutput.SoundBuffer.SamplesPerSecond / GameData->TargetFramesPerSecond;

    // Oszi 1

    OutputTestSineWave(GameData, &GameData->SoundOutput.SoundBuffer, GameData->SynthesizerState.ToneHz);

    int16* CurrentSample = GameData->SoundOutput.SoundBuffer.Samples;
    for (int i = 0; i < GameData->SoundOutput.SoundBuffer.SampleCount; ++i)
    {
        *GameData->SoundOutput.WriteCursor++ = *CurrentSample++;
        *GameData->SoundOutput.WriteCursor++ = *CurrentSample++;

        if ((char*)GameData->SoundOutput.WriteCursor >= ((char*)GameData->SoundOutput.CoreAudioBuffer + GameData->SoundOutput.SoundBufferSize))
        {
            //printf("Write cursor wrapped!\n");
            GameData->SoundOutput.WriteCursor  = GameData->SoundOutput.CoreAudioBuffer;
        }
    }
}
因此,当在启动时创建缓冲区时,也会初始化核心音频,我这样做:

    typedef struct osx_sound_output
    {
       game_sound_output_buffer SoundBuffer;
       u32 SoundBufferSize;
       s16* CoreAudioBuffer;
       s16* ReadCursor;
       s16* WriteCursor;

       AudioStreamBasicDescription AudioDescriptor;
       AudioUnit AudioUnit;  
    } osx_sound_output;
void OSXInitCoreAudio(osx_sound_output* SoundOutput)
{
    AudioComponentDescription acd;
    acd.componentType         = kAudioUnitType_Output;
    acd.componentSubType      = kAudioUnitSubType_DefaultOutput;
    acd.componentManufacturer = kAudioUnitManufacturer_Apple;

    AudioComponent outputComponent = AudioComponentFindNext(NULL, &acd);

    AudioComponentInstanceNew(outputComponent, &SoundOutput->AudioUnit);
    AudioUnitInitialize(SoundOutput->AudioUnit);

    // uint16
    //AudioStreamBasicDescription asbd;
    SoundOutput->AudioDescriptor.mSampleRate       = SoundOutput->SoundBuffer.SamplesPerSecond;
    SoundOutput->AudioDescriptor.mFormatID         = kAudioFormatLinearPCM;
    SoundOutput->AudioDescriptor.mFormatFlags      = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsNonInterleaved | kAudioFormatFlagIsPacked;
    SoundOutput->AudioDescriptor.mFramesPerPacket  = 1;
    SoundOutput->AudioDescriptor.mChannelsPerFrame = 2; // Stereo
    SoundOutput->AudioDescriptor.mBitsPerChannel   = sizeof(int16) * 8;
    SoundOutput->AudioDescriptor.mBytesPerFrame    = sizeof(int16); // don't multiply by channel count with non-interleaved!
    SoundOutput->AudioDescriptor.mBytesPerPacket   = SoundOutput->AudioDescriptor.mFramesPerPacket * SoundOutput->AudioDescriptor.mBytesPerFrame;



    AudioUnitSetProperty(SoundOutput->AudioUnit,
                         kAudioUnitProperty_StreamFormat,
                         kAudioUnitScope_Input,
                         0,
                         &SoundOutput->AudioDescriptor,
                         sizeof(SoundOutput->AudioDescriptor));

    AURenderCallbackStruct cb;
    cb.inputProc = OSXAudioUnitCallback;
    cb.inputProcRefCon = SoundOutput;

    AudioUnitSetProperty(SoundOutput->AudioUnit,
                         kAudioUnitProperty_SetRenderCallback,
                         kAudioUnitScope_Global,
                         0,
                         &cb,
                         sizeof(cb));

    AudioOutputUnitStart(SoundOutput->AudioUnit);
}
核心音频的初始化代码将渲染回调设置为
OSXAudioUnitCallback

OSStatus OSXAudioUnitCallback(void * inRefCon,
                              AudioUnitRenderActionFlags * ioActionFlags,
                              const AudioTimeStamp * inTimeStamp,
                              UInt32 inBusNumber,
                              UInt32 inNumberFrames,
                              AudioBufferList * ioData)
{
#pragma unused(ioActionFlags)
#pragma unused(inTimeStamp)
#pragma unused(inBusNumber)

    //double currentPhase = *((double*)inRefCon);

    osx_sound_output* SoundOutput = ((osx_sound_output*)inRefCon);


    if (SoundOutput->ReadCursor == SoundOutput->WriteCursor)
    {
        SoundOutput->SoundBuffer.SampleCount = 0;
        //printf("AudioCallback: No Samples Yet!\n");
    }

    //printf("AudioCallback: SampleCount = %d\n", SoundOutput->SoundBuffer.SampleCount);

    int SampleCount = inNumberFrames;
    if (SoundOutput->SoundBuffer.SampleCount < inNumberFrames)
    {
        SampleCount = SoundOutput->SoundBuffer.SampleCount;
    }

    int16* outputBufferL = (int16 *)ioData->mBuffers[0].mData;
    int16* outputBufferR = (int16 *)ioData->mBuffers[1].mData;

    for (UInt32 i = 0; i < SampleCount; ++i)
    {
        outputBufferL[i] = *SoundOutput->ReadCursor++;
        outputBufferR[i] = *SoundOutput->ReadCursor++;

        if ((char*)SoundOutput->ReadCursor >= (char*)((char*)SoundOutput->CoreAudioBuffer + SoundOutput->SoundBufferSize))
        {
            //printf("Callback: Read cursor wrapped!\n");
            SoundOutput->ReadCursor = SoundOutput->CoreAudioBuffer;
        }
    }

    for (UInt32 i = SampleCount; i < inNumberFrames; ++i)
    {
        outputBufferL[i] = 0.0;
        outputBufferR[i] = 0.0;
    }

    return noErr;
}
然而,我得到的结果好坏参半,甚至不确定多线程是否是这样完成的。每秒创建和销毁线程60次似乎不是一个好办法

我还想在应用程序实际运行到主循环之前,让声音处理在完全不同的线程上进行。类似于两个同时运行的while循环,前者处理音频,后者处理用户界面和输入

问题:

  • 我听到了刺耳的声音。渲染和输入似乎工作正常,但音频有时会出现小故障,有时则不会。根据我提供的代码,你能看到我做错了什么吗
  • 为了实现实时低延迟信号生成,我是否以错误的方式使用了核心音频技术
  • 我应该像上面提到的那样在单独的线程中进行声音处理吗?在这种情况下如何正确地执行线程?有一个专门用于声音的线程是有意义的,对吗
  • 我说的基本音频处理不应该在核心音频的渲染回调中完成,对吗?此功能是否仅用于输出提供的声音缓冲区? 如果声音处理应该在这里完成,我如何从回调中访问诸如键盘状态之类的信息
  • 你能给我指出哪些我可能错过的资源吗
  • 这是我知道的唯一一个可以在这个项目中得到帮助的地方。我非常感谢你的帮助

    如果你不清楚,请告诉我


    谢谢:)

    通常,在处理低延迟音频时,您希望实现尽可能确定的行为

    例如,这可以转化为:

    • 不要在音频线程上持有任何锁(优先级反转)
    • 音频线程上没有内存分配(通常需要太多时间)
    • 音频线程上没有文件/网络IO(通常花费太多时间)
    问题1

    当您想要获得连续、实时、无故障的音频时,您的代码确实存在一些问题

    1。两个不同的时钟域。
    您提供的音频数据来自(我称之为)与请求数据的时钟域不同的时钟域。在这种情况下,时钟域1由
    TargetFramesPerSecond
    值定义,时钟域2由Core Audio定义。然而,由于调度的工作方式,您不能保证线程能够按时完成。您尝试将渲染目标设定为每秒n帧,但如果不按时间进行调整,会发生什么情况?就我所知,渲染周期与理想时间相比所产生的偏差是无法补偿的。 线程的工作方式是,最终由操作系统调度程序决定线程何时处于活动状态。没有任何保证,这会导致渲染周期不太精确(就音频渲染所需的精度而言)

    2。渲染线程和核心音频rendercallback线程之间没有同步。
    运行
    OSXAudioUnitCallback
    的线程与运行
    OSXProcessFrameAndRunGameLogic
    的线程不同,因此运行
    输出正弦波。您正在从主线程提供数据,并且正在从核心音频渲染线程读取数据。通常,您会使用一些互斥锁来保护数据,但在本例中,情况并非如此