mpv/libao2/ao_macosx.c

449 lines
13 KiB
C
Raw Normal View History

/*
*
* ao_macosx.c
*
* Original Copyright (C) Timothy J. Wood - Aug 2000
*
* This file is part of libao, a cross-platform library. See
* README for a history of this source code.
*
* libao 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, or (at your option)
* any later version.
*
* libao 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 libao; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/*
* The MacOS X CoreAudio framework doesn't mesh as simply as some
* simpler frameworks do. This is due to the fact that CoreAudio pulls
* audio samples rather than having them pushed at it (which is nice
* when you are wanting to do good buffering of audio).
*/
/* Change log:
*
* 14/5-2003: Ported to MPlayer libao2 by Dan Christiansen
*
* AC-3 and MPEG audio passthrough is possible, but I don't have
* access to a sound card that supports it.
*/
#include <CoreServices/CoreServices.h>
#include <AudioUnit/AudioUnit.h>
#include <AudioToolbox/AudioToolbox.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <inttypes.h>
#include <pthread.h>
#include "config.h"
#include "mp_msg.h"
#include "audio_out.h"
#include "audio_out_internal.h"
#include "libaf/af_format.h"
static ao_info_t info =
{
"Darwin/Mac OS X native audio output",
"macosx",
"Timothy J. Wood & Dan Christiansen & Chris Roccati",
""
};
LIBAO_EXTERN(macosx)
/* Prefix for all mp_msg() calls */
#define ao_msg(a, b, c...) mp_msg(a, b, "AO: [macosx] " c)
/* This is large, but best (maybe it should be even larger).
* CoreAudio supposedly has an internal latency in the order of 2ms */
#define NUM_BUFS 32
typedef struct ao_macosx_s
{
/* AudioUnit */
AudioUnit theOutputUnit;
int packetSize;
int paused;
/* Ring-buffer */
/* does not need explicit synchronization, but needs to allocate
* (num_chunks + 1) * chunk_size memory to store num_chunks * chunk_size
* data */
unsigned char *buffer;
unsigned int buffer_len; ///< must always be (num_chunks + 1) * chunk_size
unsigned int num_chunks;
unsigned int chunk_size;
unsigned int buf_read_pos;
unsigned int buf_write_pos;
} ao_macosx_t;
static ao_macosx_t *ao = NULL;
/**
* \brief return number of free bytes in the buffer
* may only be called by mplayer's thread
* \return minimum number of free bytes in buffer, value may change between
* two immediately following calls, and the real number of free bytes
* might actually be larger!
*/
static int buf_free(void) {
int free = ao->buf_read_pos - ao->buf_write_pos - ao->chunk_size;
if (free < 0) free += ao->buffer_len;
return free;
}
/**
* \brief return number of buffered bytes
* may only be called by playback thread
* \return minimum number of buffered bytes, value may change between
* two immediately following calls, and the real number of buffered bytes
* might actually be larger!
*/
static int buf_used(void) {
int used = ao->buf_write_pos - ao->buf_read_pos;
if (used < 0) used += ao->buffer_len;
return used;
}
/**
* \brief add data to ringbuffer
*/
static int write_buffer(unsigned char* data, int len){
int first_len = ao->buffer_len - ao->buf_write_pos;
int free = buf_free();
if (len > free) len = free;
if (first_len > len) first_len = len;
// till end of buffer
memcpy (&ao->buffer[ao->buf_write_pos], data, first_len);
if (len > first_len) { // we have to wrap around
// remaining part from beginning of buffer
memcpy (ao->buffer, &data[first_len], len - first_len);
}
ao->buf_write_pos = (ao->buf_write_pos + len) % ao->buffer_len;
return len;
}
/**
* \brief remove data from ringbuffer
*/
static int read_buffer(unsigned char* data,int len){
int first_len = ao->buffer_len - ao->buf_read_pos;
int buffered = buf_used();
if (len > buffered) len = buffered;
if (first_len > len) first_len = len;
// till end of buffer
memcpy (data, &ao->buffer[ao->buf_read_pos], first_len);
if (len > first_len) { // we have to wrap around
// remaining part from beginning of buffer
memcpy (&data[first_len], ao->buffer, len - first_len);
}
ao->buf_read_pos = (ao->buf_read_pos + len) % ao->buffer_len;
return len;
}
OSStatus theRenderProc(void *inRefCon, AudioUnitRenderActionFlags *inActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumFrames, AudioBufferList *ioData)
{
int amt=buf_used();
int req=(inNumFrames)*ao->packetSize;
if(amt>req)
amt=req;
if(amt)
read_buffer((unsigned char *)ioData->mBuffers[0].mData, amt);
else audio_pause();
ioData->mBuffers[0].mDataByteSize = amt;
return noErr;
}
static int control(int cmd,void *arg){
ao_control_vol_t *control_vol;
OSStatus err;
Float32 vol;
switch (cmd) {
case AOCONTROL_GET_VOLUME:
control_vol = (ao_control_vol_t*)arg;
err = AudioUnitGetParameter(ao->theOutputUnit, kHALOutputParam_Volume, kAudioUnitScope_Global, 0, &vol);
if(err==0) {
// printf("GET VOL=%f\n", vol);
control_vol->left=control_vol->right=vol*100.0/4.0;
return CONTROL_TRUE;
}
else {
return CONTROL_FALSE;
}
case AOCONTROL_SET_VOLUME:
control_vol = (ao_control_vol_t*)arg;
vol=(control_vol->left+control_vol->right)*4.0/200.0;
err = AudioUnitSetParameter(ao->theOutputUnit, kHALOutputParam_Volume, kAudioUnitScope_Global, 0, vol, 0);
if(err==0) {
// printf("SET VOL=%f\n", vol);
return CONTROL_TRUE;
}
else {
return CONTROL_FALSE;
}
/* Everything is currently unimplemented */
default:
return CONTROL_FALSE;
}
}
static void print_format(const char* str,AudioStreamBasicDescription *f){
uint32_t flags=(uint32_t) f->mFormatFlags;
ao_msg(MSGT_AO,MSGL_V, "%s %7.1fHz %dbit [%c%c%c%c] %s %s %s%s%s%s\n",
str, f->mSampleRate, f->mBitsPerChannel,
(int)(f->mFormatID & 0xff000000) >> 24,
(int)(f->mFormatID & 0x00ff0000) >> 16,
(int)(f->mFormatID & 0x0000ff00) >> 8,
(int)(f->mFormatID & 0x000000ff) >> 0,
(flags&kAudioFormatFlagIsFloat) ? "float" : "int",
(flags&kAudioFormatFlagIsBigEndian) ? "BE" : "LE",
(flags&kAudioFormatFlagIsSignedInteger) ? "S" : "U",
(flags&kAudioFormatFlagIsPacked) ? " packed" : "",
(flags&kAudioFormatFlagIsAlignedHigh) ? " aligned" : "",
(flags&kAudioFormatFlagIsNonInterleaved) ? " ni" : "" );
ao_msg(MSGT_AO,MSGL_DBG2, "%5d mBytesPerPacket\n",
(int)f->mBytesPerPacket);
ao_msg(MSGT_AO,MSGL_DBG2, "%5d mFramesPerPacket\n",
(int)f->mFramesPerPacket);
ao_msg(MSGT_AO,MSGL_DBG2, "%5d mBytesPerFrame\n",
(int)f->mBytesPerFrame);
ao_msg(MSGT_AO,MSGL_DBG2, "%5d mChannelsPerFrame\n",
(int)f->mChannelsPerFrame);
}
static int init(int rate,int channels,int format,int flags)
{
AudioStreamBasicDescription inDesc;
ComponentDescription desc;
Component comp;
AURenderCallbackStruct renderCallback;
OSStatus err;
UInt32 size, maxFrames;
int aoIsCreated = ao != NULL;
if (!aoIsCreated) ao = malloc(sizeof(ao_macosx_t));
// Build Description for the input format
inDesc.mSampleRate=rate;
inDesc.mFormatID=kAudioFormatLinearPCM;
inDesc.mChannelsPerFrame=channels;
switch(format&AF_FORMAT_BITS_MASK){
case AF_FORMAT_8BIT:
inDesc.mBitsPerChannel=8;
break;
case AF_FORMAT_16BIT:
inDesc.mBitsPerChannel=16;
break;
case AF_FORMAT_24BIT:
inDesc.mBitsPerChannel=24;
break;
case AF_FORMAT_32BIT:
inDesc.mBitsPerChannel=32;
break;
default:
ao_msg(MSGT_AO, MSGL_WARN, "Unsupported format (0x%08x)\n", format);
return CONTROL_FALSE;
break;
}
if((format&AF_FORMAT_POINT_MASK)==AF_FORMAT_F) {
// float
inDesc.mFormatFlags = kAudioFormatFlagIsFloat|kAudioFormatFlagIsPacked;
}
else if((format&AF_FORMAT_SIGN_MASK)==AF_FORMAT_SI) {
// signed int
inDesc.mFormatFlags = kAudioFormatFlagIsSignedInteger|kAudioFormatFlagIsPacked;
}
else {
// unsigned int
inDesc.mFormatFlags = kAudioFormatFlagIsPacked;
}
if((format&AF_FORMAT_END_MASK)==AF_FORMAT_BE)
inDesc.mFormatFlags |= kAudioFormatFlagIsBigEndian;
inDesc.mFramesPerPacket = 1;
ao->packetSize = inDesc.mBytesPerPacket = inDesc.mBytesPerFrame = inDesc.mFramesPerPacket*channels*(inDesc.mBitsPerChannel/8);
print_format("source: ",&inDesc);
if (!aoIsCreated) {
desc.componentType = kAudioUnitType_Output;
desc.componentSubType = kAudioUnitSubType_DefaultOutput;
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
desc.componentFlags = 0;
desc.componentFlagsMask = 0;
comp = FindNextComponent(NULL, &desc); //Finds an component that meets the desc spec's
if (comp == NULL) {
ao_msg(MSGT_AO, MSGL_WARN, "Unable to find Output Unit component\n");
return CONTROL_FALSE;
}
err = OpenAComponent(comp, &(ao->theOutputUnit)); //gains access to the services provided by the component
if (err) {
ao_msg(MSGT_AO, MSGL_WARN, "Unable to open Output Unit component (err=%d)\n", err);
return CONTROL_FALSE;
}
// Initialize AudioUnit
err = AudioUnitInitialize(ao->theOutputUnit);
if (err) {
ao_msg(MSGT_AO, MSGL_WARN, "Unable to initialize Output Unit component (err=%d)\n", err);
return CONTROL_FALSE;
}
}
size = sizeof(AudioStreamBasicDescription);
err = AudioUnitSetProperty(ao->theOutputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &inDesc, size);
if (err) {
ao_msg(MSGT_AO, MSGL_WARN, "Unable to set the input format (err=%d)\n", err);
return CONTROL_FALSE;
}
size = sizeof(UInt32);
err = AudioUnitGetProperty(ao->theOutputUnit, kAudioDevicePropertyBufferSize, kAudioUnitScope_Input, 0, &maxFrames, &size);
if (err)
{
ao_msg(MSGT_AO,MSGL_WARN, "AudioUnitGetProperty returned %d when getting kAudioDevicePropertyBufferSize\n", (int)err);
return CONTROL_FALSE;
}
ao->chunk_size = maxFrames;//*inDesc.mBytesPerFrame;
ao_data.samplerate = inDesc.mSampleRate;
ao_data.channels = inDesc.mChannelsPerFrame;
ao_data.bps = ao_data.samplerate * inDesc.mBytesPerFrame;
ao_data.outburst = ao->chunk_size;
ao_data.buffersize = ao_data.bps;
ao->num_chunks = (ao_data.bps+ao->chunk_size-1)/ao->chunk_size;
ao->buffer_len = (ao->num_chunks + 1) * ao->chunk_size;
ao->buffer = aoIsCreated ? realloc(ao->buffer,(ao->num_chunks + 1)*ao->chunk_size)
: calloc(ao->num_chunks + 1, ao->chunk_size);
ao_msg(MSGT_AO,MSGL_V, "using %5d chunks of %d bytes (buffer len %d bytes)\n", (int)ao->num_chunks, (int)ao->chunk_size, (int)ao->buffer_len);
renderCallback.inputProc = theRenderProc;
renderCallback.inputProcRefCon = 0;
err = AudioUnitSetProperty(ao->theOutputUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &renderCallback, sizeof(AURenderCallbackStruct));
if (err) {
ao_msg(MSGT_AO, MSGL_WARN, "Unable to set the render callback (err=%d)\n", err);
return CONTROL_FALSE;
}
reset();
return CONTROL_OK;
}
static int play(void* output_samples,int num_bytes,int flags)
{
int wrote=write_buffer(output_samples, num_bytes);
audio_resume();
return wrote;
}
/* set variables and buffer to initial state */
static void reset(void)
{
audio_pause();
/* reset ring-buffer state */
ao->buf_read_pos=0;
ao->buf_write_pos=0;
return;
}
/* return available space */
static int get_space(void)
{
return buf_free();
}
/* return delay until audio is played */
static float get_delay(void)
{
int buffered = ao->buffer_len - ao->chunk_size - buf_free(); // could be less
// inaccurate, should also contain the data buffered e.g. by the OS
return (float)(buffered)/(float)ao_data.bps;
}
/* unload plugin and deregister from coreaudio */
static void uninit(int immed)
{
if (!immed) {
long long timeleft=(1000000LL*buf_used())/ao_data.bps;
ao_msg(MSGT_AO,MSGL_DBG2, "%d bytes left @%d bps (%ld usec)\n", buf_used(), ao_data.bps, (int)timeleft);
usec_sleep((int)timeleft);
}
AudioOutputUnitStop(ao->theOutputUnit);
AudioUnitUninitialize(ao->theOutputUnit);
CloseComponent(ao->theOutputUnit);
free(ao->buffer);
free(ao);
ao = NULL;
}
/* stop playing, keep buffers (for pause) */
static void audio_pause(void)
{
OSErr status=noErr;
/* stop callback */
status=AudioOutputUnitStop(ao->theOutputUnit);
if (status)
ao_msg(MSGT_AO,MSGL_WARN, "AudioOutputUnitStop returned %d\n",
(int)status);
ao->paused=1;
}
/* resume playing, after audio_pause() */
static void audio_resume(void)
{
if(ao->paused) {
OSErr status=noErr;
/* start callback */
status=AudioOutputUnitStart(ao->theOutputUnit);
if (status)
ao_msg(MSGT_AO,MSGL_WARN, "AudioOutputUnitStart returned %d\n",
(int)status);
ao->paused=0;
}
}