Last active
October 22, 2019 18:09
-
-
Save mk-fg/7c3bc67ec8a13541182e0139f0a1a5b4 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Compile & run with: gcc -O1 -o test -lpulse test.c && ./test | |
#include <stdio.h> | |
#include <unistd.h> | |
#include <string.h> | |
#include <time.h> | |
#include <error.h> | |
#include <pulse/context.h> | |
#include <pulse/mainloop.h> | |
#include <pulse/introspect.h> | |
#include <pulse/proplist.h> | |
#include <pulse/stream.h> | |
#define err(n, ...) \ | |
error_at_line(n, 0, __FILE__, __LINE__, __VA_ARGS__) | |
#define p(...) do { \ | |
printf(__VA_ARGS__); \ | |
fflush(stdout); \ | |
} while(0) | |
int st_connected = 0; | |
void st_callback(pa_context *c, void *userdata) { | |
pa_context_state_t st = pa_context_get_state(c); | |
if (st < PA_CONTEXT_READY) return; | |
if (st == PA_CONTEXT_READY) st_connected = 1; | |
else err(37, "ctx state [%d]", st); | |
} | |
char *bin_match = "mpv"; | |
int info_done = 0, info_found = 0; | |
uint32_t sink_input_idx, sink_idx, src_idx; | |
volatile uint32_t sink_owner; // random metadata to query | |
void info_callback_find_sink_input( pa_context *c, | |
const pa_sink_input_info *info, int eol, void *userdata ) { | |
if (eol) { | |
info_done = 1; | |
return; } | |
const char *bin = pa_proplist_gets( info->proplist, | |
PA_PROP_APPLICATION_PROCESS_BINARY ); | |
if (strcmp(bin, bin_match) != 0) return; | |
sink_input_idx = info->index; | |
sink_idx = info->sink; | |
info_found = 1; | |
} | |
void info_callback_query_sink( pa_context *c, | |
const pa_sink_info *info, int eol, void *userdata ) { | |
if (eol) { | |
info_done = 1; | |
return; } | |
src_idx = info->monitor_source; | |
sink_owner = info->owner_module; | |
info_found = 1; | |
} | |
float peak = 0; | |
void read_callback(pa_stream *s, size_t bs, void *userdata) { | |
const void *data; | |
if (pa_stream_peek(s, &data, &bs) < 0) err(50, "stream_peek"); | |
if (!data) { | |
if (bs) | |
if (pa_stream_drop(s)) err(51, "stream_drop"); | |
return; } | |
float v = ((const float*) data)[bs / sizeof(float) -1]; | |
if (pa_stream_drop(s)) err(52, "stream_drop"); | |
if (v < 0) return; | |
if (v > peak) peak = v; | |
} | |
uint64_t ts0 = 0; | |
uint32_t mono_time_ms() { | |
struct timespec tp; | |
if (clock_gettime(CLOCK_MONOTONIC, &tp) < 0) err(48, "clock"); | |
uint64_t ts = tp.tv_sec * 1000 + tp.tv_nsec / 1000000; | |
if (!ts0) ts0 = ts; | |
return (uint32_t) (ts - ts0); | |
} | |
int main(int argc, char*argv[]) { | |
pa_mainloop *m = pa_mainloop_new(); | |
pa_mainloop_api *api = pa_mainloop_get_api(m); | |
pa_context *c = pa_context_new(api, "stream-info-pointer-test"); | |
pa_context_set_state_callback(c, st_callback, NULL); | |
// Connect | |
if (pa_context_connect(c, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL) < 0) | |
err(38, "connect"); | |
while (!st_connected) | |
if (pa_mainloop_iterate(m, 1, NULL) <= 0) err(39, "connect-iter"); | |
// Find sink input index | |
while (1) { | |
info_done = 0; | |
pa_operation *op = | |
pa_context_get_sink_input_info_list( | |
c, info_callback_find_sink_input, NULL ); | |
while (!info_done) | |
if (pa_mainloop_iterate(m, 1, NULL) <= 0) | |
err(40, "sink_input_info_list"); | |
pa_operation_unref(op); | |
if (info_found) break; | |
if (sleep(3)) err(41, "sink_input_info_list sleep"); | |
} | |
// stream + get_sink_info_by_index loop | |
int iter_n = 0; | |
peaks_and_info_loop: while (1) { | |
// Query sink_idx for monitor_source | |
info_found = 0; info_done = 0; | |
pa_operation *op = | |
pa_context_get_sink_info_by_index( | |
c, sink_idx, info_callback_query_sink, NULL ); | |
while (!info_done) | |
if (pa_mainloop_iterate(m, 1, NULL) <= 0) err(42, "sink_info_list"); | |
pa_operation_unref(op); | |
if (!info_found) err(43, "sink_idx info not found"); | |
// Init stream | |
pa_sample_spec ss = { | |
.format = PA_SAMPLE_FLOAT32LE, | |
.rate = 25, | |
.channels = 1 }; | |
pa_proplist *proplist = pa_proplist_from_string( | |
"application.id=org.PulseAudio.pavucontrol" ); | |
if (!proplist) err(44, "stream-proplist"); | |
pa_stream *s = | |
pa_stream_new_with_proplist(c, "peak detect", &ss, NULL, proplist); | |
pa_proplist_free(proplist); | |
if (!s) err(45, "stream"); | |
if (pa_stream_set_monitor_stream(s, sink_input_idx) < 0) | |
err(46, "set_monitor_stream"); | |
pa_stream_set_read_callback(s, read_callback, NULL); | |
pa_buffer_attr attr; | |
memset(&attr, 0, sizeof(attr)); | |
attr.fragsize = sizeof(float); | |
attr.maxlength = (uint32_t) -1; | |
pa_stream_flags_t flags = PA_STREAM_DONT_MOVE | | |
PA_STREAM_PEAK_DETECT | PA_STREAM_ADJUST_LATENCY | | |
PA_STREAM_DONT_INHIBIT_AUTO_SUSPEND ; | |
char src_idx_str[16]; | |
snprintf(src_idx_str, sizeof(src_idx_str), "%u", src_idx); | |
if (pa_stream_connect_record(s, src_idx_str, &attr, flags) < 0) | |
err(47, "stream_connect_record"); | |
// Poll for peak values | |
peak = 0; | |
uint32_t ts1 = mono_time_ms() + 200; // 0.2s | |
while (1) { | |
uint32_t ts = mono_time_ms(); | |
if (ts >= ts1) break; | |
if ( pa_mainloop_prepare(m, ts1 - ts) < 0 || | |
pa_mainloop_poll(m) < 0 || | |
pa_mainloop_dispatch(m) < 0 ) | |
err(49, "poll"); } | |
if (pa_stream_disconnect(s)) err(53, "stream_disconnect"); | |
iter_n++; | |
p("peak value [%03d]: %.5f\n", iter_n, peak); | |
} // peaks_and_info_loop | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment