From 9444e5d55d783bdcc79e20998876d9b0cf6504c0 Mon Sep 17 00:00:00 2001 From: "Carsten Haitzler (Rasterman)" Date: Tue, 30 Mar 2021 01:09:06 +0100 Subject: [PATCH] e mixer - add ability to monitor streams with vu meters too it's generic infra where you can get a general waveform (44.1khz stereo floats per strea) for playback (sink inputs), output (sinks) and inputs (sources). the emix gui will put a vu meter (actually it's not a vbu meter - it's a peak sample measure over any frame period) with a progress bar there. very useful now. @feat --- src/modules/mixer/backend.c | 2 +- src/modules/mixer/emixer.c | 228 +++++++++++- src/modules/mixer/lib/backends/alsa/alsa.c | 5 +- .../mixer/lib/backends/pulseaudio/pulse.c | 338 +++++++++++++++++- src/modules/mixer/lib/emix.c | 38 +- src/modules/mixer/lib/emix.h | 27 +- 6 files changed, 621 insertions(+), 17 deletions(-) diff --git a/src/modules/mixer/backend.c b/src/modules/mixer/backend.c index 559fb2514..b08693dc8 100644 --- a/src/modules/mixer/backend.c +++ b/src/modules/mixer/backend.c @@ -1248,7 +1248,7 @@ backend_shutdown(void) } - emix_event_callback_del(_events_cb); + emix_event_callback_del(_events_cb, NULL); emix_shutdown(); emix_config_shutdown(); diff --git a/src/modules/mixer/emixer.c b/src/modules/mixer/emixer.c index 2ddf094ff..e9554818e 100644 --- a/src/modules/mixer/emixer.c +++ b/src/modules/mixer/emixer.c @@ -22,6 +22,123 @@ Eina_List *source_list = NULL, *sink_input_list = NULL, *sink_list = NULL, *card ////////////////////////////////////////////////////////////////////////////// +typedef struct _Mon_Data +{ + Emix_Sink *sink; + Emix_Sink_Input *input; + Emix_Source *source; + Evas_Object *fr; + Evas_Object *vu; + Ecore_Animator *animator; + float samp_max; + int mon_skips; + int mon_update; + int mon_samps; +} Mon_Data; + +static Eina_List *_monitor_data_list = NULL; + +static Eina_Bool +_cb_emix_monitor_update(void *data) +{ + Mon_Data *md = data; + + if (md->mon_update == 0) + { + md->mon_skips++; + if (md->mon_skips > 5) + { + elm_progressbar_value_set(md->vu, 0.0); + md->animator = NULL; + return EINA_FALSE; + } + return EINA_TRUE; + } + elm_progressbar_value_set(md->vu, md->samp_max); + md->mon_update = 0; + md->samp_max = 0; + md->mon_skips = 0; + md->mon_samps = 0; + return EINA_TRUE; +} + +static void +_cb_emix_sink_monitor_event(void *data, enum Emix_Event event, void *event_info) +{ + Mon_Data *md = data; + Emix_Sink *sink = event_info; + + if (sink != md->sink) return; + if (event == EMIX_SINK_MONITOR_EVENT) + { + unsigned int i, num = sink->mon_num * 2; + float samp, max = 0.0; + + for (i = 0; i < num; i++) + { + samp = fabs(sink->mon_buf[i]); + if (samp > max) max = samp; + } + md->mon_samps += num; + if (md->samp_max < max) md->samp_max = max; + md->mon_update++; + if (!md->animator) + md->animator = ecore_animator_add(_cb_emix_monitor_update, md); + } +} + +static void +_cb_emix_sink_input_monitor_event(void *data, enum Emix_Event event, void *event_info) +{ + Mon_Data *md = data; + Emix_Sink_Input *input = event_info; + + if (input != md->input) return; + if (event == EMIX_SINK_INPUT_MONITOR_EVENT) + { + unsigned int i, num = input->mon_num * 2; + float samp, max = 0.0; + + for (i = 0; i < num; i++) + { + samp = fabs(input->mon_buf[i]); + if (samp > max) max = samp; + } + md->mon_samps += num; + if (md->samp_max < max) md->samp_max = max; + md->mon_update++; + if (!md->animator) + md->animator = ecore_animator_add(_cb_emix_monitor_update, md); + } +} + +static void +_cb_emix_source_monitor_event(void *data, enum Emix_Event event, void *event_info) +{ + Mon_Data *md = data; + Emix_Source *source = event_info; + + if (source != md->source) return; + if (event == EMIX_SOURCE_MONITOR_EVENT) + { + unsigned int i, num = source->mon_num * 2; + float samp, max = 0.0; + + for (i = 0; i < num; i++) + { + samp = fabs(source->mon_buf[i]); + if (samp > max) max = samp; + } + md->mon_samps += num; + if (md->samp_max < max) md->samp_max = max; + md->mon_update++; + if (!md->animator) + md->animator = ecore_animator_add(_cb_emix_monitor_update, md); + } +} + +////////////////////////////////////////////////////////////////////////////// + static void _emix_sink_volume_fill(Emix_Sink *sink, Evas_Object *bxv, Evas_Object *bx, Eina_Bool locked); static Evas_Object *_icon(Evas_Object *base, const char *name); @@ -40,6 +157,12 @@ _backend_init(const char *back) return EINA_FALSE; } +static char * +_cb_vu_format_cb(double v EINA_UNUSED) +{ + return ""; +} + static void _cb_sink_port_change(void *data, Evas_Object *obj, @@ -250,10 +373,11 @@ _emix_sink_volume_fill(Emix_Sink *sink, Evas_Object *fr, Evas_Object *bx, Eina_B static void _emix_sink_add(Emix_Sink *sink) { - Evas_Object *bxv, *bx, *lb, *hv, *fr, *sep; + Evas_Object *bxv, *bx, *lb, *hv, *fr, *sep, *vu; const Eina_List *l; Emix_Port *port; Eina_Bool locked = EINA_TRUE; + Mon_Data *md; unsigned int i; fr = elm_frame_add(win); @@ -323,6 +447,14 @@ _emix_sink_add(Emix_Sink *sink) elm_box_pack_end(sink_box, fr); evas_object_show(fr); + vu = elm_progressbar_add(win); + elm_progressbar_unit_format_function_set(vu, _cb_vu_format_cb, NULL); + evas_object_data_set(fr, "vu", vu); + evas_object_size_hint_weight_set(vu, EVAS_HINT_EXPAND, 0.0); + evas_object_size_hint_align_set(vu, EVAS_HINT_FILL, 0.0); + elm_box_pack_end(sink_box, vu); + evas_object_show(vu); + sep = elm_separator_add(win); evas_object_data_set(fr, "extra", sep); elm_separator_horizontal_set(sep, EINA_TRUE); @@ -330,19 +462,44 @@ _emix_sink_add(Emix_Sink *sink) evas_object_size_hint_align_set(sep, EVAS_HINT_FILL, 0.0); elm_box_pack_end(sink_box, sep); evas_object_show(sep); + + md = calloc(1, sizeof(Mon_Data)); + if (md) + { + md->sink = sink; + md->fr = fr; + md->vu = vu; + emix_event_callback_add(_cb_emix_sink_monitor_event, md); + _monitor_data_list = eina_list_append(_monitor_data_list, md); + } + emix_sink_monitor(sink, EINA_TRUE); } static void _emix_sink_del(Emix_Sink *sink) { - Eina_List *l; + Eina_List *l, *ll; Evas_Object *fr; + Mon_Data *md; + + emix_sink_monitor(sink, EINA_FALSE); + EINA_LIST_FOREACH_SAFE(_monitor_data_list, l, ll, md) + { + if (md->sink == sink) + { + emix_event_callback_del(_cb_emix_sink_monitor_event, md); + _monitor_data_list = eina_list_remove_list(_monitor_data_list, l); + if (md->animator) ecore_animator_del(md->animator); + free(md); + } + } EINA_LIST_FOREACH(sink_list, l, fr) { if (evas_object_data_get(fr, "sink") == sink) { sink_list = eina_list_remove_list(sink_list, l); evas_object_del(evas_object_data_get(fr, "extra")); + evas_object_del(evas_object_data_get(fr, "vu")); evas_object_del(fr); return; } @@ -626,10 +783,11 @@ _emix_sink_input_volume_fill(Emix_Sink_Input *input, Evas_Object *fr, Evas_Objec static void _emix_sink_input_add(Emix_Sink_Input *input) { - Evas_Object *bxv, *bx, *lb, *hv, *ic, *fr, *sep; + Evas_Object *bxv, *bx, *lb, *hv, *ic, *fr, *sep, *vu; const Eina_List *l; Emix_Sink *sink; Eina_Bool locked = EINA_TRUE; + Mon_Data *md; unsigned int i; if (!input->sink) return; @@ -717,6 +875,14 @@ _emix_sink_input_add(Emix_Sink_Input *input) elm_box_pack_end(sink_input_box, fr); evas_object_show(fr); + vu = elm_progressbar_add(win); + elm_progressbar_unit_format_function_set(vu, _cb_vu_format_cb, NULL); + evas_object_data_set(fr, "vu", vu); + evas_object_size_hint_weight_set(vu, EVAS_HINT_EXPAND, 0.0); + evas_object_size_hint_align_set(vu, EVAS_HINT_FILL, 0.0); + elm_box_pack_end(sink_input_box, vu); + evas_object_show(vu); + sep = elm_separator_add(win); evas_object_data_set(fr, "extra", sep); elm_separator_horizontal_set(sep, EINA_TRUE); @@ -724,19 +890,44 @@ _emix_sink_input_add(Emix_Sink_Input *input) evas_object_size_hint_align_set(sep, EVAS_HINT_FILL, 0.0); elm_box_pack_end(sink_input_box, sep); evas_object_show(sep); + + md = calloc(1, sizeof(Mon_Data)); + if (md) + { + md->input = input; + md->fr = fr; + md->vu = vu; + emix_event_callback_add(_cb_emix_sink_input_monitor_event, md); + _monitor_data_list = eina_list_append(_monitor_data_list, md); + } + emix_sink_input_monitor(input, EINA_TRUE); } static void _emix_sink_input_del(Emix_Sink_Input *input) { - Eina_List *l; + Eina_List *l, *ll; Evas_Object *fr; + Mon_Data *md; + + emix_sink_input_monitor(input, EINA_FALSE); + EINA_LIST_FOREACH_SAFE(_monitor_data_list, l, ll, md) + { + if (md->input == input) + { + emix_event_callback_del(_cb_emix_sink_input_monitor_event, md); + _monitor_data_list = eina_list_remove_list(_monitor_data_list, l); + if (md->animator) ecore_animator_del(md->animator); + free(md); + } + } EINA_LIST_FOREACH(sink_input_list, l, fr) { if (evas_object_data_get(fr, "input") == input) { sink_input_list = eina_list_remove_list(sink_input_list, l); evas_object_del(evas_object_data_get(fr, "extra")); + evas_object_del(evas_object_data_get(fr, "vu")); evas_object_del(fr); return; } @@ -1026,9 +1217,10 @@ _emix_source_volume_fill(Emix_Source *source, Evas_Object *fr, Evas_Object *bx, static void _emix_source_add(Emix_Source *source) { - Evas_Object *bxv, *bx, *fr, *lb, *sep; + Evas_Object *bxv, *bx, *fr, *lb, *sep, *vu; unsigned int i; Eina_Bool locked = EINA_TRUE; + Mon_Data *md; fr = elm_frame_add(win); evas_object_size_hint_weight_set(fr, EVAS_HINT_EXPAND, 1.0); @@ -1081,6 +1273,14 @@ _emix_source_add(Emix_Source *source) elm_box_pack_end(source_box, fr); evas_object_show(fr); + vu = elm_progressbar_add(win); + elm_progressbar_unit_format_function_set(vu, _cb_vu_format_cb, NULL); + evas_object_data_set(fr, "vu", vu); + evas_object_size_hint_weight_set(vu, EVAS_HINT_EXPAND, 0.0); + evas_object_size_hint_align_set(vu, EVAS_HINT_FILL, 0.0); + elm_box_pack_end(source_box, vu); + evas_object_show(vu); + sep = elm_separator_add(win); evas_object_data_set(fr, "extra", sep); elm_separator_horizontal_set(sep, EINA_TRUE); @@ -1088,6 +1288,17 @@ _emix_source_add(Emix_Source *source) evas_object_size_hint_align_set(sep, EVAS_HINT_FILL, 0.0); elm_box_pack_end(source_box, sep); evas_object_show(sep); + + md = calloc(1, sizeof(Mon_Data)); + if (md) + { + md->source = source; + md->fr = fr; + md->vu = vu; + emix_event_callback_add(_cb_emix_source_monitor_event, md); + _monitor_data_list = eina_list_append(_monitor_data_list, md); + } + emix_source_monitor(source, EINA_TRUE); } static void @@ -1101,6 +1312,7 @@ _emix_source_del(Emix_Source *source) { source_list = eina_list_remove_list(source_list, l); evas_object_del(evas_object_data_get(fr, "extra")); + evas_object_del(evas_object_data_get(fr, "vu")); evas_object_del(fr); return; } @@ -1322,6 +1534,12 @@ _cb_emix_event(void *data EINA_UNUSED, enum Emix_Event event, void *event_info) case EMIX_CARD_CHANGED_EVENT: _emix_card_change(event_info); break; + case EMIX_SINK_MONITOR_EVENT: + break; + case EMIX_SINK_INPUT_MONITOR_EVENT: + break; + case EMIX_SOURCE_MONITOR_EVENT: + break; default: break; } diff --git a/src/modules/mixer/lib/backends/alsa/alsa.c b/src/modules/mixer/lib/backends/alsa/alsa.c index 894b1115e..465f08094 100644 --- a/src/modules/mixer/lib/backends/alsa/alsa.c +++ b/src/modules/mixer/lib/backends/alsa/alsa.c @@ -537,7 +537,10 @@ _alsa_backend = _alsa_sources_volume_set, /* source volume set */ NULL, /* advanced options */ NULL, /* card list */ - NULL /* card profile set */ + NULL, /* card profile set */ + NULL, /* sink monitor set */ + NULL, /* sink input monitor set */ + NULL /* ssource monitor set */ }; E_API Emix_Backend * diff --git a/src/modules/mixer/lib/backends/pulseaudio/pulse.c b/src/modules/mixer/lib/backends/pulseaudio/pulse.c index ac18a74f0..75caff59b 100644 --- a/src/modules/mixer/lib/backends/pulseaudio/pulse.c +++ b/src/modules/mixer/lib/backends/pulseaudio/pulse.c @@ -33,14 +33,21 @@ typedef struct _Context typedef struct _Sink { Emix_Sink base; - int idx; + int idx, monitor_idx; const char *pulse_name; + const char *monitor_source_name; + int mon_count; + pa_stream *mon_stream; + Eina_Bool running : 1; } Sink; typedef struct _Sink_Input { Emix_Sink_Input base; - int idx; + int idx, sink_idx; + int mon_count; + pa_stream *mon_stream; + Eina_Bool running : 1; } Sink_Input; typedef struct _Source @@ -48,6 +55,8 @@ typedef struct _Source Emix_Source base; int idx; const char *pulse_name; + int mon_count; + pa_stream *mon_stream; } Source; typedef struct _Profile @@ -65,6 +74,13 @@ typedef struct _Card static Context *ctx = NULL; extern pa_mainloop_api functable; +static void _sink_monitor_begin(Sink *sink); +static void _sink_monitor_end(Sink *sink); +static void _sink_input_monitor_begin(Sink_Input *i); +static void _sink_input_monitor_end(Sink_Input *i); +static void _source_monitor_begin(Source *s); +static void _source_monitor_end(Source *s); + static pa_cvolume _emix_volume_convert(const Emix_Volume *volume) { @@ -116,6 +132,8 @@ _sink_del(Sink *sink) free(sink->base.volume.channel_names); eina_stringshare_del(sink->base.name); eina_stringshare_del(sink->pulse_name); + eina_stringshare_del(sink->monitor_source_name); + if (sink->mon_stream) pa_stream_disconnect(sink->mon_stream); free(sink); } @@ -131,6 +149,7 @@ _sink_input_del(Sink_Input *input) free(input->base.volume.channel_names); eina_stringshare_del(input->base.name); eina_stringshare_del(input->base.icon); + if (input->mon_stream) pa_stream_disconnect(input->mon_stream); free(input); } @@ -165,6 +184,30 @@ _card_del(Card *card) free(card); } +static void +_sink_state_running_set(Sink *sink, Eina_Bool running) +{ + if (running) + { + if ((!sink->running) && (sink->mon_count > 0)) + { + sink->running = running; + _sink_monitor_begin(sink); + return; + } + } + else + { + if ((sink->running) && (sink->mon_count > 0)) + { + sink->running = running; + _sink_monitor_end(sink); + return; + } + } + sink->running = running; +} + static void _sink_cb(pa_context *c EINA_UNUSED, const pa_sink_info *info, int eol, void *userdata EINA_UNUSED) @@ -190,6 +233,7 @@ _sink_cb(pa_context *c EINA_UNUSED, const pa_sink_info *info, int eol, sink = calloc(1, sizeof(Sink)); sink->idx = info->index; + sink->monitor_idx = info->monitor_source; sink->pulse_name = eina_stringshare_add(info->name); sink->base.name = eina_stringshare_add(info->description); _pa_cvolume_convert(&info->volume, &sink->base.volume); @@ -197,6 +241,7 @@ _sink_cb(pa_context *c EINA_UNUSED, const pa_sink_info *info, int eol, for (i = 0; i < sink->base.volume.channel_count; ++i) sink->base.volume.channel_names[i] = eina_stringshare_add(pa_channel_position_to_pretty_string(info->channel_map.map[i])); sink->base.mute = !!info->mute; + sink->monitor_source_name = eina_stringshare_add(info->monitor_source_name); for (i = 0; i < info->n_ports; i++) { @@ -214,7 +259,8 @@ _sink_cb(pa_context *c EINA_UNUSED, const pa_sink_info *info, int eol, if (info->ports[i]->name == info->active_port->name) port->active = EINA_TRUE; } - + if (info->state == PA_SINK_RUNNING) _sink_state_running_set(sink, EINA_TRUE); + else _sink_state_running_set(sink, EINA_FALSE); ctx->sinks = eina_list_append(ctx->sinks, sink); if (ctx->cb) ctx->cb((void *)ctx->userdata, EMIX_SINK_ADDED_EVENT, @@ -297,6 +343,11 @@ _sink_changed_cb(pa_context *c EINA_UNUSED, const pa_sink_info *info, int eol, port->active = EINA_TRUE; } + sink->monitor_idx = info->monitor_source; + + if (info->state == PA_SINK_RUNNING) _sink_state_running_set(sink, EINA_TRUE); + else _sink_state_running_set(sink, EINA_FALSE); + if (ctx->cb) ctx->cb((void *)ctx->userdata, EMIX_SINK_CHANGED_EVENT, (Emix_Sink *)sink); @@ -357,6 +408,30 @@ _icon_from_properties(pa_proplist *l) return "audio-card"; } +static void +_sink_input_state_running_set(Sink_Input *input, Eina_Bool running) +{ + if (running) + { + if ((!input->running) && (input->mon_count > 0)) + { + input->running = running; + _sink_input_monitor_begin(input); + return; + } + } + else + { + if ((input->running) && (input->mon_count > 0)) + { + input->running = running; + _sink_input_monitor_end(input); + return; + } + } + input->running = running; +} + static void _sink_input_cb(pa_context *c EINA_UNUSED, const pa_sink_input_info *info, int eol, void *userdata EINA_UNUSED) @@ -387,6 +462,7 @@ _sink_input_cb(pa_context *c EINA_UNUSED, const pa_sink_input_info *info, info->name); input->idx = info->index; + input->sink_idx = info->sink; Eina_Strbuf *input_name; @@ -421,6 +497,8 @@ _sink_input_cb(pa_context *c EINA_UNUSED, const pa_sink_input_info *info, { input->base.pid = atoi(t); } + if (!info->corked) _sink_input_state_running_set(input, EINA_TRUE); + else _sink_input_state_running_set(input, EINA_FALSE); if (ctx->cb) ctx->cb((void *)ctx->userdata, EMIX_SINK_INPUT_ADDED_EVENT, @@ -469,6 +547,7 @@ _sink_input_changed_cb(pa_context *c EINA_UNUSED, ctx->inputs = eina_list_append(ctx->inputs, input); } input->idx = info->index; + input->sink_idx = info->sink; if (input->base.volume.channel_count != info->volume.channels) { for (i = 0; i < input->base.volume.channel_count; ++i) @@ -493,6 +572,9 @@ _sink_input_changed_cb(pa_context *c EINA_UNUSED, input->base.pid = atoi(t); } + if (!info->corked) _sink_input_state_running_set(input, EINA_TRUE); + else _sink_input_state_running_set(input, EINA_FALSE); + if (ctx->cb) ctx->cb((void *)ctx->userdata, EMIX_SINK_INPUT_CHANGED_EVENT, (Emix_Sink_Input *)input); @@ -529,6 +611,7 @@ _source_cb(pa_context *c EINA_UNUSED, const pa_source_info *info, { Source *source; unsigned int i; + size_t len; EINA_SAFETY_ON_NULL_RETURN(ctx); if (eol < 0) @@ -543,6 +626,13 @@ _source_cb(pa_context *c EINA_UNUSED, const pa_source_info *info, if (eol > 0) return; + len = strlen(info->name); + if (len > 8) + { + const char *s = info->name + len - 8; + if (!strcmp(s, ".monitor")) return; + } + source = calloc(1, sizeof(Source)); EINA_SAFETY_ON_NULL_RETURN(source); @@ -600,7 +690,7 @@ _source_changed_cb(pa_context *c EINA_UNUSED, EINA_SAFETY_ON_NULL_RETURN(source); ctx->sources = eina_list_append(ctx->sources, source); } - source->idx= info->index; + source->idx = info->index; if (source->base.volume.channel_count != info->volume.channels) { for (i = 0; i < source->base.volume.channel_count; ++i) @@ -1522,6 +1612,241 @@ _card_profile_set(Emix_Card *card, const Emix_Profile *profile) return EINA_TRUE; } +static void +_sink_mon_read(pa_stream *stream, size_t bytes EINA_UNUSED, void *data) +{ + Sink *s = data; + size_t rbytes; + const void *buf = NULL; + + if (pa_stream_peek(stream, &buf, &rbytes) == 0) + { + if ((!buf) && (rbytes)) + { + pa_stream_drop(stream); + return; + } + s->base.mon_num = (unsigned int)((rbytes / sizeof(float)) / 2); + s->base.mon_buf = buf; + if (ctx->cb) + ctx->cb((void *)ctx->userdata, EMIX_SINK_MONITOR_EVENT, s); + pa_stream_drop(stream); + } +} + +static void +_sink_monitor_begin(Sink *s) +{ + pa_sample_spec samp; + pa_buffer_attr attr; + + if (pa_context_get_server_protocol_version(ctx->context) < 13) return; + + pa_sample_spec_init(&samp); + samp.format = PA_SAMPLE_FLOAT32; + samp.rate = 44100; + samp.channels = 2; + memset(&attr, 0, sizeof(attr)); + attr.fragsize = sizeof(float) * 4096; + attr.maxlength = (uint32_t) -1; + + s->mon_stream = pa_stream_new(ctx->context, "__e_mon", &samp, NULL); + pa_stream_set_read_callback(s->mon_stream, _sink_mon_read, s); + pa_stream_connect_record(s->mon_stream, s->monitor_source_name, + &attr, + PA_STREAM_NOFLAGS + | PA_STREAM_DONT_MOVE + | PA_STREAM_ADJUST_LATENCY + | PA_STREAM_DONT_INHIBIT_AUTO_SUSPEND + ); +} + +static void +_sink_monitor_end(Sink *s) +{ + if (s->mon_stream) + { + if (s->mon_stream) pa_stream_disconnect(s->mon_stream); + s->mon_stream = NULL; + } +} + +static void +_sink_monitor_set(Emix_Sink *sink, Eina_Bool monitor) +{ + Sink *s = (Sink *)sink; + EINA_SAFETY_ON_NULL_RETURN(ctx); + if (monitor) s->mon_count++; + else s->mon_count--; + if (s->mon_count < 0) s->mon_count = 0; + if (s->mon_count == 1) + { + if (s->running) _sink_monitor_begin(s); + } + else if (s->mon_count == 0) _sink_monitor_end(s); +} + +static void +_sink_input_mon_read(pa_stream *stream, size_t bytes EINA_UNUSED, void *data) +{ + Sink_Input *i = data; + size_t rbytes; + const void *buf = NULL; + + if (pa_stream_peek(stream, &buf, &rbytes) == 0) + { + if ((!buf) && (rbytes)) + { + pa_stream_drop(stream); + return; + } + i->base.mon_num = (unsigned int)((rbytes / sizeof(float)) / 2); + i->base.mon_buf = buf; + if (ctx->cb) + ctx->cb((void *)ctx->userdata, EMIX_SINK_INPUT_MONITOR_EVENT, i); + pa_stream_drop(stream); + } +} + +static void +_sink_input_monitor_begin(Sink_Input *i) +{ + pa_sample_spec samp; + pa_buffer_attr attr; + char buf[16]; + Eina_List *l; + Sink *s; + unsigned int mon_idx = 0; + + if (pa_context_get_server_protocol_version(ctx->context) < 13) return; + + pa_sample_spec_init(&samp); + samp.format = PA_SAMPLE_FLOAT32; + samp.rate = 44100; + samp.channels = 2; + memset(&attr, 0, sizeof(attr)); + attr.fragsize = sizeof(float) * 4096; + attr.maxlength = (uint32_t) -1; + + i->mon_stream = pa_stream_new(ctx->context, "__e_mon", &samp, NULL); + pa_stream_set_monitor_stream(i->mon_stream, i->idx); + pa_stream_set_read_callback(i->mon_stream, _sink_input_mon_read, i); + EINA_LIST_FOREACH(ctx->sinks, l, s) + { + if (i->sink_idx == s->idx) + { + mon_idx = s->monitor_idx; + break; + } + } + if (!l) return; + snprintf(buf, sizeof(buf), "%i", mon_idx); + pa_stream_connect_record(i->mon_stream, buf, + &attr, + PA_STREAM_NOFLAGS + | PA_STREAM_DONT_MOVE + | PA_STREAM_ADJUST_LATENCY + | PA_STREAM_DONT_INHIBIT_AUTO_SUSPEND + ); +} + +static void +_sink_input_monitor_end(Sink_Input *i) +{ + if (i->mon_stream) + { + if (i->mon_stream) pa_stream_disconnect(i->mon_stream); + i->mon_stream = NULL; + } +} + +static void +_sink_input_monitor_set(Emix_Sink_Input *input, Eina_Bool monitor) +{ + Sink_Input *i = (Sink_Input *)input; + EINA_SAFETY_ON_NULL_RETURN(ctx); + if (monitor) i->mon_count++; + else i->mon_count--; + if (i->mon_count < 0) i->mon_count = 0; + if (i->mon_count == 1) + { + if (i->running) _sink_input_monitor_begin(i); + } + else if (i->mon_count == 0) _sink_input_monitor_end(i); +} + +static void +_source_mon_read(pa_stream *stream, size_t bytes EINA_UNUSED, void *data) +{ + Source *s = data; + size_t rbytes; + const void *buf = NULL; + + if (pa_stream_peek(stream, &buf, &rbytes) == 0) + { + if ((!buf) && (rbytes)) + { + pa_stream_drop(stream); + return; + } + s->base.mon_num = (unsigned int)((rbytes / sizeof(float)) / 2); + s->base.mon_buf = buf; + if (ctx->cb) + ctx->cb((void *)ctx->userdata, EMIX_SOURCE_MONITOR_EVENT, s); + pa_stream_drop(stream); + } +} + +static void +_source_monitor_begin(Source *s) +{ + pa_sample_spec samp; + pa_buffer_attr attr; + char buf[16]; + + if (pa_context_get_server_protocol_version(ctx->context) < 13) return; + + pa_sample_spec_init(&samp); + samp.format = PA_SAMPLE_FLOAT32; + samp.rate = 44100; + samp.channels = 2; + memset(&attr, 0, sizeof(attr)); + attr.fragsize = sizeof(float) * 4096; + attr.maxlength = (uint32_t) -1; + + s->mon_stream = pa_stream_new(ctx->context, "__e_mon", &samp, NULL); + pa_stream_set_read_callback(s->mon_stream, _source_mon_read, s); + snprintf(buf, sizeof(buf), "%i", s->idx); + pa_stream_connect_record(s->mon_stream, buf, + &attr, + PA_STREAM_NOFLAGS + | PA_STREAM_DONT_MOVE + | PA_STREAM_ADJUST_LATENCY + ); +} + +static void +_source_monitor_end(Source *s) +{ + if (s->mon_stream) + { + if (s->mon_stream) pa_stream_disconnect(s->mon_stream); + s->mon_stream = NULL; + } +} + +static void +_source_monitor_set(Emix_Source *source, Eina_Bool monitor) +{ + Source *s = (Source *)source; + EINA_SAFETY_ON_NULL_RETURN(ctx); + if (monitor) s->mon_count++; + else s->mon_count--; + if (s->mon_count < 0) s->mon_count = 0; + if (s->mon_count == 1) _source_monitor_begin(s); + else if (s->mon_count == 0) _source_monitor_end(s); +} + static Emix_Backend _pulseaudio_backend = { @@ -1548,7 +1873,10 @@ _pulseaudio_backend = _source_volume_set, NULL, _cards_get, - _card_profile_set + _card_profile_set, + _sink_monitor_set, + _sink_input_monitor_set, + _source_monitor_set }; E_API Emix_Backend * diff --git a/src/modules/mixer/lib/emix.c b/src/modules/mixer/lib/emix.c index 962e7e535..5fb83aa0e 100644 --- a/src/modules/mixer/lib/emix.c +++ b/src/modules/mixer/lib/emix.c @@ -419,7 +419,7 @@ emix_event_callback_add(Emix_Event_Cb cb, const void *data) } Eina_Bool -emix_event_callback_del(Emix_Event_Cb cb) +emix_event_callback_del(Emix_Event_Cb cb, const void *data) { struct Callback_Data *callback; Eina_List *l; @@ -427,7 +427,7 @@ emix_event_callback_del(Emix_Event_Cb cb) EINA_LIST_FOREACH(ctx->callbacks, l, callback) { - if (callback->cb == cb) + if ((callback->cb == cb) && (callback->data == data)) { ctx->callbacks = eina_list_remove_list(ctx->callbacks, l); free(callback); @@ -456,3 +456,37 @@ emix_card_profile_set(Emix_Card *card, Emix_Profile *profile) return ctx->loaded->ebackend_card_profile_set(card, profile); } + +void +emix_sink_monitor(Emix_Sink *sink, Eina_Bool monitor) +{ + EINA_SAFETY_ON_FALSE_RETURN((ctx && ctx->loaded && + ctx->loaded->ebackend_sink_mute_set && + sink)); + if (!ctx->loaded->ebackend_sink_monitor_set) return; + ctx->loaded->ebackend_sink_monitor_set(sink, monitor); +} + +void +emix_sink_input_monitor(Emix_Sink_Input *input, Eina_Bool monitor) +{ + EINA_SAFETY_ON_FALSE_RETURN((ctx && ctx->loaded && + ctx->loaded->ebackend_sink_mute_set && + input)); + + if (!ctx->loaded->ebackend_sink_input_monitor_set) return; + ctx->loaded->ebackend_sink_input_monitor_set(input, monitor); +} + +void +emix_source_monitor(Emix_Source *source, Eina_Bool monitor) +{ + EINA_SAFETY_ON_FALSE_RETURN((ctx && ctx->loaded && + ctx->loaded->ebackend_sink_mute_set && + source)); + + if (!ctx->loaded->ebackend_source_monitor_set) return; + ctx->loaded->ebackend_source_monitor_set(source, monitor); +} + + diff --git a/src/modules/mixer/lib/emix.h b/src/modules/mixer/lib/emix.h index 5f2512310..1e1387937 100644 --- a/src/modules/mixer/lib/emix.h +++ b/src/modules/mixer/lib/emix.h @@ -39,7 +39,10 @@ enum Emix_Event { EMIX_SOURCE_CHANGED_EVENT, EMIX_CARD_ADDED_EVENT, EMIX_CARD_REMOVED_EVENT, - EMIX_CARD_CHANGED_EVENT + EMIX_CARD_CHANGED_EVENT, + EMIX_SINK_MONITOR_EVENT, + EMIX_SINK_INPUT_MONITOR_EVENT, + EMIX_SOURCE_MONITOR_EVENT, }; typedef const char * Emix_Channel; @@ -64,6 +67,8 @@ typedef struct _Emix_Sink { Eina_Bool mute; Eina_Bool default_sink; Eina_List *ports; + unsigned int mon_num; // number of left + right sample pairs + const float *mon_buf; // LRLRLR unsigned char samples } Emix_Sink; typedef struct _Emix_Sink_Input { @@ -73,6 +78,8 @@ typedef struct _Emix_Sink_Input { Emix_Sink *sink; pid_t pid; const char *icon; + unsigned int mon_num; // number of left + right sample pairs + const float *mon_buf; // LRLRLR unsigned char samples } Emix_Sink_Input; typedef struct _Emix_Source { @@ -80,6 +87,8 @@ typedef struct _Emix_Source { Emix_Volume volume; Eina_Bool mute; Eina_Bool default_source; + unsigned int mon_num; // number of left + right sample pairs + const float *mon_buf; // LRLRLR unsigned char samples } Emix_Source; typedef struct _Emix_Profile { @@ -135,6 +144,13 @@ typedef struct _Emix_Backend { Evas_Object* (*ebackend_advanced_options_add)(Evas_Object *parent); const Eina_List* (*ebackend_cards_get)(void); Eina_Bool (*ebackend_card_profile_set)(Emix_Card *card, const Emix_Profile *profile); + + void (*ebackend_sink_monitor_set)(Emix_Sink *sink, + Eina_Bool monitor); + void (*ebackend_sink_input_monitor_set)(Emix_Sink_Input *input, + Eina_Bool monitor); + void (*ebackend_source_monitor_set)(Emix_Source *source, + Eina_Bool monitor); } Emix_Backend; ////////////////////////////////////////////////////////////////////////////// @@ -160,8 +176,9 @@ E_API const Eina_List* emix_backends_available(void); E_API Eina_Bool emix_backend_set(const char *backend); E_API Eina_Bool emix_event_callback_add(Emix_Event_Cb cb, - const void *data); -E_API Eina_Bool emix_event_callback_del(Emix_Event_Cb cb); + const void *data); +E_API Eina_Bool emix_event_callback_del(Emix_Event_Cb cb, + const void *data); E_API int emix_max_volume_get(void); @@ -196,4 +213,8 @@ E_API Evas_Object* emix_advanced_options_add(Evas_Object *parent); E_API const Eina_List* emix_cards_get(void); E_API Eina_Bool emix_card_profile_set(Emix_Card *card, Emix_Profile *profile); +E_API void emix_sink_monitor(Emix_Sink *sink, Eina_Bool monitor); +E_API void emix_sink_input_monitor(Emix_Sink_Input *input, Eina_Bool monitor); +E_API void emix_source_monitor(Emix_Source *source, Eina_Bool monitor); + #endif /* EMIX_H */