tldr: timer events - am I doing something wrong, or is this feature broken?
When putting together the Rust bindings a few months ago (see separate announcement email), I created a few small test programs to test a handful of features. One of the last features I tried to play with was timer events. These events though seem to always fire immediately, which puzzles me. Yesterday I recreated the primary test program in C, to rule out any Rust binding related issues, and it exhibits the same problem. So question, am I doing something wrong (quite likely), or is this feature broken? I have attached a copy of both the Rust crate test program, and the C version. The test program is very simple, but deserves a word on usage. It requires one cmd-line arg, a file to "play". Whatever file you point it at it pretends contains raw audio, which it serves to PA one second's worth at a time. What I did was to use VLC to capture the raw audio of a video to a file, which I point it at.
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <inttypes.h>
#include <stddef.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdbool.h>
#include <assert.h>
#include <malloc.h>
#include <pulse/def.h>
#include <pulse/sample.h>
#include <pulse/thread-mainloop.h>
#include <pulse/context.h>
#include <pulse/stream.h>
#include <pulse/operation.h>
#include <pulse/error.h>
#include <pulse/mainloop-api.h>
#include <pulse/timeval.h>
void context_state_change_cb(pa_context *context, void *data);
void stream_state_change_cb(pa_stream *stream, void *data);
void drain_cb(pa_stream *stream, int i, void *data);
void timer_event_cb(pa_mainloop_api*a, pa_time_event* e, const struct timeval *tv, void *userdata);
void timer_event_cb_2(pa_mainloop_api*a, pa_time_event* e, const struct timeval *tv, void *userdata);
void timer_event_cb_3(pa_mainloop_api*a, pa_time_event* e, const struct timeval *tv, void *userdata);
void main(int argc, char *argv[]) {
if (argc < 2) {
printf("Error: too few arguments!\n");
printf("Usage: %s FILENAME\n", argv[0]);
return;
}
char *filename = argv[1];
printf("opening: %s\n", filename);
int fd = open(filename, O_RDONLY);
if (!fd) {
printf("Error: failed to open file!\n");
return;
}
pa_sample_spec spec = { PA_SAMPLE_S16NE, 44100, 2 };
assert(pa_sample_spec_valid(&spec));
printf("bytes per second: %i\n", pa_bytes_per_second(&spec));
printf("frame size: %i\n", pa_frame_size(&spec));
printf("sample size: %i\n", pa_sample_size(&spec));
#define BYTES_PER_SEC 176400
#define BUFFER_SIZE BYTES_PER_SEC
char *buffer = malloc(BUFFER_SIZE);
if (!buffer) {
printf("Error: failed to alloc buffer!\n");
return;
}
assert(BUFFER_SIZE % pa_frame_size(&spec) == 0);
pa_threaded_mainloop *mainloop = pa_threaded_mainloop_new();
if (!mainloop) {
printf("Error: failed to create mainloop!\n");
return;
}
static char * APP_NAME = "FooAppContext";
pa_context *context = pa_context_new(pa_threaded_mainloop_get_api(mainloop), APP_NAME);
pa_context_set_state_callback(context, context_state_change_cb, (void*) mainloop);
if (pa_context_connect(context, NULL, 0, NULL) < 0) {
printf("Error: context connection failed!\n");
pa_context_set_state_callback(context, NULL, NULL);
pa_context_unref(context);
pa_threaded_mainloop_free(mainloop);
return;
}
pa_threaded_mainloop_lock(mainloop);
if (pa_threaded_mainloop_start(mainloop) < 0) {
printf("Error: failed to start mainloop!\n");
pa_threaded_mainloop_unlock(mainloop);
pa_context_disconnect(context);
pa_context_set_state_callback(context, NULL, NULL);
pa_context_unref(context);
pa_threaded_mainloop_free(mainloop);
return;
}
// Wait for context to be ready
printf("waiting for context state change...\n");
for(;;) {
bool ready = false;
switch (pa_context_get_state(context)) {
case PA_CONTEXT_READY:
ready = true;
break;
case PA_CONTEXT_FAILED:
case PA_CONTEXT_TERMINATED:
printf("context state failed/terminated, quitting...\n");
pa_threaded_mainloop_unlock(mainloop);
pa_threaded_mainloop_stop(mainloop);
return;
default:
pa_threaded_mainloop_wait(mainloop);
}
if (ready)
break;
}
pa_buffer_attr ba = {
(unsigned int) -1,
(unsigned int) -1,
(unsigned int) -1,
(unsigned int) -1,
(unsigned int) -1,
};
printf("creating stream...\n");
pa_stream *stream = pa_stream_new(context, "Music", &spec, NULL);
if (!stream) {
printf("Error: failed to create stream!\n");
pa_threaded_mainloop_unlock(mainloop);
pa_threaded_mainloop_stop(mainloop);
pa_context_disconnect(context);
pa_context_set_state_callback(context, NULL, NULL);
pa_context_unref(context);
pa_threaded_mainloop_free(mainloop);
return;
}
pa_stream_set_state_callback(stream, stream_state_change_cb, (void*) mainloop);
printf("connecting stream...\n");
if (pa_stream_connect_playback(stream, NULL, &ba, PA_STREAM_ADJUST_LATENCY, NULL, NULL) != 0) {
printf("Error: could not connect playback!\n");
pa_stream_set_state_callback(stream, NULL, NULL);
pa_stream_unref(stream);
pa_threaded_mainloop_unlock(mainloop);
pa_threaded_mainloop_stop(mainloop);
pa_context_disconnect(context);
pa_context_set_state_callback(context, NULL, NULL);
pa_context_unref(context);
pa_threaded_mainloop_free(mainloop);
return;
}
// Wait for stream to be ready
printf("waiting for stream state change...\n");
for(;;) {
bool ready = false;
switch (pa_stream_get_state(stream)) {
case PA_STREAM_READY:
ready = true;
break;
case PA_STREAM_FAILED:
case PA_STREAM_TERMINATED:
printf("stream state failed/terminated, quitting...\n");
pa_threaded_mainloop_unlock(mainloop);
pa_threaded_mainloop_stop(mainloop);
return;
default:
pa_threaded_mainloop_wait(mainloop);
}
if (ready)
break;
}
#define USEC_PER_SEC 1000000
#define MSEC_PER_SEC 1000
//TODO: testing timer event - why the fuck do these seem to fire immediately?
pa_time_event *t_event = pa_context_rttime_new(context, 5 * USEC_PER_SEC, timer_event_cb, NULL);
struct timeval time = { 3, 100 };
pa_mainloop_api *api = pa_threaded_mainloop_get_api(mainloop);
pa_time_event *t_event2 = api->time_new(api, &time, timer_event_cb_2, NULL);
pa_threaded_mainloop_unlock(mainloop);
pa_time_event *t_event3;
printf("starting mainloop...\n");
// Our main loop
int i = 0;
bool first = true;
for(;;) {
i++;
pa_threaded_mainloop_lock(mainloop);
if (i == 1) {
t_event3 = pa_context_rttime_new(context, 5 * USEC_PER_SEC, timer_event_cb_3, NULL);
}
/*
if stream.is_corked().unwrap() {
stream.uncork(None);
}
*/
int len = BUFFER_SIZE;
if (first) {
printf("first write...\n");
first = false;
}
else {
// let possible = stream.writable_size().unwrap();
// if len > possible {
//:
// len = possible;
// }
if (len == 0) {
printf("len == 0\n");
}
}
len = read(fd, buffer, BUFFER_SIZE);
if (len != BYTES_PER_SEC) {
if (len % pa_frame_size(&spec) != 0) {
len -= len % pa_frame_size(&spec);
}
}
if (len == 0) {
printf("no data read, ending...\n");
pa_threaded_mainloop_unlock(mainloop);
pa_threaded_mainloop_stop(mainloop);
break;
}
printf("%i: writing data...\n", i);
int result = pa_stream_write(stream, buffer, BUFFER_SIZE, NULL, 0, PA_SEEK_RELATIVE);
if (result != 0) {
printf("Error: failed to write to stream: %i (%s)\n", result, pa_strerror(result));
pa_threaded_mainloop_unlock(mainloop);
pa_threaded_mainloop_stop(mainloop);
break;
};
int corked = pa_stream_is_corked(stream);
if (corked > 0) {
pa_stream_cork(stream, 0, NULL, NULL);
}
else if (corked < 0) {
printf("Error: could not determine corked state!\n");
pa_threaded_mainloop_unlock(mainloop);
pa_threaded_mainloop_stop(mainloop);
return;
}
pa_operation *o = pa_stream_drain(stream, drain_cb, (void*) mainloop);
if (!o) {
printf("Error failed to create operation for drain!\n");
pa_threaded_mainloop_unlock(mainloop);
pa_threaded_mainloop_stop(mainloop);
return;
}
while (pa_operation_get_state(o) == PA_OPERATION_RUNNING) {
pa_threaded_mainloop_wait(mainloop);
}
pa_threaded_mainloop_unlock(mainloop);
}
printf("shutting down...\n");
pa_threaded_mainloop_lock(mainloop);
pa_stream_set_state_callback(stream, NULL, NULL);
pa_context_set_state_callback(context, NULL, NULL);
pa_context_disconnect(context);
pa_threaded_mainloop_unlock(mainloop);
}
void context_state_change_cb(pa_context *context, void *data) {
assert(data);
printf("context state change callback called!\n");
pa_threaded_mainloop *mainloop = (pa_threaded_mainloop*) data;
switch (pa_context_get_state(context)) {
case PA_CONTEXT_READY:
case PA_CONTEXT_FAILED:
case PA_CONTEXT_TERMINATED:
printf("context state change\n");
pa_threaded_mainloop_signal(mainloop, 0);
default:
break;
}
}
void stream_state_change_cb(pa_stream *stream, void *data) {
assert(data);
printf("context state change callback called!\n");
pa_threaded_mainloop *mainloop = (pa_threaded_mainloop*) data;
switch (pa_stream_get_state(stream)) {
case PA_STREAM_READY:
case PA_STREAM_FAILED:
case PA_STREAM_TERMINATED:
printf("stream state change\n");
pa_threaded_mainloop_signal(mainloop, 0);
default:
break;
}
}
void drain_cb(pa_stream *stream, int i, void *data) {
assert(data);
pa_threaded_mainloop *mainloop = (pa_threaded_mainloop*) data;
pa_threaded_mainloop_signal(mainloop, 0);
}
void timer_event_cb(pa_mainloop_api*a, pa_time_event* e, const struct timeval *tv, void *userdata) {
printf("Timer event fired!\n");
}
void timer_event_cb_2(pa_mainloop_api*a, pa_time_event* e, const struct timeval *tv, void *userdata) {
printf("Timer event fired! (v2)\n");
}
void timer_event_cb_3(pa_mainloop_api*a, pa_time_event* e, const struct timeval *tv, void *userdata) {
printf("Timer event fired! (v3)\n");
}
<<attachment: rust.zip>>
_______________________________________________ pulseaudio-discuss mailing list [email protected] https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
