commit 6df0caaa1fc7bf68b2e96947da52c068d507b898
parent f5965d79a5588d78b19f9c30f77aa7cc84222988
Author: Louis Burda <quent.burda@gmail.com>
Date: Fri, 14 Jul 2023 14:52:45 +0200
Rename main.c to mplay.c
Diffstat:
M | Makefile | | | 8 | ++++---- |
D | main.c | | | 704 | ------------------------------------------------------------------------------- |
A | mplay.c | | | 703 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
3 files changed, 707 insertions(+), 708 deletions(-)
diff --git a/Makefile b/Makefile
@@ -1,8 +1,8 @@
PREFIX ?= /usr/local
BINDIR ?= /bin
-CFLAGS = -g -I lib/minimp3/ -Wunused-variable -Wunused-function -Wconversion
-LDLIBS = -lpulse -lpulse-simple
+CFLAGS = -I lib/minimp3/ -Wunused-variable -Wunused-function -Wconversion
+LDFLAGS = -lpulse -lpulse-simple
ifeq ($(DEBUG),1)
CFLAGS += -Og -g
@@ -15,8 +15,8 @@ all: mplay
clean:
rm -f mplay
-mplay: main.c
- $(CC) -o $@ $^ $(CFLAGS) $(LDLIBS)
+mplay: mplay.c
+ $(CC) -o $@ $^ $(CFLAGS) $(LDFLAGS)
install:
install -m755 mplay -t "$(DESTDIR)$(PREFIX)$(BINDIR)"
diff --git a/main.c b/main.c
@@ -1,704 +0,0 @@
-#define MINIMP3_IMPLEMENTATION
-#include "minimp3.h"
-
-#include <pulse/sample.h>
-#include <pulse/introspect.h>
-#include <pulse/operation.h>
-#include <pulse/stream.h>
-#include <pulse/thread-mainloop.h>
-#include <pulse/volume.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>
-#include <stdbool.h>
-#include <stdio.h>
-#include <stdlib.h>
-
-enum {
- KEY_NONE = 0,
- KEY_BRK = 3,
- KEY_EOF = 0x100,
- KEY_LEFT,
- KEY_RIGHT,
- KEY_UP,
- KEY_DOWN,
-};
-
-struct audiofile {
- uint8_t *data;
- size_t len;
-};
-
-struct mp3d {
- mp3dec_t dec;
- uint8_t *pos;
- ssize_t left;
-
- mp3d_sample_t *samples;
- size_t sample_cnt;
- size_t sample_cap;
-
- size_t sample_next;
-
- bool seek;
-
- int rate;
- int channels;
-
- bool init;
- bool pause;
-};
-
-static pa_sample_spec pa_spec = {
- .format = PA_SAMPLE_S16LE,
- .rate = 44100,
- .channels = 2
-};
-
-static pa_buffer_attr pa_buf = {
- .fragsize = UINT32_MAX,
- .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_AUTO_TIMING_UPDATE | PA_STREAM_ADJUST_LATENCY;
-
-static pa_threaded_mainloop *pa_mloop;
-static pa_context *pa_ctx;
-static pa_stream *pa_strm;
-static pa_sink_input_info pa_strm_sink;
-static bool pa_strm_sink_update;
-
-static struct audiofile audiofile = { 0 };
-
-static struct mp3d mp3d = { 0 };
-
-static pthread_t input_worker_thread;
-
-static struct termios term_orig;
-static struct termios term_raw;
-static bool term_set = false;
-
-static bool cmd_input_mode = false;
-static bool use_stdio = true;
-static bool debug = false;
-
-static uint8_t *
-map_file(const char *path, size_t *len)
-{
- struct stat attr;
- uint8_t *buf;
- int fd;
-
- fd = open(path, O_RDONLY);
- if (fd < 0) err(1, "open %s", path);
-
- if (fstat(fd, &attr))
- err(1, "fstat %s", path);
-
- if ((attr.st_mode & S_IFMT) == S_IFDIR)
- errx(1, "not a file: %s", path);
-
- buf = mmap(NULL, (size_t) attr.st_size, PROT_READ, MAP_SHARED, fd, 0);
- if (!buf) err(1, "mmap %s", path);
-
- *len = (size_t) attr.st_size;
-
- close(fd);
-
- return buf;
-}
-
-static size_t
-decode_next_frame(mp3d_sample_t *samples, size_t *size)
-{
- static const size_t max_cnt = MINIMP3_MAX_SAMPLES_PER_FRAME;
- mp3dec_frame_info_t info;
- size_t cnt;
-
- if (size) *size = 0;
-
- if (mp3d.sample_next < mp3d.sample_cnt) {
- cnt = MIN(max_cnt, mp3d.sample_cnt - mp3d.sample_next);
- *size = cnt * sizeof(mp3d_sample_t);
- } else {
- if (mp3d.sample_cnt + max_cnt > mp3d.sample_cap) {
- mp3d.sample_cap = MAX(mp3d.sample_cap * 2,
- mp3d.sample_cnt + max_cnt);
- mp3d.samples = realloc(mp3d.samples,
- mp3d.sample_cap * sizeof(mp3d_sample_t));
- if (!mp3d.samples) err(1, "realloc");
- }
-
- cnt = (size_t) mp3dec_decode_frame(&mp3d.dec, mp3d.pos,
- (int) mp3d.left, &mp3d.samples[mp3d.sample_cnt], &info);
- cnt *= (size_t) info.channels;
- mp3d.sample_cnt += cnt;
-
- if (size) *size = (size_t) info.frame_bytes;
-
- if (!info.frame_bytes)
- return 0;
-
- if (!mp3d.init) {
- mp3d.rate = info.hz;
- mp3d.channels = info.channels;
- }
-
- mp3d.pos += info.frame_bytes;
- mp3d.left -= info.frame_bytes;
- }
-
- if (samples != NULL)
- memcpy(samples, &mp3d.samples[mp3d.sample_next],
- cnt * sizeof(mp3d_sample_t));
- mp3d.sample_next += cnt;
-
- return cnt;
-}
-
-static void
-decoder_init(void)
-{
- size_t size;
-
- mp3d_sample_t samples[MINIMP3_MAX_SAMPLES_PER_FRAME];
-
- mp3dec_init(&mp3d.dec);
- mp3d.pos = audiofile.data;
- mp3d.left = (ssize_t) audiofile.len;
-
- mp3d.sample_cnt = 0;
- mp3d.sample_next = 0;
- mp3d.sample_cap = MINIMP3_MAX_SAMPLES_PER_FRAME;
- mp3d.samples = malloc(mp3d.sample_cap * sizeof(mp3d_sample_t));
- if (!mp3d.samples) err(1, "malloc");
-
- /* decode channel specs */
- decode_next_frame(samples, &size);
- if (!size) errx(1, "Invalid mp3");
-
- mp3d.seek = false;
- mp3d.pause = false;
- mp3d.init = true;
-}
-
-static void
-decoder_seek(size_t sample_pos)
-{
- size_t size, cnt;
-
- while (mp3d.sample_next < sample_pos) {
- cnt = decode_next_frame(NULL, &size);
- if (!size) {
- printf("+EXIT: End of file (seek)\n");
- exit(0);
- }
- }
- mp3d.sample_next = sample_pos;
-}
-
-static void
-pa_stream_drain_callback(pa_stream *stream, int success, void *data)
-{
- printf("+EXIT: End of file\n");
- exit(0);
-}
-
-static void
-pa_stream_write_callback(pa_stream *stream, size_t requested, void *data)
-{
- mp3d_sample_t samples[MINIMP3_MAX_SAMPLES_PER_FRAME];
- pa_operation *op;
- ssize_t remaining;
- size_t cnt, size;
-
- remaining = (ssize_t) requested;
- while (remaining > 0) {
- cnt = decode_next_frame(samples, &size);
- if (!size) {
- op = pa_stream_drain(pa_strm,
- pa_stream_drain_callback, NULL);
- pa_operation_unref(op);
- return;
- }
- if (!cnt) continue;
-
- pa_stream_write(stream, samples,
- cnt * sizeof(mp3d_sample_t), NULL, 0,
- mp3d.seek ? PA_SEEK_RELATIVE_ON_READ: PA_SEEK_RELATIVE);
-
- mp3d.seek = false;
- remaining -= (ssize_t) (cnt * sizeof(mp3d_sample_t));
- }
-}
-
-static void
-pa_stream_underflow_callback(struct pa_stream *stream, void *data)
-{
- printf("+UNDERFLOW\n");
-}
-
-static void
-pa_stream_overflow_callback(struct pa_stream *stream, void *data)
-{
- printf("+OVERFLOW\n");
-}
-
-static void
-update_sink_input_info_callback(struct pa_context *ctx,
- const pa_sink_input_info *info, int eol, void *data)
-{
- if (eol) return;
- memcpy(&pa_strm_sink, info, sizeof(pa_sink_input_info));
- pa_strm_sink_update = true;
- pa_threaded_mainloop_signal(pa_mloop, true);
-}
-
-static void
-update_sink_input_info(void)
-{
- pa_operation *op;
-
- pa_strm_sink_update = false;
- op = pa_context_get_sink_input_info(pa_ctx,
- pa_stream_get_index(pa_strm),
- update_sink_input_info_callback, NULL);
- if (!op) errx(1, "pa_context_get_sink_input_info failed");
-
- while (!pa_strm_sink_update) {
- pa_threaded_mainloop_unlock(pa_mloop);
- pa_threaded_mainloop_wait(pa_mloop);
- pa_threaded_mainloop_lock(pa_mloop);
- }
-
- pa_threaded_mainloop_accept(pa_mloop);
-
- pa_operation_unref(op);
-}
-
-static size_t
-stream_samples_buffered(void)
-{
- static size_t last_read_index = 0, last_write_index = 0;
- const pa_timing_info *info;
- size_t buffered;
-
- info = pa_stream_get_timing_info(pa_strm);
- if (!info || info->write_index_corrupt || info->read_index_corrupt)
- return 0;
-
- if (info->read_index != last_read_index)
- last_write_index = (size_t) MAX(0, info->write_index);
-
- buffered = (last_write_index - last_read_index)
- / sizeof(mp3d_sample_t);
-
- last_read_index = (size_t) MAX(0, info->read_index);
-
- return buffered;
-}
-
-static double
-user_vol(void)
-{
- pa_volume_t vol;
-
- update_sink_input_info();
- if (!pa_strm_sink.has_volume)
- return -1;
-
- vol = pa_cvolume_avg(&pa_strm_sink.volume);
-
- return (double) vol * 100.F / (double) PA_VOLUME_NORM;
-}
-
-static double
-user_time(void)
-{
- size_t sample_pos;
-
- sample_pos = MAX(0, mp3d.sample_next - stream_samples_buffered());
-
- return (double) sample_pos * 1.F / (double) (mp3d.rate * mp3d.channels);
-}
-
-static void
-cmd_setvol(double vol)
-{
- pa_operation *op;
-
- update_sink_input_info();
- pa_cvolume_set(&pa_strm_sink.volume, 2,
- (pa_volume_t) (vol * PA_VOLUME_NORM / 100.F));
- if (pa_cvolume_avg(&pa_strm_sink.volume) > 2 * PA_VOLUME_NORM)
- pa_cvolume_set(&pa_strm_sink.volume, 2, 2 * PA_VOLUME_NORM);
-
- op = pa_context_set_sink_input_volume(pa_ctx,
- pa_stream_get_index(pa_strm),
- &pa_strm_sink.volume, NULL, NULL);
- pa_operation_unref(op);
-
- printf("+VOLUME: %02.2f\n", (double) pa_cvolume_avg(&pa_strm_sink.volume)
- * 100.f / (double) PA_VOLUME_NORM);
-}
-
-static void
-cmd_seek(double time_sec)
-{
- size_t sample_pos;
- pa_operation *op;
-
- if (time_sec < 0) time_sec = 0;
- sample_pos = (size_t) (time_sec * mp3d.channels * mp3d.rate);
- decoder_seek(sample_pos);
-
- op = pa_stream_flush(pa_strm, NULL, NULL);
- pa_operation_unref(op);
- mp3d.seek = true;
-
- printf("+SEEK: %02.2f\n", user_time());
-}
-
-static void
-cmd_pause_toggle(void)
-{
- pa_operation *op;
-
- mp3d.pause ^= 1;
- op = pa_stream_cork(pa_strm, mp3d.pause, NULL, NULL);
- pa_operation_unref(op);
-
- printf("+PAUSE: %i\n", mp3d.pause);
-}
-
-static void
-cmd_status(void)
-{
- update_sink_input_info();
-
- printf("+STATUS: vol:%02.2f pos:%02.2f pause:%i\n",
- user_vol(), user_time(), mp3d.pause);
-}
-
-static int
-getkey_esc(void)
-{
- int c;
-
- c = getchar();
- switch (c) {
- case 'A':
- return KEY_UP;
- case 'B':
- return KEY_DOWN;
- case 'C':
- return KEY_RIGHT;
- case 'D':
- return KEY_LEFT;
- case EOF:
- return KEY_EOF;
- default:
- return KEY_NONE;
- }
-}
-
-static int
-getkey(void)
-{
- int c;
-
- c = getchar();
- switch (c) {
- case 0x1b:
- c = getchar();
- switch (c) {
- case '[':
- return getkey_esc();
- default:
- return KEY_NONE;
- }
- default:
- return c > 0 ? (c & 0xff) : c;
- }
-}
-
-static bool
-cmd_input(void)
-{
- char linebuf[256];
- char *end, *tok;
- double input;
-
- if (!fgets(linebuf, sizeof(linebuf), stdin))
- return false;
-
- tok = strchr(linebuf, '\n');
- if (tok) *tok = '\0';
-
- pa_threaded_mainloop_lock(pa_mloop);
-
- if (!strncmp(linebuf, "seek ", 5)) {
- input = strtof(linebuf + 5, &end);
- if (!end || end && *end) {
- printf("Invalid input\n");
- goto exit;
- }
- cmd_seek(input);
- } else if (!strncmp(linebuf, "vol ", 4)) {
- input = strtof(linebuf + 4, &end);
- if (!end || end && *end) {
- printf("Invalid input\n");
- goto exit;
- }
- cmd_setvol(input);
- } else if (!strcmp(linebuf, "status")) {
- cmd_status();
- } else if (!strcmp(linebuf, "pause")) {
- cmd_pause_toggle();
- } else if (!strcmp(linebuf, "key")) {
- cmd_input_mode = false;
- if (term_set) tcsetattr(0, TCSANOW, &term_raw);
- printf("+CMDINPUT: 0\n");
- } else {
- printf("+INVALID: Invalid command\n");
- }
-
-exit:
- pa_threaded_mainloop_unlock(pa_mloop);
- return true;
-}
-
-static bool
-key_input(void)
-{
- int key;
-
- while (!(key = getkey()));
- if (key <= 0 || key == 'q' || key == KEY_EOF)
- return false;
-
- pa_threaded_mainloop_lock(pa_mloop);
-
- switch (key) {
- case KEY_UP:
- case '+':
- cmd_setvol(user_vol() + 5);
- break;
- case KEY_DOWN:
- case '-':
- cmd_setvol(user_vol() - 5);
- break;
- case KEY_LEFT:
- cmd_seek(user_time() - 5);
- break;
- case KEY_RIGHT:
- cmd_seek(user_time() + 5);
- break;
- case 'c':
- cmd_pause_toggle();
- break;
- case 's':
- cmd_status();
- break;
- case 'i':
- cmd_input_mode = true;
- printf("+CMDINPUT: 1\n");
- if (term_set) tcsetattr(0, TCSANOW, &term_orig);
- break;
- default:
- printf("+INVALID: Invalid command '%x'\n", key);
- break;
- }
-
- pa_threaded_mainloop_unlock(pa_mloop);
-
- return true;
-}
-
-static void *
-input_worker(void *arg)
-{
- setvbuf(stdin, NULL, _IONBF, 0);
- setvbuf(stdout, NULL, _IONBF, 0);
-
- if (!tcgetattr(0, &term_orig))
- term_set = true;
-
- term_raw = term_orig;
- term_raw.c_iflag |= BRKINT;
- term_raw.c_iflag &= ~(0U | IGNBRK);
- term_raw.c_lflag &= ~(0U | ICANON);
- term_raw.c_lflag &= ~(0U | ECHO);
-
- if (term_set && !cmd_input_mode) {
- if (tcsetattr(0, TCSANOW, &term_raw))
- err(1, "tcsetattr");
- }
-
- printf("+READY\n");
-
- while (true) {
- if (cmd_input_mode) {
- if (!cmd_input())
- break;
- } else {
- if (!key_input())
- break;
- }
- }
-
- return NULL;
-}
-
-static void
-pulse_context_init(void)
-{
- pa_mainloop_api *pa_mloop_api;
- int ret;
-
- pa_mloop_api = pa_threaded_mainloop_get_api(pa_mloop);
- if (!pa_mloop_api) errx(1, "pa_threaded_mainloop_get_api");
-
- pa_ctx = pa_context_new(pa_mloop_api, "mplay");
- if (!pa_ctx) errx(1, "pa_context_new");
-
- ret = pa_context_connect(pa_ctx, NULL, 0, NULL);
- if (ret) errx(1, "pa_context_connect: %s",
- pa_strerror(pa_context_errno(pa_ctx)));
-
- while (pa_context_get_state(pa_ctx) != PA_CONTEXT_READY) {
- pa_threaded_mainloop_unlock(pa_mloop);
- pa_threaded_mainloop_wait(pa_mloop);
- pa_threaded_mainloop_lock(pa_mloop);
- }
-}
-
-static void
-pulse_stream_init(void)
-{
- pa_channel_map pa_chmap;
- int ret;
-
- pa_channel_map_init_stereo(&pa_chmap);
-
- pa_strm = pa_stream_new(pa_ctx, "mplay", &pa_spec, &pa_chmap);
- if (!pa_strm) errx(1, "pa_stream_new: %s",
- pa_strerror(pa_context_errno(pa_ctx)));
-
- pa_stream_set_write_callback(pa_strm, pa_stream_write_callback, NULL);
-
- if (debug) {
- pa_stream_set_underflow_callback(pa_strm,
- pa_stream_underflow_callback, NULL);
- pa_stream_set_overflow_callback(pa_strm,
- pa_stream_overflow_callback, NULL);
- }
-
- ret = pa_stream_connect_playback(pa_strm, NULL,
- &pa_buf, pa_stream_flags, NULL, NULL);
- if (ret) errx(1, "pa_stream_connect_playback failed");
-
- while (pa_stream_get_state(pa_strm) != PA_STREAM_READY) {
- pa_threaded_mainloop_unlock(pa_mloop);
- pa_threaded_mainloop_wait(pa_mloop);
- pa_threaded_mainloop_lock(pa_mloop);
- }
-}
-
-static void
-sigint_handler(int sig)
-{
- exit(0);
-}
-
-static void
-cleanup(void)
-{
- if (term_set) tcsetattr(0, TCSANOW, &term_orig);
-
- pa_stream_disconnect(pa_strm);
- pa_stream_unref(pa_strm);
-
- pa_context_disconnect(pa_ctx);
- pa_context_unref(pa_ctx);
-
- munmap(audiofile.data, audiofile.len);
-}
-
-static void
-usage(void)
-{
- fprintf(stderr, "Usage: mplay [-h] [-i] FILE\n");
- exit(1);
-}
-
-int
-main(int argc, const char **argv)
-{
- pthread_mutex_t lock;
- const char **arg;
- const char *file;
-
- file = NULL;
- for (arg = argv + 1; *arg; arg++) {
- if (!strcmp(*arg, "-i")) {
- cmd_input_mode = true;
- } else if (!strcmp(*arg, "-h")) {
- use_stdio = false;
- } else if (!strcmp(*arg, "-d")) {
- debug = true;
- } else {
- if (file) usage();
- file = *arg;
- }
- }
-
- if (!file) usage();
-
- if (strlen(file) < 4 || strcmp(file + strlen(file) - 4, ".mp3"))
- errx(1, "not a mp3 file");
-
- audiofile.data = map_file(file, &audiofile.len);
- decoder_init();
-
- pa_spec.channels = (uint8_t) mp3d.channels;
- pa_spec.rate = (uint32_t) mp3d.rate;
-
- pa_mloop = pa_threaded_mainloop_new();
- if (!pa_mloop) errx(1, "pa_threaded_mainloop_new");
-
- pa_threaded_mainloop_start(pa_mloop);
-
- pa_threaded_mainloop_lock(pa_mloop);
- pulse_context_init();
- pulse_stream_init();
- pa_stream_cork(pa_strm, 0, NULL, NULL);
- pa_threaded_mainloop_unlock(pa_mloop);
-
- atexit(cleanup);
- signal(SIGINT, sigint_handler);
-
- if (use_stdio) {
- if (pthread_create(&input_worker_thread, NULL, input_worker, NULL))
- err(1, "pthread_create");
- pthread_join(input_worker_thread, NULL);
- } else {
- pthread_mutex_init(&lock, NULL);
- pthread_mutex_lock(&lock);
- pthread_mutex_lock(&lock);
- }
-
- return 0;
-}
diff --git a/mplay.c b/mplay.c
@@ -0,0 +1,703 @@
+#define MINIMP3_IMPLEMENTATION
+#include "minimp3.h"
+
+#include <pulse/sample.h>
+#include <pulse/introspect.h>
+#include <pulse/operation.h>
+#include <pulse/stream.h>
+#include <pulse/thread-mainloop.h>
+#include <pulse/volume.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>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+enum {
+ KEY_NONE = 0,
+ KEY_BRK = 3,
+ KEY_EOF = 0x100,
+ KEY_LEFT,
+ KEY_RIGHT,
+ KEY_UP,
+ KEY_DOWN,
+};
+
+struct audiofile {
+ uint8_t *data;
+ size_t len;
+};
+
+struct mp3d {
+ mp3dec_t dec;
+ uint8_t *pos;
+ ssize_t left;
+
+ mp3d_sample_t *samples;
+ size_t sample_cnt;
+ size_t sample_cap;
+
+ size_t sample_next;
+
+ bool seek;
+
+ int rate;
+ int channels;
+
+ bool init;
+ bool pause;
+};
+
+static pa_sample_spec pa_spec = {
+ .format = PA_SAMPLE_S16LE,
+ .rate = 44100,
+ .channels = 2
+};
+
+static pa_buffer_attr pa_buf = {
+ .fragsize = UINT32_MAX,
+ .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_AUTO_TIMING_UPDATE | PA_STREAM_ADJUST_LATENCY;
+
+static pa_threaded_mainloop *pa_mloop;
+static pa_context *pa_ctx;
+static pa_stream *pa_strm;
+static pa_sink_input_info pa_strm_sink;
+static bool pa_strm_sink_update;
+
+static struct audiofile audiofile = { 0 };
+
+static struct mp3d mp3d = { 0 };
+
+static pthread_t input_worker_thread;
+
+static struct termios term_orig;
+static struct termios term_raw;
+static bool term_set = false;
+
+static bool cmd_input_mode = false;
+static bool use_stdio = true;
+static bool debug = false;
+
+static uint8_t *
+map_file(const char *path, size_t *len)
+{
+ struct stat attr;
+ uint8_t *buf;
+ int fd;
+
+ fd = open(path, O_RDONLY);
+ if (fd < 0) err(1, "open %s", path);
+
+ if (fstat(fd, &attr))
+ err(1, "fstat %s", path);
+
+ if ((attr.st_mode & S_IFMT) == S_IFDIR)
+ errx(1, "not a file: %s", path);
+
+ buf = mmap(NULL, (size_t) attr.st_size, PROT_READ, MAP_SHARED, fd, 0);
+ if (!buf) err(1, "mmap %s", path);
+
+ *len = (size_t) attr.st_size;
+
+ close(fd);
+
+ return buf;
+}
+
+static size_t
+decode_next_frame(mp3d_sample_t *samples, size_t *size)
+{
+ static const size_t max_cnt = MINIMP3_MAX_SAMPLES_PER_FRAME;
+ mp3dec_frame_info_t info;
+ size_t cnt;
+
+ if (size) *size = 0;
+
+ if (mp3d.sample_next < mp3d.sample_cnt) {
+ cnt = MIN(max_cnt, mp3d.sample_cnt - mp3d.sample_next);
+ *size = cnt * sizeof(mp3d_sample_t);
+ } else {
+ if (mp3d.sample_cnt + max_cnt > mp3d.sample_cap) {
+ mp3d.sample_cap = MAX(mp3d.sample_cap * 2,
+ mp3d.sample_cnt + max_cnt);
+ mp3d.samples = realloc(mp3d.samples,
+ mp3d.sample_cap * sizeof(mp3d_sample_t));
+ if (!mp3d.samples) err(1, "realloc");
+ }
+
+ cnt = (size_t) mp3dec_decode_frame(&mp3d.dec, mp3d.pos,
+ (int) mp3d.left, &mp3d.samples[mp3d.sample_cnt], &info);
+ cnt *= (size_t) info.channels;
+ mp3d.sample_cnt += cnt;
+
+ if (size) *size = (size_t) info.frame_bytes;
+
+ if (!info.frame_bytes)
+ return 0;
+
+ if (!mp3d.init) {
+ mp3d.rate = info.hz;
+ mp3d.channels = info.channels;
+ }
+
+ mp3d.pos += info.frame_bytes;
+ mp3d.left -= info.frame_bytes;
+ }
+
+ if (samples != NULL)
+ memcpy(samples, &mp3d.samples[mp3d.sample_next],
+ cnt * sizeof(mp3d_sample_t));
+ mp3d.sample_next += cnt;
+
+ return cnt;
+}
+
+static void
+decoder_init(void)
+{
+ mp3d_sample_t samples[MINIMP3_MAX_SAMPLES_PER_FRAME];
+ size_t size;
+
+ mp3dec_init(&mp3d.dec);
+ mp3d.pos = audiofile.data;
+ mp3d.left = (ssize_t) audiofile.len;
+
+ mp3d.sample_cnt = 0;
+ mp3d.sample_next = 0;
+ mp3d.sample_cap = MINIMP3_MAX_SAMPLES_PER_FRAME;
+ mp3d.samples = malloc(mp3d.sample_cap * sizeof(mp3d_sample_t));
+ if (!mp3d.samples) err(1, "malloc");
+
+ /* decode channel specs */
+ decode_next_frame(samples, &size);
+ if (!size) errx(1, "Invalid mp3");
+
+ mp3d.seek = false;
+ mp3d.pause = false;
+ mp3d.init = true;
+}
+
+static void
+decoder_seek(size_t sample_pos)
+{
+ size_t size, cnt;
+
+ while (mp3d.sample_next < sample_pos) {
+ cnt = decode_next_frame(NULL, &size);
+ if (!size) {
+ printf("+EXIT: End of file (seek)\n");
+ exit(0);
+ }
+ }
+ mp3d.sample_next = sample_pos;
+}
+
+static void
+pa_stream_drain_callback(pa_stream *stream, int success, void *data)
+{
+ printf("+EXIT: End of file\n");
+ exit(0);
+}
+
+static void
+pa_stream_write_callback(pa_stream *stream, size_t requested, void *data)
+{
+ mp3d_sample_t samples[MINIMP3_MAX_SAMPLES_PER_FRAME];
+ pa_operation *op;
+ ssize_t remaining;
+ size_t cnt, size;
+
+ remaining = (ssize_t) requested;
+ while (remaining > 0) {
+ cnt = decode_next_frame(samples, &size);
+ if (!size) {
+ op = pa_stream_drain(pa_strm,
+ pa_stream_drain_callback, NULL);
+ pa_operation_unref(op);
+ return;
+ }
+ if (!cnt) continue;
+
+ pa_stream_write(stream, samples,
+ cnt * sizeof(mp3d_sample_t), NULL, 0,
+ mp3d.seek ? PA_SEEK_RELATIVE_ON_READ: PA_SEEK_RELATIVE);
+
+ mp3d.seek = false;
+ remaining -= (ssize_t) (cnt * sizeof(mp3d_sample_t));
+ }
+}
+
+static void
+pa_stream_underflow_callback(struct pa_stream *stream, void *data)
+{
+ printf("+UNDERFLOW\n");
+}
+
+static void
+pa_stream_overflow_callback(struct pa_stream *stream, void *data)
+{
+ printf("+OVERFLOW\n");
+}
+
+static void
+update_sink_input_info_callback(struct pa_context *ctx,
+ const pa_sink_input_info *info, int eol, void *data)
+{
+ if (eol) return;
+ memcpy(&pa_strm_sink, info, sizeof(pa_sink_input_info));
+ pa_strm_sink_update = true;
+ pa_threaded_mainloop_signal(pa_mloop, true);
+}
+
+static void
+update_sink_input_info(void)
+{
+ pa_operation *op;
+
+ pa_strm_sink_update = false;
+ op = pa_context_get_sink_input_info(pa_ctx,
+ pa_stream_get_index(pa_strm),
+ update_sink_input_info_callback, NULL);
+ if (!op) errx(1, "pa_context_get_sink_input_info failed");
+
+ while (!pa_strm_sink_update) {
+ pa_threaded_mainloop_unlock(pa_mloop);
+ pa_threaded_mainloop_wait(pa_mloop);
+ pa_threaded_mainloop_lock(pa_mloop);
+ }
+
+ pa_threaded_mainloop_accept(pa_mloop);
+
+ pa_operation_unref(op);
+}
+
+static size_t
+stream_samples_buffered(void)
+{
+ static size_t last_read_index = 0, last_write_index = 0;
+ const pa_timing_info *info;
+ size_t buffered;
+
+ info = pa_stream_get_timing_info(pa_strm);
+ if (!info || info->write_index_corrupt || info->read_index_corrupt)
+ return 0;
+
+ if (info->read_index != last_read_index)
+ last_write_index = (size_t) MAX(0, info->write_index);
+
+ buffered = (last_write_index - last_read_index)
+ / sizeof(mp3d_sample_t);
+
+ last_read_index = (size_t) MAX(0, info->read_index);
+
+ return buffered;
+}
+
+static double
+user_vol(void)
+{
+ pa_volume_t vol;
+
+ update_sink_input_info();
+ if (!pa_strm_sink.has_volume)
+ return -1;
+
+ vol = pa_cvolume_avg(&pa_strm_sink.volume);
+
+ return (double) vol * 100.F / (double) PA_VOLUME_NORM;
+}
+
+static double
+user_time(void)
+{
+ size_t sample_pos;
+
+ sample_pos = MAX(0, mp3d.sample_next - stream_samples_buffered());
+
+ return (double) sample_pos * 1.F / (double) (mp3d.rate * mp3d.channels);
+}
+
+static void
+cmd_setvol(double vol)
+{
+ pa_operation *op;
+
+ update_sink_input_info();
+ pa_cvolume_set(&pa_strm_sink.volume, 2,
+ (pa_volume_t) (vol * PA_VOLUME_NORM / 100.F));
+ if (pa_cvolume_avg(&pa_strm_sink.volume) > 2 * PA_VOLUME_NORM)
+ pa_cvolume_set(&pa_strm_sink.volume, 2, 2 * PA_VOLUME_NORM);
+
+ op = pa_context_set_sink_input_volume(pa_ctx,
+ pa_stream_get_index(pa_strm),
+ &pa_strm_sink.volume, NULL, NULL);
+ pa_operation_unref(op);
+
+ printf("+VOLUME: %02.2f\n", (double) pa_cvolume_avg(&pa_strm_sink.volume)
+ * 100.f / (double) PA_VOLUME_NORM);
+}
+
+static void
+cmd_seek(double time_sec)
+{
+ size_t sample_pos;
+ pa_operation *op;
+
+ if (time_sec < 0) time_sec = 0;
+ sample_pos = (size_t) (time_sec * mp3d.channels * mp3d.rate);
+ decoder_seek(sample_pos);
+
+ op = pa_stream_flush(pa_strm, NULL, NULL);
+ pa_operation_unref(op);
+ mp3d.seek = true;
+
+ printf("+SEEK: %02.2f\n", user_time());
+}
+
+static void
+cmd_pause_toggle(void)
+{
+ pa_operation *op;
+
+ mp3d.pause ^= 1;
+ op = pa_stream_cork(pa_strm, mp3d.pause, NULL, NULL);
+ pa_operation_unref(op);
+
+ printf("+PAUSE: %i\n", mp3d.pause);
+}
+
+static void
+cmd_status(void)
+{
+ update_sink_input_info();
+
+ printf("+STATUS: vol:%02.2f pos:%02.2f pause:%i\n",
+ user_vol(), user_time(), mp3d.pause);
+}
+
+static int
+getkey_esc(void)
+{
+ int c;
+
+ c = getchar();
+ switch (c) {
+ case 'A':
+ return KEY_UP;
+ case 'B':
+ return KEY_DOWN;
+ case 'C':
+ return KEY_RIGHT;
+ case 'D':
+ return KEY_LEFT;
+ case EOF:
+ return KEY_EOF;
+ default:
+ return KEY_NONE;
+ }
+}
+
+static int
+getkey(void)
+{
+ int c;
+
+ c = getchar();
+ switch (c) {
+ case 0x1b:
+ c = getchar();
+ switch (c) {
+ case '[':
+ return getkey_esc();
+ default:
+ return KEY_NONE;
+ }
+ default:
+ return c > 0 ? (c & 0xff) : c;
+ }
+}
+
+static bool
+cmd_input(void)
+{
+ char linebuf[256];
+ char *end, *tok;
+ double input;
+
+ if (!fgets(linebuf, sizeof(linebuf), stdin))
+ return false;
+
+ tok = strchr(linebuf, '\n');
+ if (tok) *tok = '\0';
+
+ pa_threaded_mainloop_lock(pa_mloop);
+
+ if (!strncmp(linebuf, "seek ", 5)) {
+ input = strtof(linebuf + 5, &end);
+ if (!end || end && *end) {
+ printf("Invalid input\n");
+ goto exit;
+ }
+ cmd_seek(input);
+ } else if (!strncmp(linebuf, "vol ", 4)) {
+ input = strtof(linebuf + 4, &end);
+ if (!end || end && *end) {
+ printf("Invalid input\n");
+ goto exit;
+ }
+ cmd_setvol(input);
+ } else if (!strcmp(linebuf, "status")) {
+ cmd_status();
+ } else if (!strcmp(linebuf, "pause")) {
+ cmd_pause_toggle();
+ } else if (!strcmp(linebuf, "key")) {
+ cmd_input_mode = false;
+ if (term_set) tcsetattr(0, TCSANOW, &term_raw);
+ printf("+CMDINPUT: 0\n");
+ } else {
+ printf("+INVALID: Invalid command\n");
+ }
+
+exit:
+ pa_threaded_mainloop_unlock(pa_mloop);
+ return true;
+}
+
+static bool
+key_input(void)
+{
+ int key;
+
+ while (!(key = getkey()));
+ if (key <= 0 || key == 'q' || key == KEY_EOF)
+ return false;
+
+ pa_threaded_mainloop_lock(pa_mloop);
+
+ switch (key) {
+ case KEY_UP:
+ case '+':
+ cmd_setvol(user_vol() + 5);
+ break;
+ case KEY_DOWN:
+ case '-':
+ cmd_setvol(user_vol() - 5);
+ break;
+ case KEY_LEFT:
+ cmd_seek(user_time() - 5);
+ break;
+ case KEY_RIGHT:
+ cmd_seek(user_time() + 5);
+ break;
+ case 'c':
+ cmd_pause_toggle();
+ break;
+ case 's':
+ cmd_status();
+ break;
+ case 'i':
+ cmd_input_mode = true;
+ printf("+CMDINPUT: 1\n");
+ if (term_set) tcsetattr(0, TCSANOW, &term_orig);
+ break;
+ default:
+ printf("+INVALID: Invalid command '%x'\n", key);
+ break;
+ }
+
+ pa_threaded_mainloop_unlock(pa_mloop);
+
+ return true;
+}
+
+static void *
+input_worker(void *arg)
+{
+ setvbuf(stdin, NULL, _IONBF, 0);
+ setvbuf(stdout, NULL, _IONBF, 0);
+
+ if (!tcgetattr(0, &term_orig))
+ term_set = true;
+
+ term_raw = term_orig;
+ term_raw.c_iflag |= BRKINT;
+ term_raw.c_iflag &= ~(0U | IGNBRK);
+ term_raw.c_lflag &= ~(0U | ICANON);
+ term_raw.c_lflag &= ~(0U | ECHO);
+
+ if (term_set && !cmd_input_mode) {
+ if (tcsetattr(0, TCSANOW, &term_raw))
+ err(1, "tcsetattr");
+ }
+
+ printf("+READY\n");
+
+ while (true) {
+ if (cmd_input_mode) {
+ if (!cmd_input())
+ break;
+ } else {
+ if (!key_input())
+ break;
+ }
+ }
+
+ return NULL;
+}
+
+static void
+pulse_context_init(void)
+{
+ pa_mainloop_api *pa_mloop_api;
+ int ret;
+
+ pa_mloop_api = pa_threaded_mainloop_get_api(pa_mloop);
+ if (!pa_mloop_api) errx(1, "pa_threaded_mainloop_get_api");
+
+ pa_ctx = pa_context_new(pa_mloop_api, "mplay");
+ if (!pa_ctx) errx(1, "pa_context_new");
+
+ ret = pa_context_connect(pa_ctx, NULL, 0, NULL);
+ if (ret) errx(1, "pa_context_connect: %s",
+ pa_strerror(pa_context_errno(pa_ctx)));
+
+ while (pa_context_get_state(pa_ctx) != PA_CONTEXT_READY) {
+ pa_threaded_mainloop_unlock(pa_mloop);
+ pa_threaded_mainloop_wait(pa_mloop);
+ pa_threaded_mainloop_lock(pa_mloop);
+ }
+}
+
+static void
+pulse_stream_init(void)
+{
+ pa_channel_map pa_chmap;
+ int ret;
+
+ pa_channel_map_init_stereo(&pa_chmap);
+
+ pa_strm = pa_stream_new(pa_ctx, "mplay", &pa_spec, &pa_chmap);
+ if (!pa_strm) errx(1, "pa_stream_new: %s",
+ pa_strerror(pa_context_errno(pa_ctx)));
+
+ pa_stream_set_write_callback(pa_strm, pa_stream_write_callback, NULL);
+
+ if (debug) {
+ pa_stream_set_underflow_callback(pa_strm,
+ pa_stream_underflow_callback, NULL);
+ pa_stream_set_overflow_callback(pa_strm,
+ pa_stream_overflow_callback, NULL);
+ }
+
+ ret = pa_stream_connect_playback(pa_strm, NULL,
+ &pa_buf, pa_stream_flags, NULL, NULL);
+ if (ret) errx(1, "pa_stream_connect_playback failed");
+
+ while (pa_stream_get_state(pa_strm) != PA_STREAM_READY) {
+ pa_threaded_mainloop_unlock(pa_mloop);
+ pa_threaded_mainloop_wait(pa_mloop);
+ pa_threaded_mainloop_lock(pa_mloop);
+ }
+}
+
+static void
+sigint_handler(int sig)
+{
+ exit(0);
+}
+
+static void
+cleanup(void)
+{
+ if (term_set) tcsetattr(0, TCSANOW, &term_orig);
+
+ pa_stream_disconnect(pa_strm);
+ pa_stream_unref(pa_strm);
+
+ pa_context_disconnect(pa_ctx);
+ pa_context_unref(pa_ctx);
+
+ munmap(audiofile.data, audiofile.len);
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "Usage: mplay [-h] [-i] FILE\n");
+ exit(1);
+}
+
+int
+main(int argc, const char **argv)
+{
+ pthread_mutex_t lock;
+ const char **arg;
+ const char *file;
+
+ file = NULL;
+ for (arg = argv + 1; *arg; arg++) {
+ if (!strcmp(*arg, "-i")) {
+ cmd_input_mode = true;
+ } else if (!strcmp(*arg, "-h")) {
+ use_stdio = false;
+ } else if (!strcmp(*arg, "-d")) {
+ debug = true;
+ } else {
+ if (file) usage();
+ file = *arg;
+ }
+ }
+
+ if (!file) usage();
+
+ if (strlen(file) < 4 || strcmp(file + strlen(file) - 4, ".mp3"))
+ errx(1, "not a mp3 file");
+
+ audiofile.data = map_file(file, &audiofile.len);
+ decoder_init();
+
+ pa_spec.channels = (uint8_t) mp3d.channels;
+ pa_spec.rate = (uint32_t) mp3d.rate;
+
+ pa_mloop = pa_threaded_mainloop_new();
+ if (!pa_mloop) errx(1, "pa_threaded_mainloop_new");
+
+ pa_threaded_mainloop_start(pa_mloop);
+
+ pa_threaded_mainloop_lock(pa_mloop);
+ pulse_context_init();
+ pulse_stream_init();
+ pa_stream_cork(pa_strm, 0, NULL, NULL);
+ pa_threaded_mainloop_unlock(pa_mloop);
+
+ atexit(cleanup);
+ signal(SIGINT, sigint_handler);
+
+ if (use_stdio) {
+ if (pthread_create(&input_worker_thread, NULL, input_worker, NULL))
+ err(1, "pthread_create");
+ pthread_join(input_worker_thread, NULL);
+ } else {
+ pthread_mutex_init(&lock, NULL);
+ pthread_mutex_lock(&lock);
+ pthread_mutex_lock(&lock);
+ }
+
+ return 0;
+}