2013-04-17 10:31:38 -07:00
|
|
|
#ifdef HAVE_CONFIG_H
|
|
|
|
#include <config.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <string.h>
|
|
|
|
|
2015-04-10 02:15:57 -07:00
|
|
|
#if defined (__MacOSX__) || (defined (__MACH__) && defined (__APPLE__)) || defined (__FreeBSD__)
|
2014-07-22 03:39:30 -07:00
|
|
|
# include <libgen.h>
|
|
|
|
#endif
|
|
|
|
|
2013-04-17 10:31:38 -07:00
|
|
|
#ifdef HAVE_FEATURES_H
|
|
|
|
#include <features.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include <Eo.h>
|
|
|
|
#include "ecore_audio_private.h"
|
|
|
|
#include <pulse/pulseaudio.h>
|
|
|
|
|
|
|
|
#include <sys/time.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/socket.h>
|
|
|
|
|
|
|
|
extern pa_mainloop_api functable;
|
|
|
|
|
2014-06-02 06:47:59 -07:00
|
|
|
#define MY_CLASS ECORE_AUDIO_OUT_PULSE_CLASS
|
2013-11-07 03:16:01 -08:00
|
|
|
#define MY_CLASS_NAME "Ecore_Audio_Out_Pulse"
|
2013-04-17 10:31:38 -07:00
|
|
|
|
|
|
|
struct _Ecore_Audio_Pulse_Class {
|
|
|
|
pa_mainloop_api *api;
|
|
|
|
pa_context *context;
|
|
|
|
pa_context_state_t state;
|
2013-08-27 07:42:24 -07:00
|
|
|
Ecore_Job *state_job;
|
2013-04-17 10:31:38 -07:00
|
|
|
Eina_List *outputs;
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct _Ecore_Audio_Pulse_Class class_vars = {
|
|
|
|
.api = &functable,
|
|
|
|
};
|
|
|
|
|
2014-03-27 04:34:01 -07:00
|
|
|
struct _Ecore_Audio_Out_Pulse_Data
|
2013-04-17 10:31:38 -07:00
|
|
|
{
|
|
|
|
char *foo;
|
|
|
|
};
|
|
|
|
|
2014-03-27 04:34:01 -07:00
|
|
|
typedef struct _Ecore_Audio_Out_Pulse_Data Ecore_Audio_Out_Pulse_Data;
|
2013-04-17 10:31:38 -07:00
|
|
|
|
2014-03-27 04:34:01 -07:00
|
|
|
EOLIAN static void
|
|
|
|
_ecore_audio_out_pulse_ecore_audio_volume_set(Eo *eo_obj, Ecore_Audio_Out_Pulse_Data *_pd EINA_UNUSED, double volume)
|
2013-04-17 10:31:38 -07:00
|
|
|
{
|
|
|
|
Eo *in;
|
2014-04-02 06:21:57 -07:00
|
|
|
pa_stream *stream = NULL;
|
2013-04-17 10:31:38 -07:00
|
|
|
Eina_List *input;
|
|
|
|
uint32_t idx;
|
|
|
|
pa_cvolume pa_volume;
|
2014-06-02 06:47:59 -07:00
|
|
|
Ecore_Audio_Output *out_obj = eo_data_scope_get(eo_obj, ECORE_AUDIO_OUT_CLASS);
|
2013-04-17 10:31:38 -07:00
|
|
|
|
|
|
|
if (volume < 0)
|
|
|
|
volume = 0;
|
|
|
|
|
|
|
|
pa_cvolume_set(&pa_volume, 2, volume * PA_VOLUME_NORM);
|
|
|
|
|
|
|
|
eo_do_super(eo_obj, MY_CLASS, ecore_audio_obj_volume_set(volume));
|
|
|
|
|
|
|
|
EINA_LIST_FOREACH(out_obj->inputs, input, in) {
|
2014-04-02 06:21:57 -07:00
|
|
|
eo_do(in, stream = eo_key_data_get("pulse_data"));
|
2013-04-17 10:31:38 -07:00
|
|
|
idx = pa_stream_get_index(stream);
|
|
|
|
pa_operation_unref(pa_context_set_sink_input_volume(class_vars.context, idx, &pa_volume, NULL, NULL));
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
static void _write_cb(pa_stream *stream, size_t len, void *data)
|
|
|
|
{
|
|
|
|
Eo *in = data;
|
|
|
|
|
|
|
|
void *buf;
|
2014-04-02 06:21:57 -07:00
|
|
|
ssize_t bread = 0;
|
2013-04-25 09:27:13 -07:00
|
|
|
size_t wlen = len;
|
2013-04-17 10:31:38 -07:00
|
|
|
|
2013-04-25 09:27:13 -07:00
|
|
|
pa_stream_begin_write(stream, &buf, &wlen);
|
2013-04-17 10:31:38 -07:00
|
|
|
|
2014-04-02 06:21:57 -07:00
|
|
|
eo_do(in, bread = ecore_audio_obj_in_read(buf, wlen));
|
2013-04-25 09:27:13 -07:00
|
|
|
|
|
|
|
pa_stream_write(stream, buf, bread, NULL, 0, PA_SEEK_RELATIVE);
|
2013-04-18 11:34:33 -07:00
|
|
|
if (bread < (int)len)
|
2013-04-17 10:31:38 -07:00
|
|
|
{
|
2013-04-17 12:35:10 -07:00
|
|
|
pa_operation_unref(pa_stream_trigger(stream, NULL, NULL));
|
2013-04-17 10:31:38 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-04-18 11:34:33 -07:00
|
|
|
static Eina_Bool _update_samplerate_cb(void *data EINA_UNUSED, Eo *eo_obj, const Eo_Event_Description *desc EINA_UNUSED, void *event_info EINA_UNUSED)
|
2013-04-17 10:31:38 -07:00
|
|
|
{
|
2014-04-02 06:21:57 -07:00
|
|
|
pa_stream *stream = NULL;
|
|
|
|
int samplerate = 0;
|
|
|
|
double speed = 0;
|
2013-04-17 10:31:38 -07:00
|
|
|
|
2014-04-02 06:21:57 -07:00
|
|
|
eo_do(eo_obj, samplerate = ecore_audio_obj_in_samplerate_get());
|
|
|
|
eo_do(eo_obj, speed = ecore_audio_obj_in_speed_get());
|
2013-04-17 10:31:38 -07:00
|
|
|
|
2014-04-02 06:21:57 -07:00
|
|
|
eo_do(eo_obj, stream = eo_key_data_get("pulse_data"));
|
2013-04-17 10:31:38 -07:00
|
|
|
|
|
|
|
pa_operation_unref(pa_stream_update_sample_rate(stream, samplerate * speed, NULL, NULL));
|
2013-04-18 11:34:33 -07:00
|
|
|
|
|
|
|
return EINA_TRUE;
|
2013-04-17 10:31:38 -07:00
|
|
|
}
|
|
|
|
|
2013-04-18 11:21:05 -07:00
|
|
|
static Eina_Bool _input_attach_internal(Eo *eo_obj, Eo *in)
|
2013-04-17 10:31:38 -07:00
|
|
|
{
|
2014-04-02 06:21:57 -07:00
|
|
|
const char *name = NULL;
|
2013-04-17 10:31:38 -07:00
|
|
|
pa_sample_spec ss;
|
2014-04-02 06:21:57 -07:00
|
|
|
double speed = 0;
|
2013-04-17 10:31:38 -07:00
|
|
|
pa_stream *stream;
|
2014-04-02 06:21:57 -07:00
|
|
|
Eina_Bool ret = EINA_FALSE;
|
2014-06-02 06:47:59 -07:00
|
|
|
Ecore_Audio_Object *ea_obj = eo_data_scope_get(eo_obj, ECORE_AUDIO_CLASS);
|
2013-04-17 10:31:38 -07:00
|
|
|
|
2014-04-02 06:21:57 -07:00
|
|
|
eo_do_super(eo_obj, MY_CLASS, ret = ecore_audio_obj_out_input_attach(in));
|
2013-04-19 08:41:17 -07:00
|
|
|
if (!ret)
|
|
|
|
return EINA_FALSE;
|
|
|
|
|
2013-04-17 10:31:38 -07:00
|
|
|
ss.format = PA_SAMPLE_FLOAT32LE;
|
2014-04-02 06:21:57 -07:00
|
|
|
eo_do(in, ss.rate = ecore_audio_obj_in_samplerate_get());
|
|
|
|
eo_do(in, speed = ecore_audio_obj_in_speed_get());
|
|
|
|
eo_do(in, ss.channels = ecore_audio_obj_in_channels_get());
|
|
|
|
eo_do(in, name = ecore_audio_obj_name_get());
|
2013-04-17 10:31:38 -07:00
|
|
|
|
|
|
|
ss.rate = ss.rate * speed;
|
|
|
|
|
|
|
|
stream = pa_stream_new(class_vars.context, name, &ss, NULL);
|
|
|
|
if (!stream) {
|
|
|
|
ERR("Could not create stream");
|
2014-04-02 06:21:57 -07:00
|
|
|
eo_do_super(eo_obj, MY_CLASS, ecore_audio_obj_out_input_detach(in));
|
2013-04-18 11:21:05 -07:00
|
|
|
return EINA_FALSE;
|
2013-04-17 10:31:38 -07:00
|
|
|
}
|
|
|
|
|
2014-03-26 08:01:08 -07:00
|
|
|
eo_do(in, eo_event_callback_add(ECORE_AUDIO_IN_EVENT_IN_SAMPLERATE_CHANGED, _update_samplerate_cb, eo_obj));
|
2013-04-17 10:31:38 -07:00
|
|
|
|
2014-04-02 04:01:16 -07:00
|
|
|
eo_do(in, eo_key_data_set("pulse_data", stream, NULL));
|
2013-04-17 10:31:38 -07:00
|
|
|
|
|
|
|
|
|
|
|
pa_stream_set_write_callback(stream, _write_cb, in);
|
|
|
|
pa_stream_connect_playback(stream, NULL, NULL, PA_STREAM_VARIABLE_RATE, NULL, NULL);
|
2013-04-18 11:21:05 -07:00
|
|
|
|
2013-04-18 11:34:33 -07:00
|
|
|
if (ea_obj->paused)
|
|
|
|
pa_operation_unref(pa_stream_cork(stream, 1, NULL, NULL));
|
|
|
|
|
2013-04-18 11:21:05 -07:00
|
|
|
return ret;
|
2013-04-17 10:31:38 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
static Eina_Bool _delayed_attach_cb(void *data, Eo *eo_obj, const Eo_Event_Description *desc EINA_UNUSED, void *event_info EINA_UNUSED)
|
|
|
|
{
|
|
|
|
Eo *in = data;
|
2014-03-27 04:34:01 -07:00
|
|
|
eo_do(eo_obj, eo_event_callback_del(ECORE_AUDIO_OUT_PULSE_EVENT_CONTEXT_READY, _delayed_attach_cb, in));
|
2013-04-17 10:31:38 -07:00
|
|
|
|
|
|
|
_input_attach_internal(eo_obj, in);
|
|
|
|
|
|
|
|
return EINA_TRUE;
|
|
|
|
}
|
|
|
|
|
2014-03-27 04:34:01 -07:00
|
|
|
EOLIAN static Eina_Bool
|
|
|
|
_ecore_audio_out_pulse_ecore_audio_out_input_attach(Eo *eo_obj, Ecore_Audio_Out_Pulse_Data *_pd EINA_UNUSED, Eo *in)
|
2013-04-17 10:31:38 -07:00
|
|
|
{
|
2013-04-18 11:21:05 -07:00
|
|
|
Eina_Bool retval = EINA_TRUE;
|
|
|
|
|
2013-04-17 10:31:38 -07:00
|
|
|
if (class_vars.state != PA_CONTEXT_READY) {
|
|
|
|
DBG("Delaying input_attach because PA context is not ready.");
|
2014-03-27 04:34:01 -07:00
|
|
|
eo_do(eo_obj, eo_event_callback_add(ECORE_AUDIO_OUT_PULSE_EVENT_CONTEXT_READY, _delayed_attach_cb, in));
|
2013-04-17 10:31:38 -07:00
|
|
|
} else {
|
2013-04-18 11:21:05 -07:00
|
|
|
retval = _input_attach_internal(eo_obj, in);
|
2013-04-17 10:31:38 -07:00
|
|
|
}
|
2014-03-27 04:34:01 -07:00
|
|
|
|
|
|
|
return retval;
|
2013-04-17 10:31:38 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
static void _drain_cb(pa_stream *stream, int success EINA_UNUSED, void *data EINA_UNUSED)
|
|
|
|
{
|
|
|
|
pa_stream_disconnect(stream);
|
|
|
|
pa_stream_unref(stream);
|
|
|
|
}
|
|
|
|
|
2014-03-27 04:34:01 -07:00
|
|
|
EOLIAN static Eina_Bool
|
|
|
|
_ecore_audio_out_pulse_ecore_audio_out_input_detach(Eo *eo_obj, Ecore_Audio_Out_Pulse_Data *_pd EINA_UNUSED, Eo *in)
|
2013-04-17 10:31:38 -07:00
|
|
|
{
|
2014-04-02 06:21:57 -07:00
|
|
|
pa_stream *stream = NULL;
|
|
|
|
Eina_Bool ret2 = EINA_FALSE;
|
2013-04-17 10:31:38 -07:00
|
|
|
|
2014-04-02 06:21:57 -07:00
|
|
|
eo_do_super(eo_obj, MY_CLASS, ret2 = ecore_audio_obj_out_input_detach(in));
|
2013-04-23 09:10:16 -07:00
|
|
|
if (!ret2)
|
2014-03-27 04:34:01 -07:00
|
|
|
return EINA_FALSE;
|
2013-04-17 10:31:38 -07:00
|
|
|
|
2014-04-02 06:21:57 -07:00
|
|
|
eo_do(in, stream = eo_key_data_get("pulse_data"));
|
2013-04-17 10:31:38 -07:00
|
|
|
|
|
|
|
pa_stream_set_write_callback(stream, NULL, NULL);
|
|
|
|
pa_operation_unref(pa_stream_drain(stream, _drain_cb, NULL));
|
|
|
|
|
2014-03-27 04:34:01 -07:00
|
|
|
return EINA_TRUE;
|
2013-04-17 10:31:38 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
static void _state_cb(pa_context *context, void *data EINA_UNUSED)
|
|
|
|
{
|
2013-11-16 18:03:20 -08:00
|
|
|
Eina_List *out, *tmp;
|
|
|
|
Eo *eo_obj;
|
|
|
|
pa_context_state_t state;
|
|
|
|
|
|
|
|
state = pa_context_get_state(context);
|
|
|
|
class_vars.state = state;
|
|
|
|
|
|
|
|
//ref everything in the list to be sure...
|
|
|
|
EINA_LIST_FOREACH(class_vars.outputs, out, eo_obj) {
|
|
|
|
eo_ref(eo_obj);
|
|
|
|
}
|
|
|
|
// the callback here can delete things in the list..
|
|
|
|
if (state == PA_CONTEXT_READY) {
|
|
|
|
DBG("PA context ready.");
|
|
|
|
EINA_LIST_FOREACH(class_vars.outputs, out, eo_obj) {
|
2014-04-02 06:21:57 -07:00
|
|
|
eo_do(eo_obj, eo_event_callback_call(ECORE_AUDIO_OUT_PULSE_EVENT_CONTEXT_READY, NULL));
|
2013-11-16 18:03:20 -08:00
|
|
|
}
|
|
|
|
} else if ((state == PA_CONTEXT_FAILED) || (state == PA_CONTEXT_TERMINATED)) {
|
|
|
|
DBG("PA context fail.");
|
|
|
|
EINA_LIST_FOREACH(class_vars.outputs, out, eo_obj) {
|
2014-04-02 06:21:57 -07:00
|
|
|
eo_do(eo_obj, eo_event_callback_call(ECORE_AUDIO_OUT_PULSE_EVENT_CONTEXT_FAIL, NULL));
|
2013-11-16 18:03:20 -08:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
DBG("Connection state %i", state);
|
|
|
|
}
|
|
|
|
// now unref everything safely
|
|
|
|
EINA_LIST_FOREACH_SAFE(class_vars.outputs, out, tmp, eo_obj) {
|
|
|
|
eo_unref(eo_obj);
|
|
|
|
}
|
2013-08-27 07:42:24 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
static void _state_job(void *data EINA_UNUSED)
|
|
|
|
{
|
|
|
|
if ((class_vars.state == PA_CONTEXT_FAILED) ||
|
|
|
|
(class_vars.state == PA_CONTEXT_TERMINATED))
|
|
|
|
{
|
|
|
|
Eo *eo_obj;
|
2013-11-09 18:04:18 -08:00
|
|
|
Eina_List *out, *tmp;
|
2013-08-27 07:42:24 -07:00
|
|
|
|
|
|
|
DBG("PA context fail.");
|
2013-11-09 18:04:18 -08:00
|
|
|
//ref everything in the list to be sure...
|
|
|
|
EINA_LIST_FOREACH(class_vars.outputs, out, eo_obj) {
|
|
|
|
eo_ref(eo_obj);
|
|
|
|
}
|
|
|
|
// the callback here can delete things in the list..
|
2013-08-27 07:42:24 -07:00
|
|
|
EINA_LIST_FOREACH(class_vars.outputs, out, eo_obj) {
|
2014-04-02 06:21:57 -07:00
|
|
|
eo_do(eo_obj, eo_event_callback_call(ECORE_AUDIO_OUT_PULSE_EVENT_CONTEXT_FAIL, NULL));
|
2013-08-27 07:42:24 -07:00
|
|
|
}
|
2013-11-09 18:04:18 -08:00
|
|
|
// now unref everything safely
|
|
|
|
EINA_LIST_FOREACH_SAFE(class_vars.outputs, out, tmp, eo_obj) {
|
|
|
|
eo_unref(eo_obj);
|
|
|
|
}
|
2013-08-27 07:42:24 -07:00
|
|
|
}
|
|
|
|
class_vars.state_job = NULL;
|
2013-04-17 10:31:38 -07:00
|
|
|
}
|
|
|
|
|
2014-03-27 04:34:01 -07:00
|
|
|
EOLIAN static void
|
|
|
|
_ecore_audio_out_pulse_eo_base_constructor(Eo *eo_obj, Ecore_Audio_Out_Pulse_Data *_pd EINA_UNUSED)
|
2013-04-17 10:31:38 -07:00
|
|
|
{
|
|
|
|
int argc;
|
|
|
|
char **argv;
|
2014-06-02 06:47:59 -07:00
|
|
|
Ecore_Audio_Output *out_obj = eo_data_scope_get(eo_obj, ECORE_AUDIO_OUT_CLASS);
|
2013-04-17 10:31:38 -07:00
|
|
|
|
|
|
|
eo_do_super(eo_obj, MY_CLASS, eo_constructor());
|
2013-04-26 10:32:18 -07:00
|
|
|
|
|
|
|
out_obj->need_writer = EINA_FALSE;
|
2013-04-17 10:31:38 -07:00
|
|
|
|
|
|
|
if (!class_vars.context) {
|
|
|
|
ecore_app_args_get(&argc, &argv);
|
|
|
|
if (!argc) {
|
|
|
|
DBG("Could not get program name, pulse outputs will be named ecore_audio");
|
|
|
|
class_vars.context = pa_context_new(class_vars.api, "ecore_audio");
|
|
|
|
} else {
|
|
|
|
class_vars.context = pa_context_new(class_vars.api, basename(argv[0]));
|
|
|
|
}
|
|
|
|
pa_context_set_state_callback(class_vars.context, _state_cb, NULL);
|
|
|
|
pa_context_connect(class_vars.context, NULL, PA_CONTEXT_NOFLAGS, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
class_vars.outputs = eina_list_append(class_vars.outputs, eo_obj);
|
2013-08-27 07:42:24 -07:00
|
|
|
if (class_vars.state_job) eo_del(class_vars.state_job);
|
|
|
|
class_vars.state_job = ecore_job_add(_state_job, NULL);
|
2013-04-17 10:31:38 -07:00
|
|
|
}
|
|
|
|
|
2014-03-27 04:34:01 -07:00
|
|
|
EOLIAN static void
|
|
|
|
_ecore_audio_out_pulse_eo_base_destructor(Eo *eo_obj, Ecore_Audio_Out_Pulse_Data *_pd EINA_UNUSED)
|
2013-04-17 10:31:38 -07:00
|
|
|
{
|
|
|
|
class_vars.outputs = eina_list_remove(class_vars.outputs, eo_obj);
|
2013-11-09 17:45:33 -08:00
|
|
|
eo_do_super(eo_obj, MY_CLASS, eo_destructor());
|
2013-04-17 10:31:38 -07:00
|
|
|
}
|
|
|
|
|
2014-03-27 04:34:01 -07:00
|
|
|
#include "ecore_audio_out_pulse.eo.c"
|