ecore_audio: integrate Apple's CoreAudio to play sounds on OS X.

Summary:
Ecore_Audio now supports Apple's CoreAudio to play sounds read by libsndfile.
edje_multisense integrates this new feature to enable PLAY_SAMPLE on OS X.

Test Plan:
Compiles, links and installs fine on OS X.
Run terminology and elementary_test to hear sound played on user input.

Reviewers: raster, naguirre, cedric

Reviewed By: cedric

Subscribers: plamot, cedric

Differential Revision: https://phab.enlightenment.org/D2295

Signed-off-by: Cedric BAIL <cedric@osg.samsung.com>
This commit is contained in:
Jean Guyomarc'h 2015-04-15 16:53:39 +02:00 committed by Cedric BAIL
parent a53c52dee0
commit 62e29b39f4
9 changed files with 538 additions and 9 deletions

View File

@ -3208,6 +3208,54 @@ AC_ARG_ENABLE([pulseaudio],
],
[want_pulseaudio="yes"])
if test "x${have_darwin}" = "xyes"; then
want_pulseaudio="no"
want_alsa="no"
want_coreaudio="yes"
else
want_coreaudio="no"
fi
# CoreAudio flags
if test "x${want_coreaudio}" = "xyes"; then
coreaudio_ldflags=""
have_coreaudio="no"
LIBS_save="$LIBS"
LIBS="$LIBS -framework CoreAudio"
AC_LINK_IFELSE(
[AC_LANG_PROGRAM(
[[
#include <CoreAudio/CoreAudio.h>
]],
[[
UInt32 size;
AudioDeviceID dev_id;
AudioObjectPropertyAddress prop = {
kAudioHardwarePropertyDefaultOutputDevice,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
size = sizeof(AudioDeviceID);
AudioObjectGetPropertyData(kAudioObjectSystemObject, &prop, 0, NULL,
&size, &dev_id);
]])],
[
have_coreaudio="yes"
coreaudio_ldflags="-framework CoreAudio"
],
[have_coreaudio="no"])
LIBS="$LIBS_save"
AC_MSG_CHECKING([whether Apple CoreAudio framework is supported])
AC_MSG_RESULT([${have_coreaudio}])
fi
AC_SUBST(coreaudio_ldflags)
if test "x${have_coreaudio}" = "xyes"; then
AC_DEFINE([HAVE_COREAUDIO], [1], [CoreAudio support enabled])
else
AC_DEFINE([HAVE_COREAUDIO], [0], [CoreAudio support disabled])
fi
### Default values
@ -3234,6 +3282,7 @@ EFL_EVAL_PKGS([ECORE_AUDIO])
EFL_ADD_FEATURE([ECORE_AUDIO], [alsa])
EFL_ADD_FEATURE([ECORE_AUDIO], [pulseaudio])
EFL_ADD_FEATURE([ECORE_AUDIO], [sndfile])
EFL_ADD_FEATURE([ECORE_AUDIO], [coreaudio])
### Checks for header files
@ -3250,6 +3299,7 @@ EFL_ADD_FEATURE([ECORE_AUDIO], [sndfile])
EFL_LIB_END_OPTIONAL([Ecore_Audio])
AM_CONDITIONAL([HAVE_ECORE_AUDIO_PULSE], [test "x${want_pulseaudio}" = "xyes"])
AM_CONDITIONAL([HAVE_ECORE_AUDIO_SNDFILE], [test "x${want_sndfile}" = "xyes"])
AM_CONDITIONAL([HAVE_ECORE_AUDIO_CORE_AUDIO], [test "x${want_coreaudio}" = "xyes"])
#### End of Ecore_Audio
@ -4117,7 +4167,7 @@ EFL_LIB_START([Edje])
### Additional options to configure
### Default values
want_multisense="${want_pulseaudio}"
AC_ARG_ENABLE([multisense],
[AS_HELP_STRING([--enable-multisense],[Enable multisense support. @<:@default=enabled@:>@])],
[
@ -4128,7 +4178,13 @@ AC_ARG_ENABLE([multisense],
CFOPT_WARNING="xyes"
fi
],
[want_multisense="${want_pulseaudio}"])
[
if test "x${want_pulseaudio}" = "xyes" -o "x${want_coreaudio}" = "xyes"; then
want_multisense="yes"
else
want_multisense="no"
fi
])
# TODO: should we keep or remove these?
want_edje_program_cache="no"
@ -4960,11 +5016,12 @@ if test -n "$CFOPT_WARNING"; then
echo "Reconsider disabling audio."
echo "_____________________________________________________________________"
fi
if test "x${want_pulseaudio}" = "xno"; then
if test "x${have_darwin}" = "xno" -a "x${want_pulseaudio}" = "xno"; then
echo "_____________________________________________________________________"
echo "The only audio output method supported by Ecore right now is via"
echo "Pulseaudio. You have disabled that and likely have broken a whole"
echo "bunch of things in the process. Reconsider your configure options."
echo "The only audio output method supported by Ecore right now on your"
echo "system is via Pulseaudio. You have disabled that and likely have"
echo "broken a whole bunch of things in the process. Reconsider your"
echo "configure options."
echo "_____________________________________________________________________"
fi
if test "x${want_xinput2}" = "xno"; then
@ -5143,3 +5200,4 @@ if test "x${efl_deprecated_option}" = "xyes"; then
echo ""
echo "#-------------------------------------------------------------------#"
fi

View File

@ -8,9 +8,17 @@ ecore_audio_eolian_files = \
lib/ecore_audio/ecore_audio_out.eo \
lib/ecore_audio/ecore_audio_in_sndfile.eo \
lib/ecore_audio/ecore_audio_out_sndfile.eo \
lib/ecore_audio/ecore_audio_out_pulse.eo \
lib/ecore_audio/ecore_audio_in_tone.eo
if HAVE_ECORE_AUDIO_PULSE
ecore_audio_eolian_files += lib/ecore_audio/ecore_audio_out_pulse.eo
endif
if HAVE_ECORE_AUDIO_CORE_AUDIO
ecore_audio_eolian_files += lib/ecore_audio/ecore_audio_out_core_audio.eo
endif
ecore_audio_eolian_c = $(ecore_audio_eolian_files:%.eo=%.eo.c)
ecore_audio_eolian_h = $(ecore_audio_eolian_files:%.eo=%.eo.h)
@ -40,7 +48,7 @@ lib/ecore_audio/ecore_audio_obj_in_tone.h \
lib/ecore_audio/ecore_audio_protected.h
nodist_installed_ecoreaudiomainheaders_DATA = $(ecore_audio_eolian_h)
lib_ecore_audio_libecore_audio_la_SOURCES = \
lib/ecore_audio/ecore_audio.c \
lib/ecore_audio/ecore_audio_obj.c \
@ -74,6 +82,15 @@ lib/ecore_audio/ecore_audio_obj_out_sndfile.c \
lib/ecore_audio/ecore_audio_sndfile_vio.c
endif
if HAVE_ECORE_AUDIO_CORE_AUDIO
dist_installed_ecoreaudiomainheaders_DATA += \
lib/ecore_audio/ecore_audio_obj_out_core_audio.h
lib_ecore_audio_libecore_audio_la_SOURCES += \
lib/ecore_audio/ecore_audio_obj_out_core_audio.c
lib_ecore_audio_libecore_audio_la_LDFLAGS += @coreaudio_ldflags@
endif
endif
if HAVE_ELUA

View File

@ -44,6 +44,7 @@ enum _Ecore_Audio_Type {
ECORE_AUDIO_TYPE_ALSA, /**< Use ALSA module*/
ECORE_AUDIO_TYPE_SNDFILE, /**< Use libsndfile module */
ECORE_AUDIO_TYPE_TONE, /**< Use tone module */
ECORE_AUDIO_TYPE_CORE_AUDIO, /**< Use Core Audio module (Apple) */
ECORE_AUDIO_TYPE_CUSTOM, /**< Use custom module */
ECORE_AUDIO_MODULE_LAST, /**< Sentinel */
};
@ -211,7 +212,13 @@ EAPI int ecore_audio_shutdown(void);
#include <ecore_audio_obj_in_tone.h>
#include <ecore_audio_obj_out_pulse.h>
#if HAVE_COREAUDIO
# include <ecore_audio_obj_out_core_audio.h>
#endif
#if HAVE_PULSE
# include <ecore_audio_obj_out_pulse.h>
#endif
/**
* @}

View File

@ -0,0 +1,44 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#ifdef HAVE_COREAUDIO
#include "Ecore.h"
#include "ecore_private.h"
#include "Ecore_Audio.h"
#include "ecore_audio_private.h"
static Ecore_Audio_Module *_module = NULL;
EAPI Ecore_Audio_Module *
ecore_audio_core_audio_init(void)
{
/* Don't call this twice */
if (_module != NULL) return _module;
_module = calloc(1, sizeof(Ecore_Audio_Module));
if (EINA_UNLIKELY(_module == NULL))
{
CRI("Failed to allocate Ecore_Audio_Module");
goto ret_null;
}
ECORE_MAGIC_SET(_module, ECORE_MAGIC_AUDIO_MODULE);
_module->type = ECORE_AUDIO_TYPE_CORE_AUDIO;
_module->name = "CoreAudio";
return _module;
ret_null:
return NULL;
}
EAPI void
ecore_audio_nssound_shutdown(void)
{
free(_module);
_module = NULL;
}
#endif /* HAVE_COREAUDIO */

View File

@ -0,0 +1,358 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <Eo.h>
#include "ecore_audio_private.h"
#include <CoreAudio/CoreAudio.h>
/* Notes:
*
* A lot of source code on the internet dealing with CoreAudio is deprecated.
* sndfile-play (bundled with libsndfile) is no exception and uses an almost
* 10 years old API. Nethertheless, sndfile-play has been heavily used to
* create the CoreAudio module.
*
* Documentation is almost non-existant, but here is the technical note from
* Apple explaining how CoreAudio objects should be manipulated:
* https://developer.apple.com/library/mac/technotes/tn2223/_index.html
*/
#include "ecore_audio_obj_out_core_audio.h"
typedef struct
{
Eo *input;
Eo *output;
AudioDeviceIOProcID proc_id;
AudioStreamBasicDescription format;
AudioObjectID obj_id;
UInt32 buf_size;
Eina_Bool is_playing;
Eina_Bool fake_stereo;
} Core_Audio_Helper;
/* Apple's error codes are tricky: they are stored as 32 bits integers.
* However, they are supposed to be represented as 4-bytes strings.
* There is no equivalent of strerror() (of what I know).
*
* Ref: http://vgable.com/blog/2008/04/23/printing-a-fourcharcode/
*
* In case of error, take a look at CoreAudio/AudioHardwareBase.h where
* the error codes are explained.
*/
#define APPLE_ERROR(err_) \
(char[5]){((err_) >> 24) & 0xff, ((err_) >> 16) & 0xff, ((err_) >> 8) & 0xff, (err_) & 0xff, 0}
#define MY_CLASS ECORE_AUDIO_OUT_CORE_AUDIO_CLASS
#define MY_CLASS_NAME "Ecore_Audio_Out_Core_Audio"
/*
* Unused structure. Only here because of Eolian.
* XXX Maybe it is possible to get rid of it.
*/
typedef struct
{
void *this_data_is_here_to_silent_warnings;
} Ecore_Audio_Out_Core_Audio_Data;
/*============================================================================*
* Helper API *
*============================================================================*/
static Core_Audio_Helper *
_core_audio_helper_new(void)
{
return calloc(1, sizeof(Core_Audio_Helper));
}
static void
_core_audio_helper_stop(Core_Audio_Helper *helper)
{
EINA_SAFETY_ON_NULL_RETURN(helper);
OSStatus err;
if (!helper->is_playing) return;
/* Stop audio device */
err = AudioDeviceStop(helper->obj_id, helper->proc_id);
if (EINA_UNLIKELY(err != noErr))
ERR("Failed to stop audio device %i for proc id %p: '%s'",
helper->obj_id, helper->proc_id, APPLE_ERROR(err));
/* Remove proc ID */
err = AudioDeviceDestroyIOProcID(helper->obj_id, helper->proc_id);
if (EINA_UNLIKELY(err != noErr))
ERR("Failed to stop audio device %i for proc id %p: '%s'",
helper->obj_id, helper->proc_id, APPLE_ERROR(err));
helper->is_playing = EINA_FALSE;
}
static void
_core_audio_helper_free(Core_Audio_Helper *helper)
{
EINA_SAFETY_ON_NULL_RETURN(helper);
if (helper->is_playing)
_core_audio_helper_stop(helper);
free(helper);
}
/*============================================================================*
* Audio Object Properties *
*============================================================================*/
static OSStatus
_audio_object_id_get(AudioObjectID *obj_id)
{
OSStatus err;
UInt32 size;
AudioObjectPropertyAddress prop = {
kAudioHardwarePropertyDefaultOutputDevice,
kAudioObjectPropertyScopePlayThrough,
kAudioObjectPropertyElementMaster
};
/* Default output device */
size = sizeof(AudioObjectID);
err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &prop, 0, NULL,
&size, obj_id);
return err;
}
static OSStatus
_audio_device_stream_format_get(AudioObjectID obj_id,
AudioStreamBasicDescription *format)
{
OSStatus err;
UInt32 size;
AudioObjectPropertyAddress prop = {
kAudioDevicePropertyStreamFormat,
kAudioObjectPropertyScopePlayThrough,
kAudioObjectPropertyElementMaster /* Channel number */
};
size = sizeof(AudioStreamBasicDescription);
err = AudioObjectGetPropertyData(obj_id, &prop, 0, NULL, &size, format);
return err;
}
static OSStatus
_audio_device_stream_format_set(AudioObjectID obj_id,
AudioStreamBasicDescription *format)
{
OSStatus err;
UInt32 size;
AudioObjectPropertyAddress prop = {
kAudioDevicePropertyStreamFormat,
kAudioObjectPropertyScopePlayThrough,
kAudioObjectPropertyElementMaster /* Channel number */
};
size = sizeof(AudioStreamBasicDescription);
err = AudioObjectSetPropertyData(obj_id, &prop, 0, NULL, size, format);
return err;
}
/*============================================================================*
* Audio Callback *
*============================================================================*/
static OSStatus
_audio_io_proc_cb(AudioObjectID obj_id EINA_UNUSED,
const AudioTimeStamp *in_now EINA_UNUSED,
const AudioBufferList *input_data EINA_UNUSED,
const AudioTimeStamp *input_time EINA_UNUSED,
AudioBufferList *output_data,
const AudioTimeStamp *in_output_time EINA_UNUSED,
void *data)
{
Core_Audio_Helper *helper = data;
float *buf;
int size, bread, sample_count, k;
size = output_data->mBuffers[0].mDataByteSize;
buf = output_data->mBuffers[0].mData;
sample_count = size / sizeof(float);
if (helper->fake_stereo)
{
eo_do(helper->input, bread = ecore_audio_obj_in_read(buf, size * 2));
for (k = bread - 1; k >= 0; --k)
{
buf[2 * k + 0] = buf[k];
buf[2 * k + 1] = buf[k];
}
bread /= 2;
}
else
{
eo_do(helper->input, bread = ecore_audio_obj_in_read(buf, size * 4));
bread /= 4;
}
/* Done playing */
if (bread < sample_count)
{
INF("Done playing: %i < %i", bread, sample_count);
/* Auto-detached. Don't need to do more. */
}
return noErr;
}
/*============================================================================*
* Eo API *
*============================================================================*/
EOLIAN static void
_ecore_audio_out_core_audio_eo_base_constructor(Eo *obj, Ecore_Audio_Out_Core_Audio_Data *sd EINA_UNUSED)
{
eo_do_super(obj, MY_CLASS, eo_constructor());
}
EOLIAN static void
_ecore_audio_out_core_audio_eo_base_destructor(Eo *obj, Ecore_Audio_Out_Core_Audio_Data *sd EINA_UNUSED)
{
eo_do_super(obj, MY_CLASS, eo_destructor());
}
EOLIAN static void
_ecore_audio_out_core_audio_ecore_audio_volume_set(Eo *obj, Ecore_Audio_Out_Core_Audio_Data *sd EINA_UNUSED, double volume)
{
// TODO Change volume of playing inputs
eo_do_super(obj, MY_CLASS, ecore_audio_obj_volume_set(volume));
}
EOLIAN static Eina_Bool
_ecore_audio_out_core_audio_ecore_audio_out_input_attach(Eo *obj, Ecore_Audio_Out_Core_Audio_Data *sd EINA_UNUSED, Eo *input)
{
Core_Audio_Helper *helper;
UInt32 channels;
OSStatus err;
Eina_Bool chk;
eo_do_super(obj, MY_CLASS, chk = ecore_audio_obj_out_input_attach(input));
if (EINA_UNLIKELY(!chk))
{
ERR("Failed to attach input (eo_do_super)");
goto return_failure;
}
helper = _core_audio_helper_new();
if (EINA_UNLIKELY(helper == NULL))
{
CRI("Failed to allocate memory");
goto detach;
}
/* Keep track of input source and output object */
helper->input = input;
helper->output = obj;
/* Default output device */
err = _audio_object_id_get(&(helper->obj_id));
if (EINA_UNLIKELY(err != noErr))
{
ERR("Failed to get object property: default output device: '%s'",
APPLE_ERROR(err));
goto free_helper;
}
/* Get data format description */
err = _audio_device_stream_format_get(helper->obj_id, &(helper->format));
if (EINA_UNLIKELY(err != noErr))
{
ERR("Failed to get property: stream format: '%s'", APPLE_ERROR(err));
goto free_helper;
}
/* Forward samplerate to CoreAudio */
eo_do(input, helper->format.mSampleRate = ecore_audio_obj_in_samplerate_get());
/* Set channels. If only 1 channel, emulate stereo */
eo_do(input, channels = ecore_audio_obj_in_channels_get());
if (channels == 1)
{
DBG("Fake stereo enabled for input %p", input);
helper->fake_stereo = EINA_TRUE;
channels = 2;
}
helper->format.mChannelsPerFrame = channels;
/* Set new format description */
err = _audio_device_stream_format_set(helper->obj_id, &(helper->format));
if (EINA_UNLIKELY(err != noErr))
{
ERR("Failed to set property: stream format: '%s'", APPLE_ERROR(err));
goto free_helper;
}
/* We want linear PCM */
if (helper->format.mFormatID != kAudioFormatLinearPCM)
{
ERR("Invalid format ID. Expected linear PCM: '%s'", APPLE_ERROR(err));
goto free_helper;
}
/* Create IO proc ID */
err = AudioDeviceCreateIOProcID(helper->obj_id, _audio_io_proc_cb,
helper, &(helper->proc_id));
if (err != noErr)
{
ERR("Failed to create IO proc ID. Error: '%s'", APPLE_ERROR(err));
goto free_helper;
}
/* Keep track of data for deallocation */
eo_do(input, eo_key_data_set("coreaudio_data", helper, NULL));
/* Start playing */
helper->is_playing = EINA_TRUE;
err = AudioDeviceStart(helper->obj_id, helper->proc_id);
if (err != noErr)
{
ERR("Failed to start proc ID %p for device id %i: '%s'",
helper->proc_id, helper->obj_id, APPLE_ERROR(err));
goto free_proc_id;
}
return EINA_TRUE;
free_proc_id:
AudioDeviceDestroyIOProcID(helper->obj_id, helper->proc_id);
free_helper:
free(helper);
detach:
eo_do_super(obj, MY_CLASS, ecore_audio_obj_out_input_detach(input));
return_failure:
return EINA_FALSE;
}
EOLIAN static Eina_Bool
_ecore_audio_out_core_audio_ecore_audio_out_input_detach(Eo *obj, Ecore_Audio_Out_Core_Audio_Data *sd EINA_UNUSED, Eo *input)
{
Core_Audio_Helper *data;
Eina_Bool ret;
DBG("Detach");
/* Free helper */
eo_do(input, data = eo_key_data_get("coreaudio_data"));
_core_audio_helper_free(data);
eo_do_super(obj, MY_CLASS, ret = ecore_audio_obj_out_input_detach(input));
return ret;
}
#include "ecore_audio_out_core_audio.eo.c"

View File

@ -0,0 +1,15 @@
#ifndef _ECORE_AUDIO_OBJ_OUT_CORE_AUDIO_H_
#define _ECORE_AUDIO_OBJ_OUT_CORE_AUDIO_H_
#ifdef __cplusplus
extern "C" {
#endif
#include "ecore_audio_out_core_audio.eo.h"
#ifdef __cplusplus
}
#endif
#endif /* ! _ECORE_AUDIO_OBJ_OUT_CORE_AUDIO_H_ */

View File

@ -0,0 +1,12 @@
class Ecore_Audio_Out_Core_Audio (Ecore_Audio_Out)
{
eo_prefix: ecore_audio_obj_out_core_audio;
implements {
Eo.Base.constructor;
Eo.Base.destructor;
Ecore_Audio.volume.set;
Ecore_Audio_Out.input_attach;
Ecore_Audio_Out.input_detach;
}
}

View File

@ -169,6 +169,12 @@ Ecore_Audio_Module *ecore_audio_sndfile_init(void);
void ecore_audio_sndfile_shutdown(void);
#endif /* HAVE_SNDFILE */
#ifdef HAVE_COREAUDIO
/* ecore_audio_core_audio */
Ecore_Audio_Module *ecore_audio_core_audio_init(void);
void ecore_audio_core_audio_shutdown(void);
#endif /* HAVE_COREAUDIO */
Ecore_Audio_Module *ecore_audio_tone_init(void);
void ecore_audio_tone_shutdown(void);

View File

@ -204,13 +204,21 @@ _edje_multisense_internal_sound_sample_play(Edje *ed, const char *sample_name, c
eo_event_callback_add(ECORE_AUDIO_IN_EVENT_IN_STOPPED, _play_finished, NULL));
if (!out)
{
#if HAVE_COREAUDIO
out = eo_add(ECORE_AUDIO_OUT_CORE_AUDIO_CLASS, NULL);
#elif HAVE_PULSE
out = eo_add(ECORE_AUDIO_OUT_PULSE_CLASS, NULL,
eo_event_callback_add(ECORE_AUDIO_OUT_PULSE_EVENT_CONTEXT_FAIL, _out_fail, NULL));
#endif
if (out) outs++;
}
if (!out)
{
#if HAVE_COREAUDIO
ERR("Could not create multisense audio out (CoreAudio)");
#elif HAVE_PULSE
ERR("Could not create multisense audio out (pulse)");
#endif
eo_del(in);
return EINA_FALSE;
}
@ -269,8 +277,12 @@ _edje_multisense_internal_sound_tone_play(Edje *ed, const char *tone_name, const
if (!out)
{
#if HAVE_COREAUDIO
out = eo_add(ECORE_AUDIO_OUT_CORE_AUDIO_CLASS, NULL);
#elif HAVE_PULSE
out = eo_add(ECORE_AUDIO_OUT_PULSE_CLASS, NULL,
eo_event_callback_add(ECORE_AUDIO_OUT_PULSE_EVENT_CONTEXT_FAIL, _out_fail, NULL));
#endif
if (out) outs++;
}