// Test for interacting with Pulseaudio APIs. // John "Sean" Greenslade, August 2016 #include #include #include #include #include #include static pa_context *context = NULL; static pa_mainloop_api *mainloop_api = NULL; uint8_t done = 0; pa_context_state_t context_state = PA_CONTEXT_CONNECTING; // Stuff from server info. static char default_sink_name[256]; static uint32_t default_sink_index; static char default_sink_mon_name[256]; static uint32_t default_sink_mon_index; static char pianobar_sink_input_name[256] = "libao[pianobar] playback stream"; //static char pianobar_sink_input_name[256] = "audio stream"; static unsigned int pb_found = 0; static uint32_t pianobar_sink_input_index; static void exit_signal_callback(pa_mainloop_api *m, pa_signal_event *e, int sig, void *userdata) { printf("Interrupt received...\n"); exit(0); } static void context_state_callback(pa_context *c, void *userdata) { context_state = pa_context_get_state(c); } static void get_server_info_callback(pa_context *c, const pa_server_info *i, void *userdata) { if(!i) { printf("Error in retreiving server info!\n"); exit(1); } strncpy(default_sink_name, i->default_sink_name, 255); } static void get_sink_info_callback(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { if(!eol) { if(!i) { printf("Error in retreiving sink info!\n"); exit(1); } default_sink_index = i->index; default_sink_mon_index = i->monitor_source; strncpy(default_sink_mon_name, i->monitor_source_name, 255); } } static void get_sink_input_info_list_callback(pa_context *c, const pa_sink_input_info *i, int eol, void *userdata) { if(!eol) { if(!i) { printf("Error in retreiving sink input info!\n"); exit(1); } printf("%03d - %02d - %s\n", i->index, i->sink, i->name); if(!strncmp(pianobar_sink_input_name, i->name, strlen(pianobar_sink_input_name))) { pianobar_sink_input_index = i->index; pb_found = 1; printf("Found it: %05d\n", pianobar_sink_input_index); } } } static void mon_stream_status_callback(pa_stream *p, void *userdata) { printf("Stream changed state - %d...\n", pa_stream_get_state(p)); if(pa_stream_get_state(p) == PA_STREAM_TERMINATED) printf("Terminated...\n"); if(pa_stream_get_state(p) == PA_STREAM_FAILED) printf("Failed...\n"); } static void mon_stream_read_callback(pa_stream *p, size_t nbytes, void *userdata) { const void *data; size_t nb; pa_stream_peek(p, &data, &nb); if(nbytes) pa_stream_drop(p); if(nb) { //printf("%6i %6i\n", ((const int16_t*)data)[0], ((const int16_t*)data)[1]); //printf("%6i\n", ((const int16_t*)data)[0]/2048); printf("|"); for(int i=0; i<((const int16_t*)data)[0]/1024; i++) { printf("#"); } for(int i=0; i<32-((const int16_t*)data)[0]/1024; i++) { printf(" "); } for(int i=0; i<33-((const int16_t*)data)[1]/1024; i++) { printf(" "); } for(int i=0; i<((const int16_t*)data)[1]/1024; i++) { printf("#"); } printf("|\n"); } if(nbytes > 4) // Discard excess. { printf("BAD\n"); } } int main(int argc, char **argv) { int r; pa_mainloop *m = NULL; printf("PA tester. Connecting to server...\n"); m = pa_mainloop_new(); if(!m) { printf("Mainloop error!\n"); goto cleanup; } mainloop_api = pa_mainloop_get_api(m); context = pa_context_new(mainloop_api, "test_app"); if(!context) { printf("Context error!\n"); goto cleanup; } pa_context_set_state_callback(context, context_state_callback, NULL); r = pa_context_connect(context, NULL, 0, NULL); if(r < 0) { printf("Context connect failed!\n"); goto cleanup; } r = pa_signal_init(mainloop_api); if(r) { printf("Signal error!\n"); goto cleanup; } pa_signal_new(SIGINT, exit_signal_callback, NULL); pa_operation *op = NULL; uint8_t state = 0; pa_stream *mon_stream = NULL; pa_sample_spec ss = { .format = PA_SAMPLE_S16LE, .rate = 30, .channels = 2 }; pa_buffer_attr bufa = { .maxlength = UINT32_MAX, .fragsize = 1, }; // Main loop. while(!done) { if(context_state == PA_CONTEXT_FAILED || context_state == PA_CONTEXT_TERMINATED) goto cleanup; if(context_state != PA_CONTEXT_READY) goto proc; switch(state) { case 0: // Get default sink name. if(!op) { op = pa_context_get_server_info(context, get_server_info_callback, NULL); goto proc; } else if(pa_operation_get_state(op) == PA_OPERATION_RUNNING) goto proc; else { printf("Default sink: %s\n", default_sink_name); pa_operation_unref(op); op = NULL; state++; } break; case 1: // Get index number of default sink. if(!op) { op = pa_context_get_sink_info_by_name(context, default_sink_name, get_sink_info_callback, NULL); goto proc; } else if(pa_operation_get_state(op) == PA_OPERATION_RUNNING) goto proc; else { printf("Sink index: %03d\n", default_sink_index); printf("Mon index: %03d - %s\n", default_sink_mon_index, default_sink_mon_name); pa_operation_unref(op); op = NULL; state++; } break; case 2: // Get list of sink inputs. if(!op) { op = pa_context_get_sink_input_info_list(context, get_sink_input_info_list_callback, NULL); goto proc; } else if(pa_operation_get_state(op) == PA_OPERATION_RUNNING) goto proc; else { pa_operation_unref(op); op = NULL; state++; } break; case 3: // Ensure we have the sink input we want. if(!pb_found) { sleep(1); printf("Looking again...\n"); state = 2; } else state++; break; case 4: // Ready peak monitor stream. mon_stream = pa_stream_new(context, "monitor peak detect demo", &ss, NULL); pa_stream_set_state_callback(mon_stream, mon_stream_status_callback, NULL); pa_stream_set_read_callback(mon_stream, mon_stream_read_callback, NULL); pa_stream_set_monitor_stream(mon_stream, pianobar_sink_input_index); pa_stream_connect_record(mon_stream, default_sink_mon_name, &bufa, PA_STREAM_PEAK_DETECT); state++; break; case 5: // We can now do blocking iterations, since every event is now tied to a PA callback. pa_mainloop_iterate(m, 1, 0); break; } proc: // Process PA events. if(pa_mainloop_iterate(m, 0, 0) < 0) { printf("Main loop error!\n"); goto cleanup; } } cleanup: if(context) pa_context_disconnect(context); if(m) { pa_signal_done(); pa_mainloop_free(m); } return 0; }