当前位置: 首页 > 知识库问答 >
问题:

用核心音频实时产生正弦音

白高逸
2023-03-14

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

    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;
    typedef struct game_sound_output_buffer
    {
       real32 tSine;
       int SamplesPerSecond;
       int SampleCount;
       int16 *Samples;
    } game_sound_output_buffer;
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;
        }
    }
}
void OutputTestSineWave(osx_game_data *GameData, game_sound_output_buffer *SoundBuffer, int ToneHz)
{
    int16 ToneVolume = 3000;
    int WavePeriod = SoundBuffer->SamplesPerSecond/ToneHz;

    int16 *SampleOut = SoundBuffer->Samples;
    for(int SampleIndex = 0;
        SampleIndex < SoundBuffer->SampleCount;
        ++SampleIndex)
    {
        real32 SineValue = sinf(SoundBuffer->tSine);
        int16 SampleValue = (int16)(SineValue * ToneVolume);

        *SampleOut++ = SampleValue;
        *SampleOut++ = SampleValue;

        SoundBuffer->tSine += Tau32*1.0f/(real32)WavePeriod;
        if(SoundBuffer->tSine > Tau32)
        {
            SoundBuffer->tSine -= Tau32;
        }
    }
}
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);
}
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;
}

这就是它的主要内容。这是相当长的,但我没有看到一个方法,以一个更紧凑的方式呈现所有需要的信息。我想展示所有,因为我绝不是一个专业的程序员。如果你觉得缺少了什么,请告诉我。

我的感觉告诉我时机有问题。我觉得函数OSXProcessFrameAndRungameLogic有时需要更多的时间,这样核心音频回调在OutputTestSinewave完全写入之前就已经从缓冲区中提取样本了。

实际上,在OSXProcessFrameAndRungameLogic中还有更多的内容,我在这里没有展示这些内容。我是“软件渲染”到一个framebuffer中的非常基本的东西,然后由OpenGL显示,我也在那里做按键检查,因为是的,这是功能的主要功能。在未来,这是一个地方,我想处理多个振荡器,滤波器和东西的控制。无论如何,即使每次迭代都停止调用渲染和输入处理,我仍然会遇到音频故障。

pthread_t soundThread;
pthread_create(&soundThread, NULL, RunSound, GameData);
pthread_join(soundThread, NULL);
    null

谢谢:)

共有1个答案

冯新知
2023-03-14

你的基本问题是,你试图推音频从你的游戏循环,而不是让音频系统拉它;例如,而不是总是具有(或能够快速创建*)足够的音频样本,以准备由音频回调请求的量由音频回调拉动。“总是”必须考虑到足够的斜率来覆盖游戏循环中的定时抖动(被调用的时间太晚或太早或太少)。

(*没有锁、信号量、内存分配或目标C消息

 类似资料:
  • 第一次海报在这里。我通常喜欢自己找到答案(无论是通过研究还是反复试验),但我在这里被难住了。 我正在努力做:我正在建立一个简单的android音频合成器。现在,我只是在实时播放正弦音调,在用户界面中有一个滑块,可以随着用户的调整而改变音调的频率。 我是如何构建它的:基本上,我有两个线程--一个工作线程和一个输出线程。工作线程只是在每次调用其tick()方法时用正弦波数据填充缓冲区。一旦缓冲区被填满

  • 用C/C++编写了一个三维正弦波发生器 代码:

  • 问题内容: 第一次海报在这里。我通常喜欢自己找到答案(通过研究或反复试验得出),但是我很困惑。 我要做什么: 我正在构建一个简单的android音频合成器。现在,我只是在实时播放正弦音,UI中的滑块会随着用户的调整而改变音的频率。 如何构建它: 基本上,我有两个线程- 工作线程和输出线程。每当调用其tick()方法时,工作线程就用正弦波数据填充一个缓冲区。一旦缓冲区被填满,它会警告输出线程数据已准

  • 我使用函数将音频文件读入。 这是音频的和ASBD: 因此,我们获得并交织了2个声道的音频,每个声道的16位符号为int init: 并读入缓冲区: 是的和实例,它在前面的代码中启动,为了节省空间,我没有粘贴到这里。 我试图完成的是在渲染回调中修改音频样本。 是否有可能从音频数据的UInt32阵列中获得Sint16左右声道样本?

  • 我一直试图在一个正弦波产生项目中添加释放(或衰减)时间,以这个例子为基础。我基本上想要的不是声音被如此剧烈地切断,当我停止生成它时,在结束时增加一些释放。 我怎样才能实现呢?

  • 问题内容: 在Java中以任何频率生成正弦波声音的最简单方法是什么?样本大小大于2个字节会有所帮助,但这并不 重要。 问题答案: 见一个自包含的例子。 也许更简单? 如链接答案顶部所示,这51行代码段(在下面重复-分隔为单行和行内注释)大约与 生成音调一样简单(好的,您可以取出5行以上用于谐波)。 人们似乎认为这应该是工具包中内置的一种产生 纯净音调的方法。并非如此,要花一点时间就可以做到。