mplay

Controllable music player
git clone https://git.sinitax.com/sinitax/mplay
Log | Files | Refs | sfeed.txt

commit fd2eab5578b99ba0bb5f32a8f08dbd9fbeb9a9b4
parent 8bd14d100c8d07485d13211e89984e43183b874c
Author: Louis Burda <quent.burda@gmail.com>
Date:   Mon, 12 Dec 2022 00:53:24 +0100

Add threaded libpulse version for control via stdin

Stdin control is still racy since other thread can get preempted anywhere which can cause issues.. really the input_worker should have its own separate handles for the same pulseaudio objects

Diffstat:
Dcompile_commands.json | 19-------------------
Msrc/mplay.c | 260+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
2 files changed, 223 insertions(+), 56 deletions(-)

diff --git a/compile_commands.json b/compile_commands.json @@ -1,19 +0,0 @@ -[ - { - "arguments": [ - "/usr/bin/cc", - "-c", - "-g", - "-I", - "src", - "-I", - "lib/minimp3/", - "-o", - "build/mplay", - "src/mplay.c" - ], - "directory": "/snx/dev/mplay", - "file": "/snx/dev/mplay/src/mplay.c", - "output": "/snx/dev/mplay/build/mplay" - } -] diff --git a/src/mplay.c b/src/mplay.c @@ -1,13 +1,15 @@ -#include "pulse/simple.h" -#include "pulse/volume.h" -#include "pulse/error.h" - +#include <pulse/volume.h> #define MINIMP3_IMPLEMENTATION #include "minimp3.h" +#include <pulse/pulseaudio.h> +#include <pulse/def.h> + +#include <pthread.h> #include <sys/mman.h> #include <sys/stat.h> #include <fcntl.h> +#include <termios.h> #include <unistd.h> #include <err.h> #include <stdint.h> @@ -15,8 +17,17 @@ #include <stdio.h> #include <stdlib.h> -static uint8_t *map_file(const char *path, size_t *len); -static void play_file(const char *path); +#define ERR(...) err(1, __VA_ARGS__) +#define ERRX(...) errx(1, __VA_ARGS__) +#define PA_CTX_ERR(ctx, fmt, ...) do { \ + warnx(fmt ": %s", __VA_ARGS__ __VA_OPT__(,) \ + pa_strerror(pa_context_errno(ctx))); \ + pa_mainloop_quit(pa_mloop, 1); \ + } while (0) +#define PA_ERRX(...) do { \ + warnx(__VA_ARGS__); \ + pa_mainloop_quit(pa_mloop, 1); \ + } while (0) static const pa_sample_spec pa_spec = { .format = PA_SAMPLE_S16LE, @@ -24,6 +35,38 @@ static const pa_sample_spec pa_spec = { .channels = 2 }; +static const pa_buffer_attr pa_buf = { + .maxlength = UINT32_MAX, + .tlength = UINT32_MAX, + .prebuf = UINT32_MAX, + .minreq = UINT32_MAX, +}; +static pa_stream_flags_t pa_stream_flags = + PA_STREAM_START_CORKED | PA_STREAM_INTERPOLATE_TIMING | + PA_STREAM_NOT_MONOTONIC | PA_STREAM_AUTO_TIMING_UPDATE | + PA_STREAM_ADJUST_LATENCY; + +static pa_mainloop *pa_mloop; +static pa_context *pa_ctx; +static pa_stream *pa_strm; +static pa_sink_input_info pa_strm_info; + +static struct { + uint8_t *data; + size_t len; +} audiofile; + +static struct { + mp3dec_t dec; + uint8_t *pos; + ssize_t left; +} mp3d; + +static pthread_t input_worker_thread; +static pthread_t pa_worker_thread; + +static int exitcode; + uint8_t * map_file(const char *path, size_t *len) { @@ -32,13 +75,13 @@ map_file(const char *path, size_t *len) int fd; fd = open(path, O_RDONLY); - if (fd < 0) err(1, "open %s", path); + if (fd < 0) ERR("open %s", path); if (fstat(fd, &attr)) - err(1, "fstat %s", path); + ERR("fstat %s", path); buf = mmap(NULL, attr.st_size, PROT_READ, MAP_PRIVATE, fd, 0); - if (!buf) err(1, "mmap %s", path); + if (!buf) ERR("mmap %s", path); *len = attr.st_size; @@ -48,55 +91,198 @@ map_file(const char *path, size_t *len) } void -play_file(const char *filename) +pa_stream_write_callback(pa_stream *stream, size_t requested, void *data) { mp3dec_frame_info_t info; mp3d_sample_t samples[MINIMP3_MAX_SAMPLES_PER_FRAME]; - mp3dec_t mp3d; - pa_simple *pa; - uint8_t *data, *pos; - size_t len; - int pa_err; - int ret; + ssize_t remaining; int cnt; - data = map_file(filename, &len); + remaining = requested; + while (remaining > 0 && mp3d.left > 0) { + cnt = mp3dec_decode_frame(&mp3d.dec, + mp3d.pos, mp3d.left, samples, &info); + if (!cnt && !info.frame_bytes) break; + + mp3d.pos += info.frame_bytes; + mp3d.left -= info.frame_bytes; + if (!cnt) continue; + + pa_stream_write(stream, samples, + cnt * info.channels * sizeof(mp3d_sample_t), + NULL, 0, PA_SEEK_RELATIVE); + remaining -= info.frame_bytes; + } +} - mp3dec_init(&mp3d); +void +pa_stream_state_callback(pa_stream *stream, void *data) +{ + switch (pa_stream_get_state(stream)) { + case PA_STREAM_FAILED: + pa_mainloop_quit(pa_mloop, 1); + return; + case PA_STREAM_READY: + pa_stream_cork(stream, 0, NULL, NULL); + break; + case PA_STREAM_UNCONNECTED: + printf("DISCONECTED\n"); + pa_mainloop_quit(pa_mloop, 0); + break; + case PA_STREAM_CREATING: + default: + break; + } +} - pa = pa_simple_new(NULL, "mplay", PA_STREAM_PLAYBACK, NULL, - "playback", &pa_spec, NULL, NULL, &pa_err); - if (!pa) errx(1, "pa_simple_new: %s", pa_strerror(pa_err)); +void +pa_state_callback(pa_context *pa_ctx, void *data) +{ + pa_channel_map pa_chmap; + pa_context_state_t state; + int ret; - pos = data; - while (pos < data + len) { - cnt = mp3dec_decode_frame(&mp3d, pos, len, samples, &info); - if (!cnt && !info.frame_bytes) break; + switch (pa_context_get_state(pa_ctx)) { + case PA_CONTEXT_FAILED: + pa_mainloop_quit(pa_mloop, 1); + return; + case PA_CONTEXT_READY: + pa_channel_map_init_stereo(&pa_chmap); - pos += info.frame_bytes; - if (!cnt) continue; + mp3dec_init(&mp3d.dec); + mp3d.pos = audiofile.data; + mp3d.left = audiofile.len; + + pa_strm = pa_stream_new(pa_ctx, "mplay", &pa_spec, &pa_chmap); + if (!pa_strm) PA_CTX_ERR(pa_ctx, "pa_stream_new"); + + pa_stream_set_state_callback(pa_strm, + pa_stream_state_callback, NULL); + pa_stream_set_write_callback(pa_strm, + pa_stream_write_callback, NULL); + + ret = pa_stream_connect_playback(pa_strm, + NULL, &pa_buf, pa_stream_flags, NULL, NULL); + if (ret) PA_ERRX("pa_stream_connect_playback"); - ret = pa_simple_write(pa, samples, - cnt * info.channels * sizeof(mp3d_sample_t), &pa_err); - if (ret) errx(1, "pa_simple_write: %s", pa_strerror(pa_err)); + break; + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_UNCONNECTED: + default: + break; } +} + +void +pa_sink_input_info_callback(struct pa_context *ctx, + const pa_sink_input_info *info, int eol, void *data) +{ + if (eol) return; + memcpy(&pa_strm_info, info, sizeof(pa_sink_input_info)); +} - ret = pa_simple_drain(pa, &pa_err); - if (ret) errx(1, "pa_simple_drain: %s", pa_strerror(pa_err)); +void * +pa_worker(void *arg) +{ + if (pa_mainloop_run(pa_mloop, NULL)) + exitcode = 1; + printf("KILL INPUT\n"); + pthread_kill(input_worker_thread, SIGINT); + return NULL; +} - pa_simple_free(pa); +void * +input_worker(void *arg) +{ + pa_cvolume vol; + struct termios old, new; + char c; - munmap(data, len); + if (tcgetattr(0, &old)) + ERR("tcgetattr"); + + new = old; + new.c_iflag |= BRKINT; + new.c_iflag &= ~IGNBRK; + new.c_lflag &= ~ICANON; + + if (tcsetattr(0, TCSANOW, &new)) + ERR("tcsetattr"); + + while ((c = getchar())) { + if (c == '+') { + pa_context_get_sink_input_info(pa_ctx, + pa_stream_get_index(pa_strm), + pa_sink_input_info_callback, NULL); + pa_cvolume_inc(&pa_strm_info.volume, 1); + pa_context_set_sink_input_volume(pa_ctx, + pa_stream_get_index(pa_strm), + &pa_strm_info.volume, NULL, NULL); + } else if (c == '-') { + pa_context_get_sink_input_info(pa_ctx, + pa_stream_get_index(pa_strm), + pa_sink_input_info_callback, NULL); + pa_cvolume_dec(&pa_strm_info.volume, 1); + pa_context_set_sink_input_volume(pa_ctx, + pa_stream_get_index(pa_strm), + &pa_strm_info.volume, NULL, NULL); + } + } + + tcsetattr(0, TCSANOW, &old); + + return NULL; } int main(int argc, const char **argv) { + pa_mainloop_api *pa_mloop_api; + pa_context_state_t pa_ctx_state; + int ret; + if (argc != 2) { printf("USAGE: mplay FILE\n"); - exit(1); + return 1; } - play_file(argv[1]); -} + audiofile.data = map_file(argv[1], &audiofile.len); + + pa_mloop = pa_mainloop_new(); + if (!pa_mloop) ERRX("pa_mainloop_new"); + + pa_mloop_api = pa_mainloop_get_api(pa_mloop); + if (!pa_mloop_api) ERRX("pa_mainloop_get_api"); + + pa_ctx = pa_context_new(pa_mloop_api, "mplay"); + if (!pa_ctx) ERRX("pa_context_new"); + + ret = pa_context_connect(pa_ctx, NULL, 0, NULL); + if (ret) ERRX("pa_context_connect: %s", + pa_strerror(pa_context_errno(pa_ctx))); + + pa_context_set_state_callback(pa_ctx, + pa_state_callback, NULL); + + exitcode = 0; + if (pthread_create(&pa_worker_thread, NULL, pa_worker, NULL)) + ERR("pthread_create"); + + if (pthread_create(&input_worker_thread, NULL, input_worker, NULL)) + ERR("pthread_create"); + + pthread_setschedprio(pa_worker_thread, 1); + pthread_setschedprio(input_worker_thread, 2); + + pthread_join(input_worker_thread, NULL); + pthread_join(pa_worker_thread, NULL); + + pa_context_disconnect(pa_ctx); + pa_context_unref(pa_ctx); + pa_mainloop_free(pa_mloop); + + munmap(audiofile.data, audiofile.len); + + return exitcode; +}