summaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
authorJean Guyomarc'h <jean.guyomarch@gmail.com>2015-04-15 16:53:39 +0200
committerCedric BAIL <cedric@osg.samsung.com>2015-05-07 09:53:07 +0200
commit62e29b39f4df40fd3c0a6b17f7a16f0f8fc1d0c9 (patch)
treeef94069e64c3d00e792a55fc4bc9147b054cffa1 /src/lib
parenta53c52dee0a3906d1ed3628396929127bcb665ea (diff)
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>
Diffstat (limited to '')
-rw-r--r--src/lib/ecore_audio/Ecore_Audio.h9
-rw-r--r--src/lib/ecore_audio/ecore_audio_core_audio.c44
-rw-r--r--src/lib/ecore_audio/ecore_audio_obj_out_core_audio.c358
-rw-r--r--src/lib/ecore_audio/ecore_audio_obj_out_core_audio.h15
-rw-r--r--src/lib/ecore_audio/ecore_audio_out_core_audio.eo12
-rw-r--r--src/lib/ecore_audio/ecore_audio_private.h6
-rw-r--r--src/lib/edje/edje_multisense.c12
7 files changed, 455 insertions, 1 deletions
diff --git a/src/lib/ecore_audio/Ecore_Audio.h b/src/lib/ecore_audio/Ecore_Audio.h
index 0835d6d99d..edb9b49ea7 100644
--- a/src/lib/ecore_audio/Ecore_Audio.h
+++ b/src/lib/ecore_audio/Ecore_Audio.h
@@ -44,6 +44,7 @@ enum _Ecore_Audio_Type {
44 ECORE_AUDIO_TYPE_ALSA, /**< Use ALSA module*/ 44 ECORE_AUDIO_TYPE_ALSA, /**< Use ALSA module*/
45 ECORE_AUDIO_TYPE_SNDFILE, /**< Use libsndfile module */ 45 ECORE_AUDIO_TYPE_SNDFILE, /**< Use libsndfile module */
46 ECORE_AUDIO_TYPE_TONE, /**< Use tone module */ 46 ECORE_AUDIO_TYPE_TONE, /**< Use tone module */
47 ECORE_AUDIO_TYPE_CORE_AUDIO, /**< Use Core Audio module (Apple) */
47 ECORE_AUDIO_TYPE_CUSTOM, /**< Use custom module */ 48 ECORE_AUDIO_TYPE_CUSTOM, /**< Use custom module */
48 ECORE_AUDIO_MODULE_LAST, /**< Sentinel */ 49 ECORE_AUDIO_MODULE_LAST, /**< Sentinel */
49}; 50};
@@ -211,7 +212,13 @@ EAPI int ecore_audio_shutdown(void);
211 212
212#include <ecore_audio_obj_in_tone.h> 213#include <ecore_audio_obj_in_tone.h>
213 214
214#include <ecore_audio_obj_out_pulse.h> 215#if HAVE_COREAUDIO
216# include <ecore_audio_obj_out_core_audio.h>
217#endif
218
219#if HAVE_PULSE
220# include <ecore_audio_obj_out_pulse.h>
221#endif
215 222
216/** 223/**
217 * @} 224 * @}
diff --git a/src/lib/ecore_audio/ecore_audio_core_audio.c b/src/lib/ecore_audio/ecore_audio_core_audio.c
new file mode 100644
index 0000000000..2141d9d330
--- /dev/null
+++ b/src/lib/ecore_audio/ecore_audio_core_audio.c
@@ -0,0 +1,44 @@
1#ifdef HAVE_CONFIG_H
2#include <config.h>
3#endif
4
5#ifdef HAVE_COREAUDIO
6#include "Ecore.h"
7#include "ecore_private.h"
8#include "Ecore_Audio.h"
9#include "ecore_audio_private.h"
10
11static Ecore_Audio_Module *_module = NULL;
12
13EAPI Ecore_Audio_Module *
14ecore_audio_core_audio_init(void)
15{
16 /* Don't call this twice */
17 if (_module != NULL) return _module;
18
19 _module = calloc(1, sizeof(Ecore_Audio_Module));
20 if (EINA_UNLIKELY(_module == NULL))
21 {
22 CRI("Failed to allocate Ecore_Audio_Module");
23 goto ret_null;
24 }
25
26 ECORE_MAGIC_SET(_module, ECORE_MAGIC_AUDIO_MODULE);
27 _module->type = ECORE_AUDIO_TYPE_CORE_AUDIO;
28 _module->name = "CoreAudio";
29
30 return _module;
31
32ret_null:
33 return NULL;
34}
35
36EAPI void
37ecore_audio_nssound_shutdown(void)
38{
39 free(_module);
40 _module = NULL;
41}
42
43#endif /* HAVE_COREAUDIO */
44
diff --git a/src/lib/ecore_audio/ecore_audio_obj_out_core_audio.c b/src/lib/ecore_audio/ecore_audio_obj_out_core_audio.c
new file mode 100644
index 0000000000..d358ec036d
--- /dev/null
+++ b/src/lib/ecore_audio/ecore_audio_obj_out_core_audio.c
@@ -0,0 +1,358 @@
1#ifdef HAVE_CONFIG_H
2#include <config.h>
3#endif
4
5#include <Eo.h>
6#include "ecore_audio_private.h"
7
8#include <CoreAudio/CoreAudio.h>
9
10/* Notes:
11 *
12 * A lot of source code on the internet dealing with CoreAudio is deprecated.
13 * sndfile-play (bundled with libsndfile) is no exception and uses an almost
14 * 10 years old API. Nethertheless, sndfile-play has been heavily used to
15 * create the CoreAudio module.
16 *
17 * Documentation is almost non-existant, but here is the technical note from
18 * Apple explaining how CoreAudio objects should be manipulated:
19 * https://developer.apple.com/library/mac/technotes/tn2223/_index.html
20 */
21
22#include "ecore_audio_obj_out_core_audio.h"
23
24typedef struct
25{
26 Eo *input;
27 Eo *output;
28 AudioDeviceIOProcID proc_id;
29 AudioStreamBasicDescription format;
30 AudioObjectID obj_id;
31 UInt32 buf_size;
32
33 Eina_Bool is_playing;
34 Eina_Bool fake_stereo;
35} Core_Audio_Helper;
36
37
38/* Apple's error codes are tricky: they are stored as 32 bits integers.
39 * However, they are supposed to be represented as 4-bytes strings.
40 * There is no equivalent of strerror() (of what I know).
41 *
42 * Ref: http://vgable.com/blog/2008/04/23/printing-a-fourcharcode/
43 *
44 * In case of error, take a look at CoreAudio/AudioHardwareBase.h where
45 * the error codes are explained.
46 */
47#define APPLE_ERROR(err_) \
48 (char[5]){((err_) >> 24) & 0xff, ((err_) >> 16) & 0xff, ((err_) >> 8) & 0xff, (err_) & 0xff, 0}
49
50#define MY_CLASS ECORE_AUDIO_OUT_CORE_AUDIO_CLASS
51#define MY_CLASS_NAME "Ecore_Audio_Out_Core_Audio"
52
53/*
54 * Unused structure. Only here because of Eolian.
55 * XXX Maybe it is possible to get rid of it.
56 */
57typedef struct
58{
59 void *this_data_is_here_to_silent_warnings;
60} Ecore_Audio_Out_Core_Audio_Data;
61
62
63/*============================================================================*
64 * Helper API *
65 *============================================================================*/
66
67static Core_Audio_Helper *
68_core_audio_helper_new(void)
69{
70 return calloc(1, sizeof(Core_Audio_Helper));
71}
72
73static void
74_core_audio_helper_stop(Core_Audio_Helper *helper)
75{
76 EINA_SAFETY_ON_NULL_RETURN(helper);
77
78 OSStatus err;
79
80 if (!helper->is_playing) return;
81
82 /* Stop audio device */
83 err = AudioDeviceStop(helper->obj_id, helper->proc_id);
84 if (EINA_UNLIKELY(err != noErr))
85 ERR("Failed to stop audio device %i for proc id %p: '%s'",
86 helper->obj_id, helper->proc_id, APPLE_ERROR(err));
87
88 /* Remove proc ID */
89 err = AudioDeviceDestroyIOProcID(helper->obj_id, helper->proc_id);
90 if (EINA_UNLIKELY(err != noErr))
91 ERR("Failed to stop audio device %i for proc id %p: '%s'",
92 helper->obj_id, helper->proc_id, APPLE_ERROR(err));
93
94 helper->is_playing = EINA_FALSE;
95}
96
97static void
98_core_audio_helper_free(Core_Audio_Helper *helper)
99{
100 EINA_SAFETY_ON_NULL_RETURN(helper);
101
102 if (helper->is_playing)
103 _core_audio_helper_stop(helper);
104 free(helper);
105}
106
107
108/*============================================================================*
109 * Audio Object Properties *
110 *============================================================================*/
111
112static OSStatus
113_audio_object_id_get(AudioObjectID *obj_id)
114{
115 OSStatus err;
116 UInt32 size;
117 AudioObjectPropertyAddress prop = {
118 kAudioHardwarePropertyDefaultOutputDevice,
119 kAudioObjectPropertyScopePlayThrough,
120 kAudioObjectPropertyElementMaster
121 };
122
123 /* Default output device */
124 size = sizeof(AudioObjectID);
125 err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &prop, 0, NULL,
126 &size, obj_id);
127 return err;
128}
129
130static OSStatus
131_audio_device_stream_format_get(AudioObjectID obj_id,
132 AudioStreamBasicDescription *format)
133{
134 OSStatus err;
135 UInt32 size;
136 AudioObjectPropertyAddress prop = {
137 kAudioDevicePropertyStreamFormat,
138 kAudioObjectPropertyScopePlayThrough,
139 kAudioObjectPropertyElementMaster /* Channel number */
140 };
141
142 size = sizeof(AudioStreamBasicDescription);
143 err = AudioObjectGetPropertyData(obj_id, &prop, 0, NULL, &size, format);
144 return err;
145}
146
147static OSStatus
148_audio_device_stream_format_set(AudioObjectID obj_id,
149 AudioStreamBasicDescription *format)
150{
151 OSStatus err;
152 UInt32 size;
153 AudioObjectPropertyAddress prop = {
154 kAudioDevicePropertyStreamFormat,
155 kAudioObjectPropertyScopePlayThrough,
156 kAudioObjectPropertyElementMaster /* Channel number */
157 };
158
159 size = sizeof(AudioStreamBasicDescription);
160 err = AudioObjectSetPropertyData(obj_id, &prop, 0, NULL, size, format);
161 return err;
162}
163
164
165/*============================================================================*
166 * Audio Callback *
167 *============================================================================*/
168
169static OSStatus
170_audio_io_proc_cb(AudioObjectID obj_id EINA_UNUSED,
171 const AudioTimeStamp *in_now EINA_UNUSED,
172 const AudioBufferList *input_data EINA_UNUSED,
173 const AudioTimeStamp *input_time EINA_UNUSED,
174 AudioBufferList *output_data,
175 const AudioTimeStamp *in_output_time EINA_UNUSED,
176 void *data)
177{
178 Core_Audio_Helper *helper = data;
179 float *buf;
180 int size, bread, sample_count, k;
181
182 size = output_data->mBuffers[0].mDataByteSize;
183 buf = output_data->mBuffers[0].mData;
184 sample_count = size / sizeof(float);
185
186 if (helper->fake_stereo)
187 {
188 eo_do(helper->input, bread = ecore_audio_obj_in_read(buf, size * 2));
189
190 for (k = bread - 1; k >= 0; --k)
191 {
192 buf[2 * k + 0] = buf[k];
193 buf[2 * k + 1] = buf[k];
194 }
195 bread /= 2;
196 }
197 else
198 {
199 eo_do(helper->input, bread = ecore_audio_obj_in_read(buf, size * 4));
200 bread /= 4;
201 }
202
203 /* Done playing */
204 if (bread < sample_count)
205 {
206 INF("Done playing: %i < %i", bread, sample_count);
207 /* Auto-detached. Don't need to do more. */
208 }
209
210 return noErr;
211}
212
213
214/*============================================================================*
215 * Eo API *
216 *============================================================================*/
217
218EOLIAN static void
219_ecore_audio_out_core_audio_eo_base_constructor(Eo *obj, Ecore_Audio_Out_Core_Audio_Data *sd EINA_UNUSED)
220{
221 eo_do_super(obj, MY_CLASS, eo_constructor());
222}
223
224EOLIAN static void
225_ecore_audio_out_core_audio_eo_base_destructor(Eo *obj, Ecore_Audio_Out_Core_Audio_Data *sd EINA_UNUSED)
226{
227 eo_do_super(obj, MY_CLASS, eo_destructor());
228}
229
230EOLIAN static void
231_ecore_audio_out_core_audio_ecore_audio_volume_set(Eo *obj, Ecore_Audio_Out_Core_Audio_Data *sd EINA_UNUSED, double volume)
232{
233 // TODO Change volume of playing inputs
234 eo_do_super(obj, MY_CLASS, ecore_audio_obj_volume_set(volume));
235}
236
237EOLIAN static Eina_Bool
238_ecore_audio_out_core_audio_ecore_audio_out_input_attach(Eo *obj, Ecore_Audio_Out_Core_Audio_Data *sd EINA_UNUSED, Eo *input)
239{
240 Core_Audio_Helper *helper;
241 UInt32 channels;
242 OSStatus err;
243 Eina_Bool chk;
244
245 eo_do_super(obj, MY_CLASS, chk = ecore_audio_obj_out_input_attach(input));
246 if (EINA_UNLIKELY(!chk))
247 {
248 ERR("Failed to attach input (eo_do_super)");
249 goto return_failure;
250 }
251
252 helper = _core_audio_helper_new();
253 if (EINA_UNLIKELY(helper == NULL))
254 {
255 CRI("Failed to allocate memory");
256 goto detach;
257 }
258
259 /* Keep track of input source and output object */
260 helper->input = input;
261 helper->output = obj;
262
263 /* Default output device */
264 err = _audio_object_id_get(&(helper->obj_id));
265 if (EINA_UNLIKELY(err != noErr))
266 {
267 ERR("Failed to get object property: default output device: '%s'",
268 APPLE_ERROR(err));
269 goto free_helper;
270 }
271
272 /* Get data format description */
273 err = _audio_device_stream_format_get(helper->obj_id, &(helper->format));
274 if (EINA_UNLIKELY(err != noErr))
275 {
276 ERR("Failed to get property: stream format: '%s'", APPLE_ERROR(err));
277 goto free_helper;
278 }
279
280 /* Forward samplerate to CoreAudio */
281 eo_do(input, helper->format.mSampleRate = ecore_audio_obj_in_samplerate_get());
282
283 /* Set channels. If only 1 channel, emulate stereo */
284 eo_do(input, channels = ecore_audio_obj_in_channels_get());
285 if (channels == 1)
286 {
287 DBG("Fake stereo enabled for input %p", input);
288 helper->fake_stereo = EINA_TRUE;
289 channels = 2;
290 }
291 helper->format.mChannelsPerFrame = channels;
292
293 /* Set new format description */
294 err = _audio_device_stream_format_set(helper->obj_id, &(helper->format));
295 if (EINA_UNLIKELY(err != noErr))
296 {
297 ERR("Failed to set property: stream format: '%s'", APPLE_ERROR(err));
298 goto free_helper;
299 }
300
301 /* We want linear PCM */
302 if (helper->format.mFormatID != kAudioFormatLinearPCM)
303 {
304 ERR("Invalid format ID. Expected linear PCM: '%s'", APPLE_ERROR(err));
305 goto free_helper;
306 }
307
308 /* Create IO proc ID */
309 err = AudioDeviceCreateIOProcID(helper->obj_id, _audio_io_proc_cb,
310 helper, &(helper->proc_id));
311 if (err != noErr)
312 {
313 ERR("Failed to create IO proc ID. Error: '%s'", APPLE_ERROR(err));
314 goto free_helper;
315 }
316
317 /* Keep track of data for deallocation */
318 eo_do(input, eo_key_data_set("coreaudio_data", helper, NULL));
319
320 /* Start playing */
321 helper->is_playing = EINA_TRUE;
322 err = AudioDeviceStart(helper->obj_id, helper->proc_id);
323 if (err != noErr)
324 {
325 ERR("Failed to start proc ID %p for device id %i: '%s'",
326 helper->proc_id, helper->obj_id, APPLE_ERROR(err));
327 goto free_proc_id;
328 }
329
330 return EINA_TRUE;
331
332free_proc_id:
333 AudioDeviceDestroyIOProcID(helper->obj_id, helper->proc_id);
334free_helper:
335 free(helper);
336detach:
337 eo_do_super(obj, MY_CLASS, ecore_audio_obj_out_input_detach(input));
338return_failure:
339 return EINA_FALSE;
340}
341
342EOLIAN static Eina_Bool
343_ecore_audio_out_core_audio_ecore_audio_out_input_detach(Eo *obj, Ecore_Audio_Out_Core_Audio_Data *sd EINA_UNUSED, Eo *input)
344{
345 Core_Audio_Helper *data;
346 Eina_Bool ret;
347
348 DBG("Detach");
349 /* Free helper */
350 eo_do(input, data = eo_key_data_get("coreaudio_data"));
351 _core_audio_helper_free(data);
352
353 eo_do_super(obj, MY_CLASS, ret = ecore_audio_obj_out_input_detach(input));
354
355 return ret;
356}
357
358#include "ecore_audio_out_core_audio.eo.c"
diff --git a/src/lib/ecore_audio/ecore_audio_obj_out_core_audio.h b/src/lib/ecore_audio/ecore_audio_obj_out_core_audio.h
new file mode 100644
index 0000000000..9b8eb7d8f8
--- /dev/null
+++ b/src/lib/ecore_audio/ecore_audio_obj_out_core_audio.h
@@ -0,0 +1,15 @@
1#ifndef _ECORE_AUDIO_OBJ_OUT_CORE_AUDIO_H_
2#define _ECORE_AUDIO_OBJ_OUT_CORE_AUDIO_H_
3
4#ifdef __cplusplus
5extern "C" {
6#endif
7
8#include "ecore_audio_out_core_audio.eo.h"
9
10#ifdef __cplusplus
11}
12#endif
13
14#endif /* ! _ECORE_AUDIO_OBJ_OUT_CORE_AUDIO_H_ */
15
diff --git a/src/lib/ecore_audio/ecore_audio_out_core_audio.eo b/src/lib/ecore_audio/ecore_audio_out_core_audio.eo
new file mode 100644
index 0000000000..6176ce73a9
--- /dev/null
+++ b/src/lib/ecore_audio/ecore_audio_out_core_audio.eo
@@ -0,0 +1,12 @@
1class Ecore_Audio_Out_Core_Audio (Ecore_Audio_Out)
2{
3 eo_prefix: ecore_audio_obj_out_core_audio;
4 implements {
5 Eo.Base.constructor;
6 Eo.Base.destructor;
7 Ecore_Audio.volume.set;
8 Ecore_Audio_Out.input_attach;
9 Ecore_Audio_Out.input_detach;
10 }
11}
12
diff --git a/src/lib/ecore_audio/ecore_audio_private.h b/src/lib/ecore_audio/ecore_audio_private.h
index 36a06f5f4c..13a7b6ae4c 100644
--- a/src/lib/ecore_audio/ecore_audio_private.h
+++ b/src/lib/ecore_audio/ecore_audio_private.h
@@ -169,6 +169,12 @@ Ecore_Audio_Module *ecore_audio_sndfile_init(void);
169void ecore_audio_sndfile_shutdown(void); 169void ecore_audio_sndfile_shutdown(void);
170#endif /* HAVE_SNDFILE */ 170#endif /* HAVE_SNDFILE */
171 171
172#ifdef HAVE_COREAUDIO
173/* ecore_audio_core_audio */
174Ecore_Audio_Module *ecore_audio_core_audio_init(void);
175void ecore_audio_core_audio_shutdown(void);
176#endif /* HAVE_COREAUDIO */
177
172Ecore_Audio_Module *ecore_audio_tone_init(void); 178Ecore_Audio_Module *ecore_audio_tone_init(void);
173void ecore_audio_tone_shutdown(void); 179void ecore_audio_tone_shutdown(void);
174 180
diff --git a/src/lib/edje/edje_multisense.c b/src/lib/edje/edje_multisense.c
index da24e0cf4a..913c1f9a89 100644
--- a/src/lib/edje/edje_multisense.c
+++ b/src/lib/edje/edje_multisense.c
@@ -204,13 +204,21 @@ _edje_multisense_internal_sound_sample_play(Edje *ed, const char *sample_name, c
204 eo_event_callback_add(ECORE_AUDIO_IN_EVENT_IN_STOPPED, _play_finished, NULL)); 204 eo_event_callback_add(ECORE_AUDIO_IN_EVENT_IN_STOPPED, _play_finished, NULL));
205 if (!out) 205 if (!out)
206 { 206 {
207#if HAVE_COREAUDIO
208 out = eo_add(ECORE_AUDIO_OUT_CORE_AUDIO_CLASS, NULL);
209#elif HAVE_PULSE
207 out = eo_add(ECORE_AUDIO_OUT_PULSE_CLASS, NULL, 210 out = eo_add(ECORE_AUDIO_OUT_PULSE_CLASS, NULL,
208 eo_event_callback_add(ECORE_AUDIO_OUT_PULSE_EVENT_CONTEXT_FAIL, _out_fail, NULL)); 211 eo_event_callback_add(ECORE_AUDIO_OUT_PULSE_EVENT_CONTEXT_FAIL, _out_fail, NULL));
212#endif
209 if (out) outs++; 213 if (out) outs++;
210 } 214 }
211 if (!out) 215 if (!out)
212 { 216 {
217#if HAVE_COREAUDIO
218 ERR("Could not create multisense audio out (CoreAudio)");
219#elif HAVE_PULSE
213 ERR("Could not create multisense audio out (pulse)"); 220 ERR("Could not create multisense audio out (pulse)");
221#endif
214 eo_del(in); 222 eo_del(in);
215 return EINA_FALSE; 223 return EINA_FALSE;
216 } 224 }
@@ -269,8 +277,12 @@ _edje_multisense_internal_sound_tone_play(Edje *ed, const char *tone_name, const
269 277
270 if (!out) 278 if (!out)
271 { 279 {
280#if HAVE_COREAUDIO
281 out = eo_add(ECORE_AUDIO_OUT_CORE_AUDIO_CLASS, NULL);
282#elif HAVE_PULSE
272 out = eo_add(ECORE_AUDIO_OUT_PULSE_CLASS, NULL, 283 out = eo_add(ECORE_AUDIO_OUT_PULSE_CLASS, NULL,
273 eo_event_callback_add(ECORE_AUDIO_OUT_PULSE_EVENT_CONTEXT_FAIL, _out_fail, NULL)); 284 eo_event_callback_add(ECORE_AUDIO_OUT_PULSE_EVENT_CONTEXT_FAIL, _out_fail, NULL));
285#endif
274 if (out) outs++; 286 if (out) outs++;
275 } 287 }
276 288