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

Reply via email to