cscg22-gearboy

CSCG 2022 Challenge 'Gearboy'
git clone https://git.sinitax.com/sinitax/cscg22-gearboy
Log | Files | Refs | sfeed.txt

SDL_coreaudio.c (18309B)


      1/*
      2  Simple DirectMedia Layer
      3  Copyright (C) 1997-2014 Sam Lantinga <slouken@libsdl.org>
      4
      5  This software is provided 'as-is', without any express or implied
      6  warranty.  In no event will the authors be held liable for any damages
      7  arising from the use of this software.
      8
      9  Permission is granted to anyone to use this software for any purpose,
     10  including commercial applications, and to alter it and redistribute it
     11  freely, subject to the following restrictions:
     12
     13  1. The origin of this software must not be misrepresented; you must not
     14     claim that you wrote the original software. If you use this software
     15     in a product, an acknowledgment in the product documentation would be
     16     appreciated but is not required.
     17  2. Altered source versions must be plainly marked as such, and must not be
     18     misrepresented as being the original software.
     19  3. This notice may not be removed or altered from any source distribution.
     20*/
     21#include "../../SDL_internal.h"
     22#include "SDL_audio.h"
     23#include "../SDL_audio_c.h"
     24#include "../SDL_sysaudio.h"
     25#include "SDL_coreaudio.h"
     26#include "SDL_assert.h"
     27
     28#define DEBUG_COREAUDIO 0
     29
     30static void COREAUDIO_CloseDevice(_THIS);
     31
     32#define CHECK_RESULT(msg) \
     33    if (result != noErr) { \
     34        COREAUDIO_CloseDevice(this); \
     35        SDL_SetError("CoreAudio error (%s): %d", msg, (int) result); \
     36        return 0; \
     37    }
     38
     39#if MACOSX_COREAUDIO
     40typedef void (*addDevFn)(const char *name, AudioDeviceID devId, void *data);
     41
     42static void
     43addToDevList(const char *name, AudioDeviceID devId, void *data)
     44{
     45    SDL_AddAudioDevice addfn = (SDL_AddAudioDevice) data;
     46    addfn(name);
     47}
     48
     49typedef struct
     50{
     51    const char *findname;
     52    AudioDeviceID devId;
     53    int found;
     54} FindDevIdData;
     55
     56static void
     57findDevId(const char *name, AudioDeviceID devId, void *_data)
     58{
     59    FindDevIdData *data = (FindDevIdData *) _data;
     60    if (!data->found) {
     61        if (SDL_strcmp(name, data->findname) == 0) {
     62            data->found = 1;
     63            data->devId = devId;
     64        }
     65    }
     66}
     67
     68static void
     69build_device_list(int iscapture, addDevFn addfn, void *addfndata)
     70{
     71    OSStatus result = noErr;
     72    UInt32 size = 0;
     73    AudioDeviceID *devs = NULL;
     74    UInt32 i = 0;
     75    UInt32 max = 0;
     76
     77    AudioObjectPropertyAddress addr = {
     78        kAudioHardwarePropertyDevices,
     79        kAudioObjectPropertyScopeGlobal,
     80        kAudioObjectPropertyElementMaster
     81    };
     82
     83    result = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &addr,
     84                                            0, NULL, &size);
     85    if (result != kAudioHardwareNoError)
     86        return;
     87
     88    devs = (AudioDeviceID *) alloca(size);
     89    if (devs == NULL)
     90        return;
     91
     92    result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr,
     93                                        0, NULL, &size, devs);
     94    if (result != kAudioHardwareNoError)
     95        return;
     96
     97    max = size / sizeof (AudioDeviceID);
     98    for (i = 0; i < max; i++) {
     99        CFStringRef cfstr = NULL;
    100        char *ptr = NULL;
    101        AudioDeviceID dev = devs[i];
    102        AudioBufferList *buflist = NULL;
    103        int usable = 0;
    104        CFIndex len = 0;
    105
    106        addr.mScope = iscapture ? kAudioDevicePropertyScopeInput :
    107                        kAudioDevicePropertyScopeOutput;
    108        addr.mSelector = kAudioDevicePropertyStreamConfiguration;
    109
    110        result = AudioObjectGetPropertyDataSize(dev, &addr, 0, NULL, &size);
    111        if (result != noErr)
    112            continue;
    113
    114        buflist = (AudioBufferList *) SDL_malloc(size);
    115        if (buflist == NULL)
    116            continue;
    117
    118        result = AudioObjectGetPropertyData(dev, &addr, 0, NULL,
    119                                            &size, buflist);
    120
    121        if (result == noErr) {
    122            UInt32 j;
    123            for (j = 0; j < buflist->mNumberBuffers; j++) {
    124                if (buflist->mBuffers[j].mNumberChannels > 0) {
    125                    usable = 1;
    126                    break;
    127                }
    128            }
    129        }
    130
    131        SDL_free(buflist);
    132
    133        if (!usable)
    134            continue;
    135
    136        addr.mSelector = kAudioObjectPropertyName;
    137        size = sizeof (CFStringRef);
    138        result = AudioObjectGetPropertyData(dev, &addr, 0, NULL, &size, &cfstr);
    139        if (result != kAudioHardwareNoError)
    140            continue;
    141
    142        len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(cfstr),
    143                                                kCFStringEncodingUTF8);
    144
    145        ptr = (char *) SDL_malloc(len + 1);
    146        usable = ((ptr != NULL) &&
    147                  (CFStringGetCString
    148                   (cfstr, ptr, len + 1, kCFStringEncodingUTF8)));
    149
    150        CFRelease(cfstr);
    151
    152        if (usable) {
    153            len = strlen(ptr);
    154            /* Some devices have whitespace at the end...trim it. */
    155            while ((len > 0) && (ptr[len - 1] == ' ')) {
    156                len--;
    157            }
    158            usable = (len > 0);
    159        }
    160
    161        if (usable) {
    162            ptr[len] = '\0';
    163
    164#if DEBUG_COREAUDIO
    165            printf("COREAUDIO: Found %s device #%d: '%s' (devid %d)\n",
    166                   ((iscapture) ? "capture" : "output"),
    167                   (int) *devCount, ptr, (int) dev);
    168#endif
    169            addfn(ptr, dev, addfndata);
    170        }
    171        SDL_free(ptr);  /* addfn() would have copied the string. */
    172    }
    173}
    174
    175static void
    176COREAUDIO_DetectDevices(int iscapture, SDL_AddAudioDevice addfn)
    177{
    178    build_device_list(iscapture, addToDevList, addfn);
    179}
    180
    181static int
    182find_device_by_name(_THIS, const char *devname, int iscapture)
    183{
    184    AudioDeviceID devid = 0;
    185    OSStatus result = noErr;
    186    UInt32 size = 0;
    187    UInt32 alive = 0;
    188    pid_t pid = 0;
    189
    190    AudioObjectPropertyAddress addr = {
    191        0,
    192        kAudioObjectPropertyScopeGlobal,
    193        kAudioObjectPropertyElementMaster
    194    };
    195
    196    if (devname == NULL) {
    197        size = sizeof (AudioDeviceID);
    198        addr.mSelector =
    199            ((iscapture) ? kAudioHardwarePropertyDefaultInputDevice :
    200            kAudioHardwarePropertyDefaultOutputDevice);
    201        result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr,
    202                                            0, NULL, &size, &devid);
    203        CHECK_RESULT("AudioHardwareGetProperty (default device)");
    204    } else {
    205        FindDevIdData data;
    206        SDL_zero(data);
    207        data.findname = devname;
    208        build_device_list(iscapture, findDevId, &data);
    209        if (!data.found) {
    210            SDL_SetError("CoreAudio: No such audio device.");
    211            return 0;
    212        }
    213        devid = data.devId;
    214    }
    215
    216    addr.mSelector = kAudioDevicePropertyDeviceIsAlive;
    217    addr.mScope = iscapture ? kAudioDevicePropertyScopeInput :
    218                    kAudioDevicePropertyScopeOutput;
    219
    220    size = sizeof (alive);
    221    result = AudioObjectGetPropertyData(devid, &addr, 0, NULL, &size, &alive);
    222    CHECK_RESULT
    223        ("AudioDeviceGetProperty (kAudioDevicePropertyDeviceIsAlive)");
    224
    225    if (!alive) {
    226        SDL_SetError("CoreAudio: requested device exists, but isn't alive.");
    227        return 0;
    228    }
    229
    230    addr.mSelector = kAudioDevicePropertyHogMode;
    231    size = sizeof (pid);
    232    result = AudioObjectGetPropertyData(devid, &addr, 0, NULL, &size, &pid);
    233
    234    /* some devices don't support this property, so errors are fine here. */
    235    if ((result == noErr) && (pid != -1)) {
    236        SDL_SetError("CoreAudio: requested device is being hogged.");
    237        return 0;
    238    }
    239
    240    this->hidden->deviceID = devid;
    241    return 1;
    242}
    243#endif
    244
    245/* The CoreAudio callback */
    246static OSStatus
    247outputCallback(void *inRefCon,
    248               AudioUnitRenderActionFlags * ioActionFlags,
    249               const AudioTimeStamp * inTimeStamp,
    250               UInt32 inBusNumber, UInt32 inNumberFrames,
    251               AudioBufferList * ioData)
    252{
    253    SDL_AudioDevice *this = (SDL_AudioDevice *) inRefCon;
    254    AudioBuffer *abuf;
    255    UInt32 remaining, len;
    256    void *ptr;
    257    UInt32 i;
    258
    259    /* Only do anything if audio is enabled and not paused */
    260    if (!this->enabled || this->paused) {
    261        for (i = 0; i < ioData->mNumberBuffers; i++) {
    262            abuf = &ioData->mBuffers[i];
    263            SDL_memset(abuf->mData, this->spec.silence, abuf->mDataByteSize);
    264        }
    265        return 0;
    266    }
    267
    268    /* No SDL conversion should be needed here, ever, since we accept
    269       any input format in OpenAudio, and leave the conversion to CoreAudio.
    270     */
    271    /*
    272       SDL_assert(!this->convert.needed);
    273       SDL_assert(this->spec.channels == ioData->mNumberChannels);
    274     */
    275
    276    for (i = 0; i < ioData->mNumberBuffers; i++) {
    277        abuf = &ioData->mBuffers[i];
    278        remaining = abuf->mDataByteSize;
    279        ptr = abuf->mData;
    280        while (remaining > 0) {
    281            if (this->hidden->bufferOffset >= this->hidden->bufferSize) {
    282                /* Generate the data */
    283                SDL_LockMutex(this->mixer_lock);
    284                (*this->spec.callback)(this->spec.userdata,
    285                            this->hidden->buffer, this->hidden->bufferSize);
    286                SDL_UnlockMutex(this->mixer_lock);
    287                this->hidden->bufferOffset = 0;
    288            }
    289
    290            len = this->hidden->bufferSize - this->hidden->bufferOffset;
    291            if (len > remaining)
    292                len = remaining;
    293            SDL_memcpy(ptr, (char *)this->hidden->buffer +
    294                       this->hidden->bufferOffset, len);
    295            ptr = (char *)ptr + len;
    296            remaining -= len;
    297            this->hidden->bufferOffset += len;
    298        }
    299    }
    300
    301    return 0;
    302}
    303
    304static OSStatus
    305inputCallback(void *inRefCon,
    306              AudioUnitRenderActionFlags * ioActionFlags,
    307              const AudioTimeStamp * inTimeStamp,
    308              UInt32 inBusNumber, UInt32 inNumberFrames,
    309              AudioBufferList * ioData)
    310{
    311    /* err = AudioUnitRender(afr->fAudioUnit, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, afr->fAudioBuffer); */
    312    /* !!! FIXME: write me! */
    313    return noErr;
    314}
    315
    316
    317static void
    318COREAUDIO_CloseDevice(_THIS)
    319{
    320    if (this->hidden != NULL) {
    321        if (this->hidden->audioUnitOpened) {
    322            AURenderCallbackStruct callback;
    323            const AudioUnitElement output_bus = 0;
    324            const AudioUnitElement input_bus = 1;
    325            const int iscapture = this->iscapture;
    326            const AudioUnitElement bus =
    327                ((iscapture) ? input_bus : output_bus);
    328            const AudioUnitScope scope =
    329                ((iscapture) ? kAudioUnitScope_Output :
    330                 kAudioUnitScope_Input);
    331
    332            /* stop processing the audio unit */
    333            AudioOutputUnitStop(this->hidden->audioUnit);
    334
    335            /* Remove the input callback */
    336            SDL_memset(&callback, 0, sizeof(AURenderCallbackStruct));
    337            AudioUnitSetProperty(this->hidden->audioUnit,
    338                                 kAudioUnitProperty_SetRenderCallback,
    339                                 scope, bus, &callback, sizeof(callback));
    340
    341            #if MACOSX_COREAUDIO
    342            CloseComponent(this->hidden->audioUnit);
    343            #else
    344            AudioComponentInstanceDispose(this->hidden->audioUnit);
    345            #endif
    346
    347            this->hidden->audioUnitOpened = 0;
    348        }
    349        SDL_free(this->hidden->buffer);
    350        SDL_free(this->hidden);
    351        this->hidden = NULL;
    352    }
    353}
    354
    355
    356static int
    357prepare_audiounit(_THIS, const char *devname, int iscapture,
    358                  const AudioStreamBasicDescription * strdesc)
    359{
    360    OSStatus result = noErr;
    361    AURenderCallbackStruct callback;
    362#if MACOSX_COREAUDIO
    363    ComponentDescription desc;
    364    Component comp = NULL;
    365#else
    366    AudioComponentDescription desc;
    367    AudioComponent comp = NULL;
    368#endif
    369    const AudioUnitElement output_bus = 0;
    370    const AudioUnitElement input_bus = 1;
    371    const AudioUnitElement bus = ((iscapture) ? input_bus : output_bus);
    372    const AudioUnitScope scope = ((iscapture) ? kAudioUnitScope_Output :
    373                                  kAudioUnitScope_Input);
    374
    375#if MACOSX_COREAUDIO
    376    if (!find_device_by_name(this, devname, iscapture)) {
    377        SDL_SetError("Couldn't find requested CoreAudio device");
    378        return 0;
    379    }
    380#endif
    381
    382    SDL_zero(desc);
    383    desc.componentType = kAudioUnitType_Output;
    384    desc.componentManufacturer = kAudioUnitManufacturer_Apple;
    385
    386#if MACOSX_COREAUDIO
    387    desc.componentSubType = kAudioUnitSubType_DefaultOutput;
    388    comp = FindNextComponent(NULL, &desc);
    389#else
    390    desc.componentSubType = kAudioUnitSubType_RemoteIO;
    391    comp = AudioComponentFindNext(NULL, &desc);
    392#endif
    393
    394    if (comp == NULL) {
    395        SDL_SetError("Couldn't find requested CoreAudio component");
    396        return 0;
    397    }
    398
    399    /* Open & initialize the audio unit */
    400#if MACOSX_COREAUDIO
    401    result = OpenAComponent(comp, &this->hidden->audioUnit);
    402    CHECK_RESULT("OpenAComponent");
    403#else
    404    /*
    405       AudioComponentInstanceNew only available on iPhone OS 2.0 and Mac OS X 10.6
    406       We can't use OpenAComponent on iPhone because it is not present
    407     */
    408    result = AudioComponentInstanceNew(comp, &this->hidden->audioUnit);
    409    CHECK_RESULT("AudioComponentInstanceNew");
    410#endif
    411
    412    this->hidden->audioUnitOpened = 1;
    413
    414#if MACOSX_COREAUDIO
    415    result = AudioUnitSetProperty(this->hidden->audioUnit,
    416                                  kAudioOutputUnitProperty_CurrentDevice,
    417                                  kAudioUnitScope_Global, 0,
    418                                  &this->hidden->deviceID,
    419                                  sizeof(AudioDeviceID));
    420    CHECK_RESULT
    421        ("AudioUnitSetProperty (kAudioOutputUnitProperty_CurrentDevice)");
    422#endif
    423
    424    /* Set the data format of the audio unit. */
    425    result = AudioUnitSetProperty(this->hidden->audioUnit,
    426                                  kAudioUnitProperty_StreamFormat,
    427                                  scope, bus, strdesc, sizeof(*strdesc));
    428    CHECK_RESULT("AudioUnitSetProperty (kAudioUnitProperty_StreamFormat)");
    429
    430    /* Set the audio callback */
    431    SDL_memset(&callback, 0, sizeof(AURenderCallbackStruct));
    432    callback.inputProc = ((iscapture) ? inputCallback : outputCallback);
    433    callback.inputProcRefCon = this;
    434    result = AudioUnitSetProperty(this->hidden->audioUnit,
    435                                  kAudioUnitProperty_SetRenderCallback,
    436                                  scope, bus, &callback, sizeof(callback));
    437    CHECK_RESULT
    438        ("AudioUnitSetProperty (kAudioUnitProperty_SetRenderCallback)");
    439
    440    /* Calculate the final parameters for this audio specification */
    441    SDL_CalculateAudioSpec(&this->spec);
    442
    443    /* Allocate a sample buffer */
    444    this->hidden->bufferOffset = this->hidden->bufferSize = this->spec.size;
    445    this->hidden->buffer = SDL_malloc(this->hidden->bufferSize);
    446
    447    result = AudioUnitInitialize(this->hidden->audioUnit);
    448    CHECK_RESULT("AudioUnitInitialize");
    449
    450    /* Finally, start processing of the audio unit */
    451    result = AudioOutputUnitStart(this->hidden->audioUnit);
    452    CHECK_RESULT("AudioOutputUnitStart");
    453
    454    /* We're running! */
    455    return 1;
    456}
    457
    458
    459static int
    460COREAUDIO_OpenDevice(_THIS, const char *devname, int iscapture)
    461{
    462    AudioStreamBasicDescription strdesc;
    463    SDL_AudioFormat test_format = SDL_FirstAudioFormat(this->spec.format);
    464    int valid_datatype = 0;
    465
    466    /* Initialize all variables that we clean on shutdown */
    467    this->hidden = (struct SDL_PrivateAudioData *)
    468        SDL_malloc((sizeof *this->hidden));
    469    if (this->hidden == NULL) {
    470        return SDL_OutOfMemory();
    471    }
    472    SDL_memset(this->hidden, 0, (sizeof *this->hidden));
    473
    474    /* Setup a AudioStreamBasicDescription with the requested format */
    475    SDL_memset(&strdesc, '\0', sizeof(AudioStreamBasicDescription));
    476    strdesc.mFormatID = kAudioFormatLinearPCM;
    477    strdesc.mFormatFlags = kLinearPCMFormatFlagIsPacked;
    478    strdesc.mChannelsPerFrame = this->spec.channels;
    479    strdesc.mSampleRate = this->spec.freq;
    480    strdesc.mFramesPerPacket = 1;
    481
    482    while ((!valid_datatype) && (test_format)) {
    483        this->spec.format = test_format;
    484        /* Just a list of valid SDL formats, so people don't pass junk here. */
    485        switch (test_format) {
    486        case AUDIO_U8:
    487        case AUDIO_S8:
    488        case AUDIO_U16LSB:
    489        case AUDIO_S16LSB:
    490        case AUDIO_U16MSB:
    491        case AUDIO_S16MSB:
    492        case AUDIO_S32LSB:
    493        case AUDIO_S32MSB:
    494        case AUDIO_F32LSB:
    495        case AUDIO_F32MSB:
    496            valid_datatype = 1;
    497            strdesc.mBitsPerChannel = SDL_AUDIO_BITSIZE(this->spec.format);
    498            if (SDL_AUDIO_ISBIGENDIAN(this->spec.format))
    499                strdesc.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
    500
    501            if (SDL_AUDIO_ISFLOAT(this->spec.format))
    502                strdesc.mFormatFlags |= kLinearPCMFormatFlagIsFloat;
    503            else if (SDL_AUDIO_ISSIGNED(this->spec.format))
    504                strdesc.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
    505            break;
    506        }
    507    }
    508
    509    if (!valid_datatype) {      /* shouldn't happen, but just in case... */
    510        COREAUDIO_CloseDevice(this);
    511        return SDL_SetError("Unsupported audio format");
    512    }
    513
    514    strdesc.mBytesPerFrame =
    515        strdesc.mBitsPerChannel * strdesc.mChannelsPerFrame / 8;
    516    strdesc.mBytesPerPacket =
    517        strdesc.mBytesPerFrame * strdesc.mFramesPerPacket;
    518
    519    if (!prepare_audiounit(this, devname, iscapture, &strdesc)) {
    520        COREAUDIO_CloseDevice(this);
    521        return -1;      /* prepare_audiounit() will call SDL_SetError()... */
    522    }
    523
    524    return 0;   /* good to go. */
    525}
    526
    527static int
    528COREAUDIO_Init(SDL_AudioDriverImpl * impl)
    529{
    530    /* Set the function pointers */
    531    impl->OpenDevice = COREAUDIO_OpenDevice;
    532    impl->CloseDevice = COREAUDIO_CloseDevice;
    533
    534#if MACOSX_COREAUDIO
    535    impl->DetectDevices = COREAUDIO_DetectDevices;
    536#else
    537    impl->OnlyHasDefaultOutputDevice = 1;
    538
    539    /* Set category to ambient sound so that other music continues playing.
    540       You can change this at runtime in your own code if you need different
    541       behavior.  If this is common, we can add an SDL hint for this.
    542    */
    543    AudioSessionInitialize(NULL, NULL, NULL, nil);
    544    UInt32 category = kAudioSessionCategory_AmbientSound;
    545    AudioSessionSetProperty(kAudioSessionProperty_AudioCategory, sizeof(UInt32), &category);
    546#endif
    547
    548    impl->ProvidesOwnCallbackThread = 1;
    549
    550    return 1;   /* this audio target is available. */
    551}
    552
    553AudioBootStrap COREAUDIO_bootstrap = {
    554    "coreaudio", "CoreAudio", COREAUDIO_Init, 0
    555};
    556
    557/* vi: set ts=4 sw=4 expandtab: */