2005-03-13 20:23:09 +01:00
|
|
|
/* the Music Player Daemon (MPD)
|
|
|
|
* (c)2003-2004 by Warren Dukes (shank@mercury.chem.pitt.edu)
|
|
|
|
* This project's homepage is: http://www.musicpd.org
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program 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 General Public License for more details.
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "../audioOutput.h"
|
|
|
|
|
|
|
|
#ifdef HAVE_OSX
|
|
|
|
|
|
|
|
#include "../conf.h"
|
|
|
|
#include "../log.h"
|
|
|
|
|
2005-03-13 22:33:55 +01:00
|
|
|
#include <CoreAudio/AudioHardware.h>
|
2005-03-13 20:23:09 +01:00
|
|
|
#include <stdlib.h>
|
2005-03-14 05:30:32 +01:00
|
|
|
#include <pthread.h>
|
|
|
|
|
|
|
|
#define BUFFER_SIZE 1024
|
2005-03-13 20:23:09 +01:00
|
|
|
|
|
|
|
typedef struct _OsxData {
|
2005-03-14 05:30:32 +01:00
|
|
|
AudioDeviceID deviceID;
|
|
|
|
AudioStreamBasicDescription streamDesc;
|
|
|
|
pthread_mutex_t mutex;
|
|
|
|
pthread_cond_t condition;
|
|
|
|
Float32 buffer[BUFFER_SIZE];
|
|
|
|
int pos;
|
|
|
|
int len;
|
|
|
|
int go;
|
|
|
|
int started;
|
2005-03-13 20:23:09 +01:00
|
|
|
} OsxData;
|
|
|
|
|
2005-03-14 05:30:32 +01:00
|
|
|
static void printError(OSStatus val) {
|
|
|
|
switch(val) {
|
|
|
|
case kAudioHardwareNoError:
|
|
|
|
ERROR("kAudioHardwareNoErr");
|
|
|
|
break;
|
|
|
|
case kAudioHardwareNotRunningError:
|
|
|
|
ERROR("kAudioHardwareNotRunningError");
|
|
|
|
break;
|
|
|
|
case kAudioHardwareUnspecifiedError:
|
|
|
|
ERROR("kAudioHardwareUnspecifiedError");
|
|
|
|
break;
|
|
|
|
case kAudioHardwareUnknownPropertyError:
|
|
|
|
ERROR("kAudioHardwareUnknownPropertyError");
|
|
|
|
break;
|
|
|
|
case kAudioHardwareBadPropertySizeError:
|
|
|
|
ERROR("kAudioHardwareBadPropertySizeError");
|
|
|
|
break;
|
|
|
|
case kAudioHardwareIllegalOperationError:
|
|
|
|
ERROR("kAudioHardwareIllegalOperationError");
|
|
|
|
break;
|
|
|
|
case kAudioHardwareBadDeviceError:
|
|
|
|
ERROR("kAudioHardwareBadDeviceError");
|
|
|
|
break;
|
|
|
|
case kAudioHardwareBadStreamError:
|
|
|
|
ERROR("kAudioHardwareBadStreamError");
|
|
|
|
break;
|
|
|
|
case kAudioHardwareUnsupportedOperationError:
|
|
|
|
ERROR("kAudioHardwareUnsupportedOperationError");
|
|
|
|
break;
|
|
|
|
case kAudioDeviceUnsupportedFormatError:
|
|
|
|
ERROR("kAudioDeviceUnsupportedFormatError");
|
|
|
|
break;
|
|
|
|
case kAudioDevicePermissionsError:
|
|
|
|
ERROR("kAudioDevicePermissionsError");
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
ERROR("unknown");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2005-03-13 20:23:09 +01:00
|
|
|
static OsxData * newOsxData() {
|
|
|
|
OsxData * ret = malloc(sizeof(OsxData));
|
|
|
|
|
2005-03-14 05:30:32 +01:00
|
|
|
ret->deviceID = kAudioDeviceUnknown;
|
|
|
|
|
|
|
|
pthread_mutex_init(&ret->mutex, NULL);
|
|
|
|
pthread_cond_init(&ret->condition, NULL);
|
|
|
|
|
|
|
|
ret->pos = 0;
|
|
|
|
ret->len = 0;
|
|
|
|
ret->go = 0;
|
|
|
|
ret->started = 0;
|
|
|
|
|
2005-03-13 20:23:09 +01:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2005-03-13 22:33:55 +01:00
|
|
|
static int osx_testDefault() {
|
|
|
|
int err;
|
|
|
|
AudioDeviceID deviceID;
|
|
|
|
UInt32 propertySize = sizeof(deviceID);
|
|
|
|
|
|
|
|
err = AudioHardwareGetProperty(
|
|
|
|
kAudioHardwarePropertyDefaultOutputDevice,
|
|
|
|
&propertySize, &deviceID);
|
|
|
|
if(err || deviceID == kAudioDeviceUnknown) {
|
|
|
|
WARNING("Not able to get the default OS X device\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2005-03-13 20:23:09 +01:00
|
|
|
static int osx_initDriver(AudioOutput * audioOutput, ConfigParam * param) {
|
|
|
|
OsxData * od = newOsxData();
|
|
|
|
|
|
|
|
audioOutput->data = od;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void freeOsxData(OsxData * od) {
|
|
|
|
free(od);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void osx_finishDriver(AudioOutput * audioOutput) {
|
|
|
|
OsxData * od = (OsxData *)audioOutput->data;
|
|
|
|
freeOsxData(od);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void osx_dropBufferedAudio(AudioOutput * audioOutput) {
|
|
|
|
/* not implemented yet */
|
|
|
|
}
|
|
|
|
|
|
|
|
static void osx_closeDevice(AudioOutput * audioOutput) {
|
|
|
|
//OsxData * od = (OsxData *) audioOutput->data;
|
|
|
|
|
|
|
|
audioOutput->open = 0;
|
|
|
|
}
|
|
|
|
|
2005-03-14 05:30:32 +01:00
|
|
|
static OSStatus osx_IOProc(AudioDeviceID deviceID,
|
|
|
|
const AudioTimeStamp * inNow, const AudioBufferList *inData,
|
|
|
|
const AudioTimeStamp * inInputTime, AudioBufferList *outData,
|
|
|
|
const AudioTimeStamp * inOutputTime, void * vdata)
|
|
|
|
{
|
|
|
|
OsxData * od = (OsxData *)vdata;
|
|
|
|
AudioBuffer * buffer = &outData->mBuffers[0];
|
|
|
|
int bufferSize = buffer->mDataByteSize/sizeof(Float32);
|
|
|
|
int floatsToCopy;
|
|
|
|
int curpos = 0;
|
|
|
|
|
|
|
|
DEBUG("entering IOProc\n");
|
|
|
|
|
|
|
|
pthread_mutex_lock(&od->mutex);
|
|
|
|
|
|
|
|
while((od->go || od->len) && bufferSize) {
|
|
|
|
while(od->go && od->len < bufferSize &&
|
|
|
|
od->len < BUFFER_SIZE)
|
|
|
|
{
|
|
|
|
pthread_cond_wait(&od->condition, &od->mutex);
|
|
|
|
}
|
|
|
|
|
|
|
|
floatsToCopy = od->len < bufferSize ? od->len : bufferSize;
|
|
|
|
bufferSize -= floatsToCopy;
|
|
|
|
|
|
|
|
if(od->pos+floatsToCopy > BUFFER_SIZE) {
|
|
|
|
int floats = BUFFER_SIZE-od->pos;
|
|
|
|
memcpy(buffer->mData+curpos, od->buffer+od->pos,
|
|
|
|
floats*sizeof(Float32));
|
|
|
|
od->len -= floats;
|
|
|
|
od->pos = 0;
|
|
|
|
curpos += floats;
|
|
|
|
floatsToCopy -= floats;
|
|
|
|
}
|
|
|
|
|
|
|
|
memcpy(buffer->mData+curpos, od->buffer+od->pos,
|
|
|
|
floatsToCopy*sizeof(Float32));
|
|
|
|
od->len -= floatsToCopy;
|
|
|
|
od->pos += floatsToCopy;
|
|
|
|
curpos += floatsToCopy;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(bufferSize) {
|
|
|
|
memset(buffer->mData+curpos, 0, bufferSize*sizeof(Float32));
|
|
|
|
}
|
|
|
|
|
|
|
|
pthread_mutex_unlock(&od->mutex);
|
|
|
|
pthread_cond_signal(&od->condition);
|
|
|
|
|
|
|
|
DEBUG("exiting IOProc\n");
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2005-03-13 20:23:09 +01:00
|
|
|
static int osx_openDevice(AudioOutput * audioOutput) {
|
2005-03-14 05:30:32 +01:00
|
|
|
int err;
|
|
|
|
OsxData * od = (OsxData *)audioOutput->data;
|
|
|
|
UInt32 propertySize;
|
|
|
|
AudioFormat * audioFormat = &audioOutput->outAudioFormat;
|
|
|
|
UInt32 bufferByteCount = 8192;
|
|
|
|
|
|
|
|
propertySize = sizeof(od->deviceID);
|
|
|
|
err = AudioHardwareGetProperty(
|
|
|
|
kAudioHardwarePropertyDefaultOutputDevice,
|
|
|
|
&propertySize, &od->deviceID);
|
|
|
|
if(err || od->deviceID == kAudioDeviceUnknown) {
|
|
|
|
ERROR("Not able to get the default OS X device\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
od->streamDesc.mFormatID = kAudioFormatLinearPCM;
|
|
|
|
od->streamDesc.mSampleRate = audioFormat->sampleRate;
|
|
|
|
od->streamDesc.mFormatFlags = kLinearPCMFormatFlagIsFloat |
|
|
|
|
kLinearPCMFormatFlagIsBigEndian |
|
|
|
|
kLinearPCMFormatFlagIsPacked;
|
|
|
|
od->streamDesc.mBytesPerPacket = audioFormat->channels*sizeof(Float32);
|
|
|
|
od->streamDesc.mFramesPerPacket = 1;
|
|
|
|
od->streamDesc.mBytesPerFrame = audioFormat->channels*sizeof(Float32);
|
|
|
|
od->streamDesc.mChannelsPerFrame = audioFormat->channels;
|
|
|
|
od->streamDesc.mBitsPerChannel = 8 * sizeof(Float32);
|
|
|
|
|
|
|
|
audioFormat->bits = 16;
|
|
|
|
|
|
|
|
propertySize = sizeof(od->streamDesc);
|
|
|
|
err = AudioDeviceSetProperty(od->deviceID, 0, 0, false,
|
|
|
|
kAudioDevicePropertyStreamFormat,
|
|
|
|
propertySize, &od->streamDesc);
|
|
|
|
if(err) {
|
|
|
|
ERROR("unable to set format %i:%i:% on osx device\n",
|
|
|
|
(int)audioFormat->sampleRate,
|
|
|
|
(int)audioFormat->bits,
|
|
|
|
(int)audioFormat->channels);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
propertySize = sizeof(UInt32);
|
|
|
|
err = AudioDeviceSetProperty(od->deviceID, 0, 0, false,
|
|
|
|
kAudioDevicePropertyBufferSize,
|
|
|
|
propertySize, &bufferByteCount);
|
|
|
|
|
|
|
|
err = AudioDeviceAddIOProc(od->deviceID, osx_IOProc, od);
|
|
|
|
if(err) {
|
|
|
|
ERROR("error adding IOProc\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
od->go = 1;
|
|
|
|
od->pos = 0;
|
|
|
|
od->len = 0;
|
2005-03-13 20:23:09 +01:00
|
|
|
|
|
|
|
audioOutput->open = 1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2005-03-14 05:30:32 +01:00
|
|
|
static void copyIntBufferToFloat(char * playChunk, int size, float * buffer,
|
|
|
|
int floats)
|
|
|
|
{
|
|
|
|
/* this is for 16-bit audio only */
|
|
|
|
SInt16 * sample;
|
|
|
|
|
|
|
|
while(floats) {
|
|
|
|
sample = (SInt16 *)playChunk;
|
|
|
|
*buffer = *sample/32767.0;
|
|
|
|
playChunk += 2;
|
|
|
|
buffer++;
|
|
|
|
floats--;
|
|
|
|
}
|
|
|
|
}
|
2005-03-13 20:23:09 +01:00
|
|
|
|
|
|
|
static int osx_play(AudioOutput * audioOutput, char * playChunk, int size) {
|
2005-03-14 05:30:32 +01:00
|
|
|
OsxData * od = (OsxData *)audioOutput->data;
|
|
|
|
int floatsToCopy;
|
|
|
|
|
|
|
|
size /= 2;
|
|
|
|
|
|
|
|
DEBUG("entering osx_play\n");
|
|
|
|
|
|
|
|
pthread_mutex_lock(&od->mutex);
|
|
|
|
|
|
|
|
DEBUG("entering while loop\n");
|
|
|
|
while(size) {
|
|
|
|
DEBUG("iterating loop with size = %i\n", size);
|
|
|
|
while(od->len == BUFFER_SIZE) {
|
|
|
|
if(!od->started) {
|
|
|
|
OSStatus err = AudioDeviceStart(od->deviceID,
|
|
|
|
osx_IOProc);
|
|
|
|
DEBUG("start audio device\n");
|
|
|
|
if(err) {
|
|
|
|
printError(err);
|
|
|
|
ERROR(" error doing AudioDeviceStart "
|
|
|
|
"for osx device: %i\n",
|
|
|
|
(int)err);
|
|
|
|
pthread_mutex_unlock(&od->mutex);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
od->started = 1;
|
|
|
|
DEBUG("audio device started\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
DEBUG("cond_wait\n");
|
|
|
|
pthread_cond_wait(&od->condition, &od->mutex);
|
|
|
|
}
|
|
|
|
|
|
|
|
floatsToCopy = BUFFER_SIZE - od->len;
|
|
|
|
floatsToCopy = floatsToCopy < size ? floatsToCopy : size;
|
|
|
|
size -= floatsToCopy;
|
|
|
|
|
|
|
|
if(od->pos+floatsToCopy > BUFFER_SIZE) {
|
|
|
|
int floats = BUFFER_SIZE-od->pos;
|
|
|
|
copyIntBufferToFloat(playChunk,
|
|
|
|
audioOutput->outAudioFormat.bits/8,
|
|
|
|
od->buffer,
|
|
|
|
floats);
|
|
|
|
od->pos = 0;
|
|
|
|
od->len += floats;
|
|
|
|
playChunk += floats*sizeof(Float32);
|
|
|
|
}
|
|
|
|
|
|
|
|
copyIntBufferToFloat(playChunk,
|
|
|
|
audioOutput->outAudioFormat.bits/8,
|
|
|
|
od->buffer,
|
|
|
|
floatsToCopy);
|
|
|
|
od->pos += floatsToCopy;
|
|
|
|
od->len += floatsToCopy;
|
|
|
|
playChunk += floatsToCopy*sizeof(Float32);
|
|
|
|
}
|
|
|
|
|
|
|
|
pthread_mutex_unlock(&od->mutex);
|
|
|
|
pthread_cond_signal(&od->condition);
|
2005-03-13 20:23:09 +01:00
|
|
|
|
2005-03-14 05:30:32 +01:00
|
|
|
DEBUG("exiting osx_play\n");
|
2005-03-13 20:23:09 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
AudioOutputPlugin osxPlugin =
|
|
|
|
{
|
|
|
|
"osx",
|
2005-03-13 22:33:55 +01:00
|
|
|
osx_testDefault,
|
2005-03-13 20:23:09 +01:00
|
|
|
osx_initDriver,
|
|
|
|
osx_finishDriver,
|
|
|
|
osx_openDevice,
|
|
|
|
osx_play,
|
|
|
|
osx_dropBufferedAudio,
|
|
|
|
osx_closeDevice,
|
|
|
|
NULL /* sendMetadataFunc */
|
|
|
|
};
|
|
|
|
|
|
|
|
#else
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
|
|
|
AudioOutputPlugin osxPlugin =
|
|
|
|
{
|
|
|
|
NULL,
|
|
|
|
NULL,
|
|
|
|
NULL,
|
|
|
|
NULL,
|
|
|
|
NULL,
|
|
|
|
NULL,
|
|
|
|
NULL,
|
|
|
|
NULL,
|
|
|
|
NULL
|
|
|
|
};
|
|
|
|
|
|
|
|
#endif
|