],
[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
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
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
### Additional options to configure
### Default values
-want_multisense="${want_pulseaudio}"
+
AC_ARG_ENABLE([multisense],
[AS_HELP_STRING([--enable-multisense],[Enable multisense support. @<:@default=enabled@:>@])],
[
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"
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
echo ""
echo "#-------------------------------------------------------------------#"
fi
+
--- /dev/null
+#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"