summaryrefslogtreecommitdiff
path: root/plugins/coreaudio
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/coreaudio')
-rw-r--r--plugins/coreaudio/coreaudio.c318
1 files changed, 318 insertions, 0 deletions
diff --git a/plugins/coreaudio/coreaudio.c b/plugins/coreaudio/coreaudio.c
new file mode 100644
index 00000000..ffa31a75
--- /dev/null
+++ b/plugins/coreaudio/coreaudio.c
@@ -0,0 +1,318 @@
+/*
+ CoreAudio Output Deadbeef Plugin
+ Copyright (c) 2011-2013 Carlos Nunes <carloslnunes@gmail.com>
+
+ This plugin 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 of the License, or (at your option) any later version.
+
+ This plugin 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 Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+ */
+
+#include "deadbeef.h"
+
+#include <AudioUnit/AudioUnit.h> // AudioUnit
+#include <CoreAudio/CoreAudio.h> // AudioDeviceID
+#include <AudioToolbox/AudioFormat.h> // AudioFormatGetProperty
+#include <CoreServices/CoreServices.h>
+
+
+//#define trace(...) { fprintf(stderr, __VA_ARGS__); }
+#define trace(fmt,...)
+
+static DB_output_t plugin;
+DB_functions_t *deadbeef;
+
+static int state; // playing/stopped/paused
+static AudioUnit output_unit; // output unit for the audio
+static int au_state;
+
+static int
+coreaudio_plugin_start (void) {
+
+ // This is a largely undocumented but absolutely necessary
+ // requirement starting with OS-X 10.6. If not called, queries and
+ // updates to various audio device properties are not handled
+ // correctly.
+ // Many thanks to the rtaudio project for documenting this.
+ CFRunLoopRef theRunLoop = NULL;
+ AudioObjectPropertyAddress property = { kAudioHardwarePropertyRunLoop, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
+ OSStatus err = AudioObjectSetPropertyData( kAudioObjectSystemObject, &property, 0, NULL, sizeof(CFRunLoopRef), &theRunLoop);
+ if (err) { trace ("AudioObjectSetPropertyData-plugin_start= %s\n", GetMacOSStatusErrorString(err) ); return -1; }
+
+ au_state = 0;
+
+ return 0;
+}
+
+static int
+coreaudio_plugin_stop (void) {
+ return 0;
+}
+
+int
+coreaudio_set_data_format(ddb_waveformat_t *fmt) {
+
+ AudioStreamBasicDescription streamFormat;
+
+ // lets assume that if it has more than one channel it is interleaved
+ bool inIsNonInterleaved = false;
+ if (fmt->channels == 1)
+ inIsNonInterleaved = true;
+
+ UInt32 flags = (fmt->is_float ? kAudioFormatFlagIsFloat : kAudioFormatFlagIsSignedInteger) |
+ (fmt->is_bigendian ? ((UInt32)kAudioFormatFlagIsBigEndian) : 0) |
+ ((!(fmt->is_float) ) ?
+ kAudioFormatFlagIsPacked : kAudioFormatFlagIsAlignedHigh) |
+ (inIsNonInterleaved ? ((UInt32)kAudioFormatFlagIsNonInterleaved) : 0);
+
+ streamFormat.mSampleRate = fmt->samplerate;
+ streamFormat.mFormatID = kAudioFormatLinearPCM;
+ streamFormat.mFormatFlags = flags;
+ streamFormat.mBytesPerPacket = (inIsNonInterleaved ? 1 : fmt->channels) * (fmt->bps/8);
+ streamFormat.mFramesPerPacket = 1;
+ streamFormat.mBytesPerFrame = (inIsNonInterleaved ? 1 : fmt->channels) * (fmt->bps/8);
+ streamFormat.mChannelsPerFrame = fmt->channels;
+ streamFormat.mBitsPerChannel = fmt->bps;
+
+ OSStatus err = noErr;
+ err = AudioUnitSetProperty (output_unit,
+ kAudioUnitProperty_StreamFormat,
+ kAudioUnitScope_Input,
+ 0,
+ &streamFormat,
+ sizeof(streamFormat));
+ if (err) { trace ("AudioUnitSetProperty-SF= %s\n", GetMacOSStatusErrorString(err) ); return -1; }
+
+ return 0;
+}
+
+/* data callback for the audio unit */
+OSStatus
+coreaudio_callback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData)
+{
+
+ if(ioData == NULL && ioData->mNumberBuffers < 1)
+ { trace ("no buffers\n"); return -1; /* not an OSStatus but it is not that important... */ }
+
+ int sample_size = plugin.fmt.channels * (plugin.fmt.bps / 8);
+ int block_size = ioData->mBuffers[0].mDataByteSize;
+ int mod = block_size % sample_size;
+ if ( mod > 0)
+ block_size -= mod; // should not happen but just in case...
+
+ char * buffer = NULL;
+ buffer = (char*) malloc( block_size );
+ memset(buffer, 0, block_size);
+
+ deadbeef->streamer_read( buffer, block_size); // fetching the data from stream
+
+ for (int i = 0; i < ioData->mNumberBuffers; ++i) {
+
+ AudioBuffer audio_buffer = ioData->mBuffers[i];
+ void * frame_buffer = audio_buffer.mData;
+ void * stream_buffer = buffer;
+
+ memcpy(frame_buffer, stream_buffer, audio_buffer.mDataByteSize);
+ }
+
+ free(buffer);
+
+ return noErr;
+}
+
+static int
+coreaudio_init (void) {
+ trace ("coreaudio_init\n");
+
+ //selecting the default output unit
+ ComponentDescription desc;
+ desc.componentType = kAudioUnitType_Output;
+ desc.componentSubType = kAudioUnitSubType_DefaultOutput;
+ desc.componentManufacturer = kAudioUnitManufacturer_Apple;
+ desc.componentFlags = 0;
+ desc.componentFlagsMask = 0;
+
+ OSStatus err = noErr;
+ Component comp = FindNextComponent(NULL, &desc);
+ if (comp == NULL) { trace ("FindNextComponent= failed to find the default output component.\n"); return -1; }
+
+ err = OpenAComponent(comp, &output_unit);
+ if (comp == NULL) { trace ("OpenAComponent= %s\n", GetMacOSStatusErrorString(err)); return -1; }
+
+ // filling out the description for linear PCM data (can only be called after opening audio component)
+ if (coreaudio_set_data_format(&plugin.fmt) < 0)
+ return -1;
+
+ // callback
+ AURenderCallbackStruct input_cb;
+ input_cb.inputProc = coreaudio_callback;
+ input_cb.inputProcRefCon = NULL;
+
+ err = AudioUnitSetProperty(output_unit,
+ kAudioUnitProperty_SetRenderCallback,
+ kAudioUnitScope_Input,
+ 0,
+ &input_cb,
+ sizeof(input_cb));
+ if (err)
+ { trace ("AudioUnitSetProperty-CB= %s\n", GetMacOSStatusErrorString(err)); return -1; }
+
+ // Initialize unit
+ err = AudioUnitInitialize(output_unit);
+ if (err) { trace ("AudioUnitInitialize= %s\n", GetMacOSStatusErrorString(err)); return -1; }
+
+
+ au_state = 1; // audio unit initialised
+ state = OUTPUT_STATE_STOPPED;
+
+ return 0;
+}
+
+static int
+coreaudio_free(void) {
+
+ trace("coreaudio_free\n");
+
+ state = OUTPUT_STATE_STOPPED;
+
+ if(!au_state) // audio unit already unintialized
+ return 0;
+
+ OSStatus err = AudioUnitUninitialize (output_unit);
+ if (err) { trace ("AudioUnitUninitialize= %s\n", GetMacOSStatusErrorString(err)); return -1; }
+ au_state = 0;
+
+ CloseComponent(output_unit);
+
+ return 0;
+}
+
+static int
+coreaudio_play (void) {
+
+ if (coreaudio_init() < 0)
+ return -1;
+
+ OSStatus err = AudioOutputUnitStart (output_unit);
+ if (err) { trace ("AudioOutputUnitStart= %s\n", GetMacOSStatusErrorString(err)); return -1; }
+
+ state = OUTPUT_STATE_PLAYING;
+ return 0;
+}
+
+static int
+coreaudio_stop(void) {
+
+ trace("coreaudio_stop\n");
+
+ state = OUTPUT_STATE_STOPPED;
+ deadbeef->streamer_reset(1);
+
+ if(!au_state) // no audio unit to stop
+ return 0;
+
+ OSStatus err = AudioOutputUnitStop (output_unit);
+ if (err) { trace ("AudioOutputUnitStop= %s\n", GetMacOSStatusErrorString(err)); return -1; }
+
+ return coreaudio_free();
+}
+
+static int
+coreaudio_pause(void) {
+
+ state = OUTPUT_STATE_PAUSED;
+ AudioOutputUnitStop(output_unit);
+
+ return 0;
+}
+
+static int
+coreaudio_unpause(void) {
+
+ trace("coreaudio_unpause\n");
+
+ OSStatus err = AudioOutputUnitStart (output_unit);
+ if (err) { trace ("AudioOutputUnitStart= %s\n", GetMacOSStatusErrorString(err)); return -1; }
+
+ state = OUTPUT_STATE_PLAYING;
+
+ return 0;
+}
+
+static int
+coreaudio_setformat (ddb_waveformat_t *fmt) {
+ trace ("coreaudio_setformat\n");
+
+ memcpy (&plugin.fmt, fmt, sizeof (ddb_waveformat_t));
+
+ switch (state) {
+ case OUTPUT_STATE_STOPPED:
+ return coreaudio_stop ();
+ case OUTPUT_STATE_PLAYING:
+ return coreaudio_play ();
+ case OUTPUT_STATE_PAUSED:
+ if (0 != coreaudio_play ()) {
+ return -1;
+ }
+ if (0 != coreaudio_pause ()) {
+ return -1;
+ }
+ break;
+ }
+
+ return 0;
+
+}
+
+static int
+coreaudio_get_state(void) {
+ return state;
+}
+
+DB_plugin_t *
+coreaudio_load (DB_functions_t *api) {
+ deadbeef = api;
+ return DB_PLUGIN (&plugin);
+}
+
+// define plugin interface
+static DB_output_t plugin = {
+ .plugin.api_vmajor = 1,
+ .plugin.api_vminor = 0,
+ .plugin.version_major = 0,
+ .plugin.version_minor = 1,
+ .plugin.type = DB_PLUGIN_OUTPUT,
+ .plugin.id = "coreaudio",
+ .plugin.name = "core audio output plugin",
+ .plugin.descr = "Uses the core audio framework to output sound.",
+ .plugin.copyright =
+ "CoreAudio Output Deadbeef Plugin\n"
+ "Copyright (c) 2011-2013 Carlos Nunes <carloslnunes@gmail.com>\n"
+ "\n"
+ "CoreAudio Output Deadbeef Plugin is licensed under the GNU \n"
+ "Lesser General Public License version 2.\n"
+ ,
+ .plugin.website = "http://spontaneouscoders.com/projects/coreaudio-output-deadbeef-plugin",
+ .plugin.start = coreaudio_plugin_start,
+ .plugin.stop = coreaudio_plugin_stop,
+ .init = coreaudio_init,
+ .free = coreaudio_free,
+ .setformat = coreaudio_setformat,
+ .play = coreaudio_play,
+ .stop = coreaudio_stop,
+ .pause = coreaudio_pause,
+ .unpause = coreaudio_unpause,
+ .state = coreaudio_get_state,
+ .fmt = {-1},
+};
+