mirror of
https://github.com/lloeki/coreaudio_example.git
synced 2025-12-06 03:04:38 +01:00
archive
This commit is contained in:
commit
f109052d8b
4 changed files with 276 additions and 0 deletions
2
Makefile
Normal file
2
Makefile
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
all:
|
||||
clang++ -std=c++11 -stdlib=libc++ coreaudio_example.cc
|
||||
3
README.md
Normal file
3
README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
CoreAudio utility functions, public domain.
|
||||
|
||||
http://kaniini.dereferenced.org/2014/08/31/CoreAudio-sucks.html
|
||||
238
coreaudio_example.cc
Normal file
238
coreaudio_example.cc
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
/*
|
||||
CoreAudio, or how to actually use the worst documented audio API in history.
|
||||
|
||||
Recently I started working on a CoreAudio plugin for audacious, to replace the
|
||||
old one which was removed in Audacious 3.2, since the mac port was abandoned
|
||||
due to the fact that Gtk+ is horrible on mac. Instead of updating the old
|
||||
CoreAudio plugin which was very limited and consisted of bad code ported from
|
||||
the XMMS days, I decided to start from scratch, using the simple SDL output
|
||||
plugin as a model.
|
||||
|
||||
The API itself is okay, but the documentation is misleading. For example, they
|
||||
encourage you to set up an Audio Unit Graph for simple audio playback with
|
||||
audio control. At least on OS X, this isn't really necessary (thankfully).
|
||||
Since this isn't necessary, I will show you how to do it the easy way, since
|
||||
everyone seems to like to over-complicate things.
|
||||
|
||||
These examples assume you have a 'pump thread' which is pumping audio to a
|
||||
circular buffer. Implementing that is not covered here, but isn't really very
|
||||
hard to do.
|
||||
*/
|
||||
|
||||
/*
|
||||
This is the API we will be implementing, it is pretty straight-forward. There
|
||||
are 6 functions which are provided:
|
||||
|
||||
init: start up the audio system
|
||||
cleanup: shut down the audio system
|
||||
set_volume: set the volume
|
||||
open_audio: set up a stream for playback
|
||||
close_audio: shuts down the playback code
|
||||
pause_audio: pauses the playback code
|
||||
|
||||
The API will handle format conversion between your playback code's preferred
|
||||
format type and CoreAudio's native format -- float32, non-interleaved linear
|
||||
pcm. We will do this using a lookup table based on the enum...
|
||||
*/
|
||||
|
||||
/* CoreAudio utility functions, public domain.
|
||||
http://kaniini.dereferenced.org/2014/08/31/CoreAudio-sucks.html */
|
||||
|
||||
#include "coreaudio_example.h"
|
||||
|
||||
namespace coreaudio_example {
|
||||
|
||||
struct CoreAudioFormatDescriptionMap {
|
||||
enum format_type type;
|
||||
int bits_per_sample;
|
||||
int bytes_per_sample;
|
||||
unsigned int flags;
|
||||
};
|
||||
static struct CoreAudioFormatDescriptionMap format_map[] = {
|
||||
{FMT_S16_LE, 16, sizeof (int16_t), kAudioFormatFlagIsSignedInteger},
|
||||
{FMT_S16_BE, 16, sizeof (int16_t), kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsBigEndian},
|
||||
{FMT_S32_LE, 32, sizeof (int32_t), kAudioFormatFlagIsSignedInteger},
|
||||
{FMT_S32_BE, 32, sizeof (int32_t), kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsBigEndian},
|
||||
{FMT_FLOAT, 32, sizeof (float), kAudioFormatFlagIsFloat},
|
||||
};
|
||||
|
||||
/*
|
||||
This is our lookup table which handles looking up the format specification
|
||||
attributes. There are a few members in each entry which are interesting:
|
||||
|
||||
- type: this is our format type (i.e. FMT_S16_LE) which we will be looking up
|
||||
- bits_per_sample: this is the number of bits of data that should be included
|
||||
in a sample. CoreAudio takes the lower bits of data, not the higher bits of
|
||||
data. So if you were to implement 24-bit PCM support, you would pack the data
|
||||
into a series of 32-bit integers and tell CoreAudio to use 24 bits of each
|
||||
sample for PCM data.
|
||||
- bytes_per_sample: this is the size in bytes of the type which contains each
|
||||
sample.
|
||||
- flags: This is a bitfield of many flags, the ones mentioned here are:
|
||||
- kAudioFormatIsSignedInteger: The audio format is a signed integer. If this is
|
||||
not set, CoreAudio assumes it's unsigned.
|
||||
- kAudioFormatIsBigEndian: The audio format is in big-endian notation. If this
|
||||
is not set, CoreAudio assumes it's little-endian.
|
||||
- kAudioFormatIsFloat: The audio format is in 32-bit floating point format.
|
||||
|
||||
We now continue with actually initializing our output unit so we can use it.
|
||||
*/
|
||||
|
||||
static AudioComponent output_comp;
|
||||
static AudioComponentInstance output_instance;
|
||||
|
||||
bool init (void)
|
||||
{
|
||||
/* open the default audio device */
|
||||
AudioComponentDescription desc;
|
||||
desc.componentType = kAudioUnitType_Output;
|
||||
desc.componentSubType = kAudioUnitSubType_DefaultOutput;
|
||||
desc.componentFlags = 0;
|
||||
desc.componentFlagsMask = 0;
|
||||
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
|
||||
|
||||
output_comp = AudioComponentFindNext (nullptr, & desc);
|
||||
if (! output_comp)
|
||||
{
|
||||
fprintf (stderr, "Failed to open default audio device.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (AudioComponentInstanceNew (output_comp, & output_instance))
|
||||
{
|
||||
fprintf (stderr, "Failed to open default audio device.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void cleanup (void)
|
||||
{
|
||||
AudioUnitInstanceDispose (output_instance);
|
||||
}
|
||||
|
||||
/*
|
||||
The init () and cleanup () routines handle bringing up CoreAudio in the app.
|
||||
This gives you an output unit you can send data to using a callback. Now we
|
||||
should actually set up the unit for playback...
|
||||
*/
|
||||
|
||||
bool open_audio (int format, int rate, int chan, AURenderCallbackStruct * callback)
|
||||
{
|
||||
struct CoreAudioFormatDescriptionMap * m = nullptr;
|
||||
|
||||
for (struct CoreAudioFormatDescriptionMap it : format_map)
|
||||
{
|
||||
if (it.type == format)
|
||||
{
|
||||
m = & it;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (! m)
|
||||
{
|
||||
fprintf (stderr, "The requested audio format %d is unsupported.\n", format);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (AudioUnitInitialize (output_instance))
|
||||
{
|
||||
fprintf (stderr, "Unable to initialize audio unit instance\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
AudioStreamBasicDescription streamFormat;
|
||||
streamFormat.mSampleRate = rate;
|
||||
streamFormat.mFormatID = kAudioFormatLinearPCM;
|
||||
streamFormat.mFormatFlags = m->mFormatFlags;
|
||||
streamFormat.mFramesPerPacket = 1;
|
||||
streamFormat.mChannelsPerFrame = chan;
|
||||
streamFormat.mBitsPerChannel = m->mBitsPerChannel;
|
||||
streamFormat.mBytesPerPacket = chan * buffer_bytes_per_channel;
|
||||
streamFormat.mBytesPerFrame = chan * buffer_bytes_per_channel;
|
||||
|
||||
printf ("Stream format:\n");
|
||||
printf (" Channels: %d\n", streamFormat.mChannelsPerFrame);
|
||||
printf (" Sample rate: %f\n", streamFormat.mSampleRate);
|
||||
printf (" Bits per channel: %d\n", streamFormat.mBitsPerChannel);
|
||||
printf (" Bytes per frame: %d\n", streamFormat.mBytesPerFrame);
|
||||
|
||||
if (AudioUnitSetProperty (output_instance, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &streamFormat, sizeof(streamFormat)))
|
||||
{
|
||||
fprintf (stderr, "Failed to set audio unit input property.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (AudioUnitSetProperty (output_instance, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, callback, sizeof (AURenderCallbackStruct)))
|
||||
{
|
||||
fprintf (stderr, "Unable to attach an IOProc to the selected audio unit.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (AudioOutputUnitStart (output_instance))
|
||||
{
|
||||
fprintf ("Unable to start audio unit.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void close_audio (void)
|
||||
{
|
||||
AudioOutputUnitStop (output_instance);
|
||||
}
|
||||
|
||||
/*
|
||||
At this point you should have full playback with callback to your callback and
|
||||
shutdown. Now to implement volume control...
|
||||
*/
|
||||
|
||||
/* value is 0..100, the actual applied volume is based on a natual decibel scale. */
|
||||
void set_volume (int value)
|
||||
{
|
||||
float factor = (value == 0) ? 0.0 : powf (10, (float) VOLUME_RANGE * (value - 100) / 100 / 20);
|
||||
|
||||
/* lots of pain concerning controlling application volume can be avoided
|
||||
* with this one neat trick... */
|
||||
AudioUnitSetParameter (output_instance, kHALOutputParam_Volume, kAudioUnitScope_Global, 0, factor, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
Two things here:
|
||||
|
||||
Apple says you should set up an AUGraph for doing something as simple as controlling output volume. I say that is unnecessary. There is a lot of misinformation here as well, that kHALOutputParam_Volume sets the system volume; it doesn't. It sets the individual output volume on the sound server, coreaudiod.
|
||||
|
||||
The reason for the powf () and the scary math is to give a logarithmic scale for tapering the volume down, similar to what would be observed in an actual stereo system. If you don't want this, just do float factor = value / 100.0.
|
||||
|
||||
Now to handle pausing...
|
||||
*/
|
||||
|
||||
void pause_audio (bool paused)
|
||||
{
|
||||
if (paused)
|
||||
AudioOutputUnitStop (output_instance);
|
||||
else
|
||||
{
|
||||
if (AudioOutputUnitStart (output_instance))
|
||||
{
|
||||
fprintf (stderr, "Unable to restart audio unit after pausing.\n");
|
||||
close_audio ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} /* namespace coreaudio_example */
|
||||
|
||||
/*
|
||||
That is all there is to the actual CoreAudio lowlevel operations. The rest is
|
||||
up to the reader. In the callback function you just copy your buffer data to
|
||||
ioData->mBuffers[0].mData. The amount of data requested is at
|
||||
ioData->mBuffers[0].mDataByteSize.
|
||||
|
||||
Hopefully this documentation is useful to somebody else, I mostly wrote it for
|
||||
my own reference. Usually I get to just deal with FMOD which is much more
|
||||
pleasant (ha ha).
|
||||
*/
|
||||
33
coreaudio_example.h
Normal file
33
coreaudio_example.h
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
/* Header file for example code.
|
||||
We include CoreAudio and AudioUnit framework headers directly. */
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <pthread.h>
|
||||
|
||||
#include <CoreAudio/CoreAudio.h>
|
||||
#include <AudioUnit/AudioUnit.h>
|
||||
|
||||
namespace coreaudio_example {
|
||||
|
||||
/* these format constants are based on the ones we use in audacious.
|
||||
S16_LE means signed 16-bit pcm, little-endian. */
|
||||
enum format_type {
|
||||
FMT_S16_LE,
|
||||
FMT_S16_BE,
|
||||
FMT_S32_LE,
|
||||
FMT_S32_BE,
|
||||
FMT_FLOAT
|
||||
};
|
||||
|
||||
bool init (void);
|
||||
void cleanup (void);
|
||||
void set_volume (int value);
|
||||
bool open_audio (enum format_type format, int rate, int chan,
|
||||
AURenderCallbackStruct * callback);
|
||||
void close_audio (void);
|
||||
void pause_audio (bool paused);
|
||||
|
||||
#define VOLUME_RANGE (40) /* decibels */
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue