You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
418 lines
12 KiB
418 lines
12 KiB
/* AudioCore sound handler
|
|
*
|
|
* Copyright 2008 Chris Bagwell And Sundry Contributors
|
|
*/
|
|
|
|
#include "sox_i.h"
|
|
|
|
#include "CoreAudio/CoreAudio.h"
|
|
#include <pthread.h>
|
|
|
|
#define Buffactor 4
|
|
|
|
typedef struct {
|
|
AudioDeviceID adid;
|
|
pthread_mutex_t mutex;
|
|
pthread_cond_t cond;
|
|
int device_started;
|
|
size_t bufsize;
|
|
size_t bufrd;
|
|
size_t bufwr;
|
|
size_t bufrdavail;
|
|
float *buf;
|
|
} priv_t;
|
|
|
|
static OSStatus PlaybackIOProc(AudioDeviceID inDevice UNUSED,
|
|
const AudioTimeStamp *inNow UNUSED,
|
|
const AudioBufferList *inInputData UNUSED,
|
|
const AudioTimeStamp *inInputTime UNUSED,
|
|
AudioBufferList *outOutputData,
|
|
const AudioTimeStamp *inOutputTime UNUSED,
|
|
void *inClientData)
|
|
{
|
|
priv_t *ac = (priv_t*)((sox_format_t*)inClientData)->priv;
|
|
AudioBuffer *buf;
|
|
size_t copylen, avail;
|
|
|
|
pthread_mutex_lock(&ac->mutex);
|
|
|
|
for(buf = outOutputData->mBuffers;
|
|
buf != outOutputData->mBuffers + outOutputData->mNumberBuffers;
|
|
buf++){
|
|
|
|
copylen = buf->mDataByteSize / sizeof(float);
|
|
if(copylen > ac->bufrdavail)
|
|
copylen = ac->bufrdavail;
|
|
|
|
avail = ac->bufsize - ac->bufrd;
|
|
if(buf->mData == NULL){
|
|
/*do nothing-hardware can't play audio*/
|
|
}else if(copylen > avail){
|
|
memcpy(buf->mData, ac->buf + ac->bufrd, avail * sizeof(float));
|
|
memcpy((float*)buf->mData + avail, ac->buf, (copylen - avail) * sizeof(float));
|
|
}else{
|
|
memcpy(buf->mData, ac->buf + ac->bufrd, copylen * sizeof(float));
|
|
}
|
|
|
|
buf->mDataByteSize = copylen * sizeof(float);
|
|
ac->bufrd += copylen;
|
|
if(ac->bufrd >= ac->bufsize)
|
|
ac->bufrd -= ac->bufsize;
|
|
ac->bufrdavail -= copylen;
|
|
}
|
|
|
|
pthread_cond_signal(&ac->cond);
|
|
pthread_mutex_unlock(&ac->mutex);
|
|
|
|
return kAudioHardwareNoError;
|
|
}
|
|
|
|
static OSStatus RecIOProc(AudioDeviceID inDevice UNUSED,
|
|
const AudioTimeStamp *inNow UNUSED,
|
|
const AudioBufferList *inInputData,
|
|
const AudioTimeStamp *inInputTime UNUSED,
|
|
AudioBufferList *outOutputData UNUSED,
|
|
const AudioTimeStamp *inOutputTime UNUSED,
|
|
void *inClientData)
|
|
{
|
|
priv_t *ac = (priv_t *)((sox_format_t*)inClientData)->priv;
|
|
AudioBuffer const *buf;
|
|
size_t nfree, copylen, avail;
|
|
|
|
pthread_mutex_lock(&ac->mutex);
|
|
|
|
for(buf = inInputData->mBuffers;
|
|
buf != inInputData->mBuffers + inInputData->mNumberBuffers;
|
|
buf++){
|
|
|
|
if(buf->mData == NULL)
|
|
continue;
|
|
|
|
copylen = buf->mDataByteSize / sizeof(float);
|
|
nfree = ac->bufsize - ac->bufrdavail - 1;
|
|
if(nfree == 0)
|
|
lsx_warn("coreaudio: unhandled buffer overrun. Data discarded.");
|
|
|
|
if(copylen > nfree)
|
|
copylen = nfree;
|
|
|
|
avail = ac->bufsize - ac->bufwr;
|
|
if(copylen > avail){
|
|
memcpy(ac->buf + ac->bufwr, buf->mData, avail * sizeof(float));
|
|
memcpy(ac->buf, (float*)buf->mData + avail, (copylen - avail) * sizeof(float));
|
|
}else{
|
|
memcpy(ac->buf + ac->bufwr, buf->mData, copylen * sizeof(float));
|
|
}
|
|
|
|
ac->bufwr += copylen;
|
|
if(ac->bufwr >= ac->bufsize)
|
|
ac->bufwr -= ac->bufsize;
|
|
ac->bufrdavail += copylen;
|
|
}
|
|
|
|
pthread_cond_signal(&ac->cond);
|
|
pthread_mutex_unlock(&ac->mutex);
|
|
|
|
return kAudioHardwareNoError;
|
|
}
|
|
|
|
static int setup(sox_format_t *ft, int is_input)
|
|
{
|
|
priv_t *ac = (priv_t *)ft->priv;
|
|
OSStatus status;
|
|
UInt32 property_size;
|
|
struct AudioStreamBasicDescription stream_desc;
|
|
int32_t buf_size;
|
|
int rc;
|
|
|
|
if (strncmp(ft->filename, "default", (size_t)7) == 0)
|
|
{
|
|
property_size = sizeof(ac->adid);
|
|
if (is_input)
|
|
status = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultInputDevice, &property_size, &ac->adid);
|
|
else
|
|
status = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, &property_size, &ac->adid);
|
|
}
|
|
else
|
|
{
|
|
Boolean is_writable;
|
|
status = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices, &property_size, &is_writable);
|
|
|
|
if (status == noErr)
|
|
{
|
|
int device_count = property_size/sizeof(AudioDeviceID);
|
|
AudioDeviceID *devices;
|
|
|
|
devices = malloc(property_size);
|
|
status = AudioHardwareGetProperty(kAudioHardwarePropertyDevices, &property_size, devices);
|
|
|
|
if (status == noErr)
|
|
{
|
|
int i;
|
|
for (i = 0; i < device_count; i++)
|
|
{
|
|
char name[256];
|
|
status = AudioDeviceGetProperty(devices[i],0,false,kAudioDevicePropertyDeviceName,&property_size,&name);
|
|
|
|
lsx_report("Found Audio Device \"%s\"\n",name);
|
|
|
|
/* String returned from OS is truncated so only compare
|
|
* as much as returned.
|
|
*/
|
|
if (strncmp(name,ft->filename,strlen(name)) == 0)
|
|
{
|
|
ac->adid = devices[i];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
free(devices);
|
|
}
|
|
}
|
|
|
|
if (status || ac->adid == kAudioDeviceUnknown)
|
|
{
|
|
lsx_fail_errno(ft, SOX_EPERM, "can not open audio device");
|
|
return SOX_EOF;
|
|
}
|
|
|
|
/* Query device to get initial values */
|
|
property_size = sizeof(struct AudioStreamBasicDescription);
|
|
status = AudioDeviceGetProperty(ac->adid, 0, is_input,
|
|
kAudioDevicePropertyStreamFormat,
|
|
&property_size, &stream_desc);
|
|
if (status)
|
|
{
|
|
lsx_fail_errno(ft, SOX_EPERM, "can not get audio device properties");
|
|
return SOX_EOF;
|
|
}
|
|
|
|
if (!(stream_desc.mFormatFlags & kLinearPCMFormatFlagIsFloat))
|
|
{
|
|
lsx_fail_errno(ft, SOX_EPERM, "audio device does not accept floats");
|
|
return SOX_EOF;
|
|
}
|
|
|
|
/* OS X effectively only supports these values. */
|
|
ft->signal.channels = 2;
|
|
ft->signal.rate = 44100;
|
|
ft->encoding.bits_per_sample = 32;
|
|
|
|
/* TODO: My limited experience with hardware can only get floats working
|
|
* withh a fixed sample rate and stereo. I know that is a limitiation of
|
|
* audio device I have so this may not be standard operating orders.
|
|
* If some hardware supports setting sample rates and channel counts
|
|
* then should do that over resampling and mixing.
|
|
*/
|
|
#if 0
|
|
stream_desc.mSampleRate = ft->signal.rate;
|
|
stream_desc.mChannelsPerFrame = ft->signal.channels;
|
|
|
|
/* Write them back */
|
|
property_size = sizeof(struct AudioStreamBasicDescription);
|
|
status = AudioDeviceSetProperty(ac->adid, NULL, 0, is_input,
|
|
kAudioDevicePropertyStreamFormat,
|
|
property_size, &stream_desc);
|
|
if (status)
|
|
{
|
|
lsx_fail_errno(ft, SOX_EPERM, "can not set audio device properties");
|
|
return SOX_EOF;
|
|
}
|
|
|
|
/* Query device to see if it worked */
|
|
property_size = sizeof(struct AudioStreamBasicDescription);
|
|
status = AudioDeviceGetProperty(ac->adid, 0, is_input,
|
|
kAudioDevicePropertyStreamFormat,
|
|
&property_size, &stream_desc);
|
|
|
|
if (status)
|
|
{
|
|
lsx_fail_errno(ft, SOX_EPERM, "can not get audio device properties");
|
|
return SOX_EOF;
|
|
}
|
|
#endif
|
|
|
|
if (stream_desc.mChannelsPerFrame != ft->signal.channels)
|
|
{
|
|
lsx_debug("audio device did not accept %d channels. Use %d channels instead.", (int)ft->signal.channels,
|
|
(int)stream_desc.mChannelsPerFrame);
|
|
ft->signal.channels = stream_desc.mChannelsPerFrame;
|
|
}
|
|
|
|
if (stream_desc.mSampleRate != ft->signal.rate)
|
|
{
|
|
lsx_debug("audio device did not accept %d sample rate. Use %d instead.", (int)ft->signal.rate,
|
|
(int)stream_desc.mSampleRate);
|
|
ft->signal.rate = stream_desc.mSampleRate;
|
|
}
|
|
|
|
ac->bufsize = sox_globals.bufsiz / sizeof(sox_sample_t) * Buffactor;
|
|
ac->bufrd = 0;
|
|
ac->bufwr = 0;
|
|
ac->bufrdavail = 0;
|
|
ac->buf = lsx_malloc(ac->bufsize * sizeof(float));
|
|
|
|
buf_size = sox_globals.bufsiz / sizeof(sox_sample_t) * sizeof(float);
|
|
property_size = sizeof(buf_size);
|
|
status = AudioDeviceSetProperty(ac->adid, NULL, 0, is_input,
|
|
kAudioDevicePropertyBufferSize,
|
|
property_size, &buf_size);
|
|
|
|
rc = pthread_mutex_init(&ac->mutex, NULL);
|
|
if (rc)
|
|
{
|
|
lsx_fail_errno(ft, SOX_EPERM, "failed initializing mutex");
|
|
return SOX_EOF;
|
|
}
|
|
|
|
rc = pthread_cond_init(&ac->cond, NULL);
|
|
if (rc)
|
|
{
|
|
lsx_fail_errno(ft, SOX_EPERM, "failed initializing condition");
|
|
return SOX_EOF;
|
|
}
|
|
|
|
ac->device_started = 0;
|
|
|
|
/* Registers callback with the device without activating it. */
|
|
if (is_input)
|
|
status = AudioDeviceAddIOProc(ac->adid, RecIOProc, (void *)ft);
|
|
else
|
|
status = AudioDeviceAddIOProc(ac->adid, PlaybackIOProc, (void *)ft);
|
|
|
|
return SOX_SUCCESS;
|
|
}
|
|
|
|
static int startread(sox_format_t *ft)
|
|
{
|
|
return setup(ft, 1);
|
|
}
|
|
|
|
static size_t read_samples(sox_format_t *ft, sox_sample_t *buf, size_t nsamp)
|
|
{
|
|
priv_t *ac = (priv_t *)ft->priv;
|
|
size_t len;
|
|
SOX_SAMPLE_LOCALS;
|
|
|
|
if (!ac->device_started) {
|
|
AudioDeviceStart(ac->adid, RecIOProc);
|
|
ac->device_started = 1;
|
|
}
|
|
|
|
pthread_mutex_lock(&ac->mutex);
|
|
|
|
/* Wait until input buffer has been filled by device driver */
|
|
while (ac->bufrdavail == 0)
|
|
pthread_cond_wait(&ac->cond, &ac->mutex);
|
|
|
|
len = 0;
|
|
while(len < nsamp && ac->bufrdavail > 0){
|
|
buf[len] = SOX_FLOAT_32BIT_TO_SAMPLE(ac->buf[ac->bufrd], ft->clips);
|
|
len++;
|
|
ac->bufrd++;
|
|
if(ac->bufrd == ac->bufsize)
|
|
ac->bufrd = 0;
|
|
ac->bufrdavail--;
|
|
}
|
|
|
|
pthread_mutex_unlock(&ac->mutex);
|
|
|
|
return len;
|
|
}
|
|
|
|
static int stopread(sox_format_t * ft)
|
|
{
|
|
priv_t *ac = (priv_t *)ft->priv;
|
|
|
|
AudioDeviceStop(ac->adid, RecIOProc);
|
|
AudioDeviceRemoveIOProc(ac->adid, RecIOProc);
|
|
pthread_cond_destroy(&ac->cond);
|
|
pthread_mutex_destroy(&ac->mutex);
|
|
free(ac->buf);
|
|
|
|
return SOX_SUCCESS;
|
|
}
|
|
|
|
static int startwrite(sox_format_t * ft)
|
|
{
|
|
return setup(ft, 0);
|
|
}
|
|
|
|
static size_t write_samples(sox_format_t *ft, const sox_sample_t *buf, size_t nsamp)
|
|
{
|
|
priv_t *ac = (priv_t *)ft->priv;
|
|
size_t i;
|
|
|
|
SOX_SAMPLE_LOCALS;
|
|
|
|
pthread_mutex_lock(&ac->mutex);
|
|
|
|
/* Wait to start until mutex is locked to help prevent callback
|
|
* getting zero samples.
|
|
*/
|
|
if(!ac->device_started){
|
|
if(AudioDeviceStart(ac->adid, PlaybackIOProc)){
|
|
pthread_mutex_unlock(&ac->mutex);
|
|
return SOX_EOF;
|
|
}
|
|
ac->device_started = 1;
|
|
}
|
|
|
|
/* globals.bufsize is in samples
|
|
* buf_offset is in bytes
|
|
* buf_size is in bytes
|
|
*/
|
|
for(i = 0; i < nsamp; i++){
|
|
while(ac->bufrdavail == ac->bufsize - 1)
|
|
pthread_cond_wait(&ac->cond, &ac->mutex);
|
|
|
|
ac->buf[ac->bufwr] = SOX_SAMPLE_TO_FLOAT_32BIT(buf[i], ft->clips);
|
|
ac->bufwr++;
|
|
if(ac->bufwr == ac->bufsize)
|
|
ac->bufwr = 0;
|
|
ac->bufrdavail++;
|
|
}
|
|
|
|
pthread_mutex_unlock(&ac->mutex);
|
|
return nsamp;
|
|
}
|
|
|
|
|
|
static int stopwrite(sox_format_t * ft)
|
|
{
|
|
priv_t *ac = (priv_t *)ft->priv;
|
|
|
|
if(ac->device_started){
|
|
pthread_mutex_lock(&ac->mutex);
|
|
|
|
while (ac->bufrdavail > 0)
|
|
pthread_cond_wait(&ac->cond, &ac->mutex);
|
|
|
|
pthread_mutex_unlock(&ac->mutex);
|
|
|
|
AudioDeviceStop(ac->adid, PlaybackIOProc);
|
|
}
|
|
|
|
AudioDeviceRemoveIOProc(ac->adid, PlaybackIOProc);
|
|
pthread_cond_destroy(&ac->cond);
|
|
pthread_mutex_destroy(&ac->mutex);
|
|
free(ac->buf);
|
|
|
|
return SOX_SUCCESS;
|
|
}
|
|
|
|
LSX_FORMAT_HANDLER(coreaudio)
|
|
{
|
|
static char const *const names[] = { "coreaudio", NULL };
|
|
static unsigned const write_encodings[] = {
|
|
SOX_ENCODING_FLOAT, 32, 0,
|
|
0};
|
|
static sox_format_handler_t const handler = {SOX_LIB_VERSION_CODE,
|
|
"Mac AudioCore device driver",
|
|
names, SOX_FILE_DEVICE | SOX_FILE_NOSTDIO,
|
|
startread, read_samples, stopread,
|
|
startwrite, write_samples, stopwrite,
|
|
NULL, write_encodings, NULL, sizeof(priv_t)
|
|
};
|
|
return &handler;
|
|
}
|
|
|