/* * Wine Driver for CoreAudio / AudioUnit * * Copyright 2005, 2006 Emmanuel Maillard * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ #include "config.h" #ifdef HAVE_AUDIOUNIT_AUDIOUNIT_H #define ULONG CoreFoundation_ULONG #define HRESULT CoreFoundation_HRESULT #include #include #include #undef ULONG #undef HRESULT #undef DPRINTF #undef STDMETHODCALLTYPE #include "coreaudio.h" #include "wine/debug.h" WINE_DEFAULT_DEBUG_CHANNEL(wave); WINE_DECLARE_DEBUG_CHANNEL(midi); static const char *streamDescription(const AudioStreamBasicDescription* stream) { return wine_dbg_sprintf("\n mSampleRate : %f\n mFormatID : %s\n mFormatFlags : %lX\n mBytesPerPacket : %lu\n mFramesPerPacket : %lu\n mBytesPerFrame : %lu\n mChannelsPerFrame : %lu\n mBitsPerChannel : %lu\n", stream->mSampleRate, wine_dbgstr_fourcc(stream->mFormatID), stream->mFormatFlags, stream->mBytesPerPacket, stream->mFramesPerPacket, stream->mBytesPerFrame, stream->mChannelsPerFrame, stream->mBitsPerChannel); } extern OSStatus CoreAudio_woAudioUnitIOProc(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData); extern OSStatus CoreAudio_wiAudioUnitIOProc(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData); int AudioUnit_CreateDefaultAudioUnit(void *wwo, AudioUnit *au) { OSStatus err; Component comp; ComponentDescription desc; AURenderCallbackStruct callbackStruct; TRACE("\n"); desc.componentType = kAudioUnitType_Output; desc.componentSubType = kAudioUnitSubType_DefaultOutput; desc.componentManufacturer = kAudioUnitManufacturer_Apple; desc.componentFlags = 0; desc.componentFlagsMask = 0; comp = FindNextComponent(NULL, &desc); if (comp == NULL) return 0; err = OpenAComponent(comp, au); if (err != noErr || *au == NULL) return 0; callbackStruct.inputProc = CoreAudio_woAudioUnitIOProc; callbackStruct.inputProcRefCon = wwo; err = AudioUnitSetProperty( *au, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &callbackStruct, sizeof(callbackStruct)); return (err == noErr); } int AudioUnit_CloseAudioUnit(AudioUnit au) { OSStatus err = CloseComponent(au); return (err == noErr); } int AudioUnit_InitializeWithStreamDescription(AudioUnit au, AudioStreamBasicDescription *stream) { OSStatus err = noErr; TRACE("input format: %s\n", streamDescription(stream)); err = AudioUnitSetProperty(au, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, stream, sizeof(*stream)); if (err != noErr) { ERR("AudioUnitSetProperty return an error %s\n", wine_dbgstr_fourcc(err)); return 0; } err = AudioUnitInitialize(au); if (err != noErr) { ERR("AudioUnitInitialize return an error %s\n", wine_dbgstr_fourcc(err)); return 0; } return 1; } int AudioUnit_SetVolume(AudioUnit au, float left, float right) { OSStatus err = noErr; FIXME("independent left/right volume not implemented (%f, %f)\n", left, right); err = AudioUnitSetParameter(au, kHALOutputParam_Volume, kAudioUnitParameterFlag_Output, 0, left, 0); if (err != noErr) { ERR("AudioUnitSetParameter return an error %s\n", wine_dbgstr_fourcc(err)); return 0; } return 1; } int AudioUnit_GetVolume(AudioUnit au, float *left, float *right) { OSStatus err = noErr; FIXME("independent left/right volume not implemented\n"); err = AudioUnitGetParameter(au, kHALOutputParam_Volume, kAudioUnitParameterFlag_Output, 0, left); if (err != noErr) { ERR("AudioUnitGetParameter return an error %s\n", wine_dbgstr_fourcc(err)); return 0; } *right = *left; return 1; } /* FIXME: implement sample rate conversion on input */ int AudioUnit_GetInputDeviceSampleRate(void) { AudioDeviceID defaultInputDevice; UInt32 param; Float64 sampleRate; OSStatus err; param = sizeof(defaultInputDevice); err = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultInputDevice, ¶m, &defaultInputDevice); if (err != noErr || defaultInputDevice == kAudioDeviceUnknown) { ERR("Couldn't get the default audio input device ID: %08lx\n", err); return 0; } param = sizeof(sampleRate); err = AudioDeviceGetProperty(defaultInputDevice, 0, 1, kAudioDevicePropertyNominalSampleRate, ¶m, &sampleRate); if (err != noErr) { ERR("Couldn't get the device sample rate: %08lx\n", err); return 0; } return sampleRate; } int AudioUnit_CreateInputUnit(void* wwi, AudioUnit* out_au, WORD nChannels, DWORD nSamplesPerSec, WORD wBitsPerSample, UInt32* outFrameCount) { OSStatus err = noErr; ComponentDescription description; Component component; AudioUnit au; UInt32 param; AURenderCallbackStruct callback; AudioDeviceID defaultInputDevice; AudioStreamBasicDescription desiredFormat; if (!outFrameCount) { ERR("Invalid parameter\n"); return 0; } /* Open the AudioOutputUnit */ description.componentType = kAudioUnitType_Output; description.componentSubType = kAudioUnitSubType_HALOutput; description.componentManufacturer = kAudioUnitManufacturer_Apple; description.componentFlags = 0; description.componentFlagsMask = 0; component = FindNextComponent(NULL, &description); if (!component) { ERR("FindNextComponent(kAudioUnitSubType_HALOutput) failed\n"); return 0; } err = OpenAComponent(component, &au); if (err != noErr || au == NULL) { ERR("OpenAComponent failed: %08lx\n", err); return 0; } /* Configure the AudioOutputUnit */ /* The AUHAL has two buses (AKA elements). Bus 0 is output from the app * to the device. Bus 1 is input from the device to the app. Each bus * has two ends (AKA scopes). Data goes from the input scope to the * output scope. The terminology is somewhat confusing because the terms * "input" and "output" have two meanings. Here's a summary: * * Bus 0, input scope: refers to the source of data to be output as sound * Bus 0, output scope: refers to the actual sound output device * Bus 1, input scope: refers to the actual sound input device * Bus 1, output scope: refers to the destination of data received by the input device */ /* Enable input on the AUHAL */ param = 1; err = AudioUnitSetProperty(au, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, ¶m, sizeof(param)); if (err != noErr) { ERR("Couldn't enable input on AUHAL: %08lx\n", err); goto error; } /* Disable Output on the AUHAL */ param = 0; err = AudioUnitSetProperty(au, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, 0, ¶m, sizeof(param)); if (err != noErr) { ERR("Couldn't disable output on AUHAL: %08lx\n", err); goto error; } /* Find the default input device */ param = sizeof(defaultInputDevice); err = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultInputDevice, ¶m, &defaultInputDevice); if (err != noErr || defaultInputDevice == kAudioDeviceUnknown) { ERR("Couldn't get the default audio device ID: %08lx\n", err); goto error; } /* Set the current device to the default input device. */ err = AudioUnitSetProperty(au, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, 0, &defaultInputDevice, sizeof(defaultInputDevice)); if (err != noErr) { ERR("Couldn't set current device of AUHAL to default input device: %08lx\n", err); goto error; } /* Setup render callback */ /* This will be called when the AUHAL has input data. However, it won't * be passed the data itself. The callback will have to all AudioUnitRender. */ callback.inputProc = CoreAudio_wiAudioUnitIOProc; callback.inputProcRefCon = wwi; err = AudioUnitSetProperty(au, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, 0, &callback, sizeof(callback)); if (err != noErr) { ERR("Couldn't set input callback of AUHAL: %08lx\n", err); goto error; } /* Setup the desired data format. */ /* FIXME: implement sample rate conversion on input. We shouldn't set * the mSampleRate of this to the desired sample rate. We need to query * the input device and use that. If they don't match, we need to set up * an AUConverter to do the sample rate conversion on a separate thread. */ desiredFormat.mFormatID = kAudioFormatLinearPCM; desiredFormat.mFormatFlags = kLinearPCMFormatFlagIsPacked; if (wBitsPerSample != 8) desiredFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger; desiredFormat.mSampleRate = nSamplesPerSec; desiredFormat.mChannelsPerFrame = nChannels; desiredFormat.mFramesPerPacket = 1; desiredFormat.mBitsPerChannel = wBitsPerSample; desiredFormat.mBytesPerFrame = desiredFormat.mBitsPerChannel * desiredFormat.mChannelsPerFrame / 8; desiredFormat.mBytesPerPacket = desiredFormat.mBytesPerFrame * desiredFormat.mFramesPerPacket; /* Set the AudioOutputUnit output data format */ err = AudioUnitSetProperty(au, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &desiredFormat, sizeof(desiredFormat)); if (err != noErr) { ERR("Couldn't set desired input format of AUHAL: %08lx\n", err); goto error; } /* Get the number of frames in the IO buffer(s) */ param = sizeof(*outFrameCount); err = AudioUnitGetProperty(au, kAudioDevicePropertyBufferFrameSize, kAudioUnitScope_Global, 0, outFrameCount, ¶m); if (err != noErr) { ERR("Failed to get audio sample size: %08lx\n", err); goto error; } TRACE("Frame count: %lu\n", *outFrameCount); /* Initialize the AU */ err = AudioUnitInitialize(au); if (err != noErr) { ERR("Failed to initialize AU: %08lx\n", err); goto error; } *out_au = au; return 1; error: if (au) CloseComponent(au); return 0; } /* * MIDI Synth Unit */ int SynthUnit_CreateDefaultSynthUnit(AUGraph *graph, AudioUnit *synth) { OSStatus err; ComponentDescription desc; AUNode synthNode; AUNode outNode; err = NewAUGraph(graph); if (err != noErr) { ERR_(midi)("NewAUGraph return %s\n", wine_dbgstr_fourcc(err)); return 0; } desc.componentManufacturer = kAudioUnitManufacturer_Apple; desc.componentFlags = 0; desc.componentFlagsMask = 0; /* create synth node */ desc.componentType = kAudioUnitType_MusicDevice; desc.componentSubType = kAudioUnitSubType_DLSSynth; err = AUGraphNewNode(*graph, &desc, 0, NULL, &synthNode); if (err != noErr) { ERR_(midi)("AUGraphNewNode cannot create synthNode : %s\n", wine_dbgstr_fourcc(err)); return 0; } /* create out node */ desc.componentType = kAudioUnitType_Output; desc.componentSubType = kAudioUnitSubType_DefaultOutput; err = AUGraphNewNode(*graph, &desc, 0, NULL, &outNode); if (err != noErr) { ERR_(midi)("AUGraphNewNode cannot create outNode %s\n", wine_dbgstr_fourcc(err)); return 0; } err = AUGraphOpen(*graph); if (err != noErr) { ERR_(midi)("AUGraphOpen return %s\n", wine_dbgstr_fourcc(err)); return 0; } /* connecting the nodes */ err = AUGraphConnectNodeInput(*graph, synthNode, 0, outNode, 0); if (err != noErr) { ERR_(midi)("AUGraphConnectNodeInput cannot connect synthNode to outNode : %s\n", wine_dbgstr_fourcc(err)); return 0; } /* Get the synth unit */ err = AUGraphGetNodeInfo(*graph, synthNode, 0, 0, 0, synth); if (err != noErr) { ERR_(midi)("AUGraphGetNodeInfo return %s\n", wine_dbgstr_fourcc(err)); return 0; } return 1; } int SynthUnit_Initialize(AudioUnit synth, AUGraph graph) { OSStatus err = noErr; err = AUGraphInitialize(graph); if (err != noErr) { ERR_(midi)("AUGraphInitialize(%p) return %s\n", graph, wine_dbgstr_fourcc(err)); return 0; } err = AUGraphStart(graph); if (err != noErr) { ERR_(midi)("AUGraphStart(%p) return %s\n", graph, wine_dbgstr_fourcc(err)); return 0; } return 1; } int SynthUnit_Close(AUGraph graph) { OSStatus err = noErr; err = AUGraphStop(graph); if (err != noErr) { ERR_(midi)("AUGraphStop(%p) return %s\n", graph, wine_dbgstr_fourcc(err)); return 0; } err = DisposeAUGraph(graph); if (err != noErr) { ERR_(midi)("DisposeAUGraph(%p) return %s\n", graph, wine_dbgstr_fourcc(err)); return 0; } return 1; } #endif