mplay.mp3.c (12426B)
1#include "mplay.h" 2 3#define MINIMP3_IMPLEMENTATION 4#include "minimp3_ex.h" 5 6#include <pulse/sample.h> 7#include <pulse/introspect.h> 8#include <pulse/operation.h> 9#include <pulse/stream.h> 10#include <pulse/thread-mainloop.h> 11#include <pulse/volume.h> 12#include <pulse/pulseaudio.h> 13#include <pulse/def.h> 14 15#include <sys/mman.h> 16#include <sys/stat.h> 17#include <pthread.h> 18#include <fcntl.h> 19#include <termios.h> 20#include <unistd.h> 21 22#include <stdarg.h> 23#include <stdint.h> 24#include <stdbool.h> 25#include <stdio.h> 26#include <stdlib.h> 27 28struct decoder { 29 mp3dec_ex_t dec; 30 size_t sample_next; 31 size_t samples_max; 32 size_t samples_read; 33 34 int rate; 35 int channels; 36 37 bool seek; 38 bool init; 39 bool pause; 40}; 41 42static pa_buffer_attr pa_buf = { 43 .fragsize = UINT32_MAX, 44 .maxlength = UINT32_MAX, 45 .tlength = UINT32_MAX, 46 .prebuf = UINT32_MAX, 47 .minreq = UINT32_MAX, 48}; 49 50static pa_stream_flags_t pa_stream_flags = PA_STREAM_START_CORKED 51 | PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_ADJUST_LATENCY; 52 53static pa_threaded_mainloop *pa_mloop; 54static pa_context *pa_ctx; 55static pa_stream *pa_strm; 56static pa_sink_input_info pa_strm_sink; 57static bool pa_strm_sink_update; 58 59static struct decoder decoder = { 0 }; 60 61static pthread_t input_worker_thread; 62static bool input_worker_alive = false; 63 64static struct termios term_old; 65static struct termios term_new; 66static bool term_set = false; 67 68static bool headless = false; 69static bool verbose = false; 70static bool istty = false; 71 72static void 73__attribute__((noreturn)) 74__attribute__((format(printf, 1, 2))) 75die(const char *fmt, ...) 76{ 77 va_list ap; 78 79 fputs("mplay.mp3: ", stderr); 80 va_start(ap, fmt); 81 vfprintf(stderr, fmt, ap); 82 va_end(ap); 83 if (*fmt && fmt[strlen(fmt)-1] == ':') { 84 perror(NULL); 85 } else { 86 fputc('\n', stderr); 87 } 88 89 exit(1); 90} 91 92static void 93term_set_canonical(void) 94{ 95 term_new.c_lflag |= ICANON; 96 term_new.c_lflag |= ECHO; 97 if (tcsetattr(0, TCSANOW, &term_new)) 98 die("tcsetattr:"); 99} 100 101static void 102term_set_raw(void) 103{ 104 term_new.c_lflag &= ~(0U | ICANON); 105 term_new.c_lflag &= ~(0U | ECHO); 106 if (tcsetattr(0, TCSANOW, &term_new)) 107 die("tcsetattr:"); 108} 109 110static void 111get_line(char *buf, int size) 112{ 113 char *c; 114 115 if (istty) putc('>', stderr); 116 if (istty) term_set_canonical(); 117 fgets(buf, size, stdin); 118 if ((c = strchr(buf, '\n'))) *c = '\0'; 119 if (istty) term_set_raw(); 120} 121 122static void 123decoder_init(const char *filepath) 124{ 125 if (mp3dec_ex_open(&decoder.dec, filepath, MP3D_SEEK_TO_SAMPLE)) 126 die("mp3dec_ex_open"); 127 128 decoder.sample_next = 0; 129 decoder.samples_max = 0; 130 decoder.samples_max = decoder.dec.samples / (size_t)decoder.dec.info.channels; 131 132 decoder.channels = decoder.dec.info.channels; 133 decoder.rate = decoder.dec.info.hz; 134 135 decoder.seek = false; 136 decoder.pause = false; 137 decoder.init = true; 138} 139 140static void 141decoder_seek(size_t sample_pos) 142{ 143 mp3dec_ex_seek(&decoder.dec, sample_pos * (size_t)decoder.channels); 144 decoder.sample_next = sample_pos; 145} 146 147static void 148pa_stream_drain_callback(pa_stream *stream, int success, void *data) 149{ 150 mplay_status(MPLAY_INFO_EXIT, NULL); 151 exit(0); 152} 153 154static void 155pa_stream_write_callback(pa_stream *stream, size_t requested, void *data) 156{ 157 mp3dec_frame_info_t info; 158 mp3d_sample_t *samples; 159 pa_operation *op; 160 ssize_t remaining; 161 size_t cnt; 162 163 remaining = (ssize_t) requested; 164 while (remaining > 0) { 165 cnt = mp3dec_ex_read_frame(&decoder.dec, &samples, &info, requested); 166 if (!cnt) { 167 op = pa_stream_drain(pa_strm, 168 pa_stream_drain_callback, NULL); 169 pa_operation_unref(op); 170 return; 171 } 172 173 pa_stream_write(stream, samples, 174 cnt * sizeof(mp3d_sample_t), NULL, 0, 175 decoder.seek ? PA_SEEK_RELATIVE_ON_READ: PA_SEEK_RELATIVE); 176 177 decoder.seek = false; 178 decoder.sample_next += cnt / (size_t)decoder.channels; 179 decoder.samples_read = decoder.sample_next; 180 decoder.samples_max = MAX(decoder.samples_max, decoder.samples_read); 181 remaining -= (ssize_t) (cnt * (size_t)decoder.channels * sizeof(mp3d_sample_t)); 182 } 183} 184 185static void 186pa_stream_underflow_callback(struct pa_stream *stream, void *data) 187{ 188 fprintf(stderr, "pulseaudio underflow!\n"); 189} 190 191static void 192pa_stream_overflow_callback(struct pa_stream *stream, void *data) 193{ 194 fprintf(stderr, "pulseaudio overflow!\n"); 195} 196 197static void 198update_sink_input_info_callback(struct pa_context *ctx, 199 const pa_sink_input_info *info, int eol, void *data) 200{ 201 if (eol) return; 202 memcpy(&pa_strm_sink, info, sizeof(pa_sink_input_info)); 203 pa_strm_sink_update = true; 204 pa_threaded_mainloop_signal(pa_mloop, true); 205} 206 207static void 208update_sink_input_info(void) 209{ 210 pa_operation *op; 211 212 pa_strm_sink_update = false; 213 op = pa_context_get_sink_input_info(pa_ctx, 214 pa_stream_get_index(pa_strm), 215 update_sink_input_info_callback, NULL); 216 if (!op) die("pa_context_get_sink_input_info failed"); 217 218 while (!pa_strm_sink_update) { 219 pa_threaded_mainloop_unlock(pa_mloop); 220 pa_threaded_mainloop_wait(pa_mloop); 221 pa_threaded_mainloop_lock(pa_mloop); 222 } 223 224 pa_threaded_mainloop_accept(pa_mloop); 225 226 pa_operation_unref(op); 227} 228 229static size_t 230stream_samples_buffered(void) 231{ 232 static size_t last_read_index = 0, last_write_index = 0; 233 const pa_timing_info *info; 234 size_t buffered; 235 236 info = pa_stream_get_timing_info(pa_strm); 237 if (!info || info->write_index_corrupt || info->read_index_corrupt) 238 return 0; 239 240 if (info->read_index != last_read_index) 241 last_write_index = (size_t) MAX(0, info->write_index); 242 243 buffered = (last_write_index - last_read_index) 244 / ((size_t) decoder.channels * sizeof(mp3d_sample_t)); 245 246 last_read_index = (size_t) MAX(0, info->read_index); 247 248 return buffered; 249} 250 251static double 252user_vol(void) 253{ 254 pa_volume_t vol; 255 256 update_sink_input_info(); 257 if (!pa_strm_sink.has_volume) 258 return -1; 259 260 vol = pa_cvolume_avg(&pa_strm_sink.volume); 261 262 return (double) vol * 100.F / (double) PA_VOLUME_NORM; 263} 264 265static double 266user_pos(void) 267{ 268 ssize_t sample_pos; 269 270 sample_pos = (ssize_t) decoder.samples_read; 271 sample_pos -= (ssize_t) stream_samples_buffered(); 272 sample_pos = MAX(0, sample_pos); 273 274 return (double) sample_pos * 1.F / (double) decoder.rate; 275} 276 277static double 278user_end(void) 279{ 280 return (double) decoder.samples_max * 1.F / (double) decoder.rate; 281} 282 283static void 284cmd_setvol(double vol, double delta) 285{ 286 pa_operation *op; 287 288 pa_threaded_mainloop_lock(pa_mloop); 289 290 if (delta != 0) { 291 vol = user_vol() + delta; 292 } 293 294 update_sink_input_info(); 295 pa_cvolume_set(&pa_strm_sink.volume, 2, 296 (pa_volume_t) (vol * PA_VOLUME_NORM / 100.F)); 297 if (pa_cvolume_avg(&pa_strm_sink.volume) > 2 * PA_VOLUME_NORM) 298 pa_cvolume_set(&pa_strm_sink.volume, 2, 2 * PA_VOLUME_NORM); 299 300 op = pa_context_set_sink_input_volume(pa_ctx, 301 pa_stream_get_index(pa_strm), 302 &pa_strm_sink.volume, NULL, NULL); 303 pa_operation_unref(op); 304 305 mplay_status(MPLAY_INFO_VOLUME, "%02.2f", (double) pa_cvolume_avg(&pa_strm_sink.volume) 306 * 100.f / (double) PA_VOLUME_NORM); 307 308 pa_threaded_mainloop_unlock(pa_mloop); 309} 310 311static void 312cmd_seek(double time_sec, double delta) 313{ 314 size_t sample_pos; 315 pa_operation *op; 316 317 pa_threaded_mainloop_lock(pa_mloop); 318 319 if (delta != 0) { 320 time_sec = user_pos() + delta; 321 } 322 323 if (time_sec < 0) time_sec = 0; 324 sample_pos = (size_t) (time_sec * decoder.rate); 325 decoder_seek(sample_pos); 326 327 op = pa_stream_flush(pa_strm, NULL, NULL); 328 pa_operation_unref(op); 329 decoder.seek = true; 330 331 mplay_status(MPLAY_INFO_SEEK, "%02.2f", user_pos()); 332 333 pa_threaded_mainloop_unlock(pa_mloop); 334} 335 336static void 337cmd_playpause(void) 338{ 339 pa_operation *op; 340 341 pa_threaded_mainloop_lock(pa_mloop); 342 343 decoder.pause ^= 1; 344 op = pa_stream_cork(pa_strm, decoder.pause, NULL, NULL); 345 pa_operation_unref(op); 346 347 mplay_status(MPLAY_INFO_PAUSE, "%i", decoder.pause); 348 349 pa_threaded_mainloop_unlock(pa_mloop); 350} 351 352static void 353cmd_status(void) 354{ 355 pa_threaded_mainloop_lock(pa_mloop); 356 357 update_sink_input_info(); 358 359 mplay_status(MPLAY_INFO_STATUS, 360 "volume=%02.2f,pos=%02.2f,end=%02.2f,pause=%i", 361 user_vol(), user_pos(), user_end(), decoder.pause); 362 363 pa_threaded_mainloop_unlock(pa_mloop); 364} 365 366static bool 367key_input(void) 368{ 369 char linebuf[16], *end; 370 float fval; 371 int key; 372 373 while ((key = mplay_getkey()) == MPLAY_KEY_INV); 374 if (key == MPLAY_KEY_EOF) return false; 375 376 switch (mplay_action(key)) { 377 case MPLAY_ACTION_VOLUME: 378 get_line(linebuf, sizeof(linebuf)); 379 fval = strtof(linebuf, &end); 380 if (!end || *end) { 381 mplay_status(MPLAY_INFO_VOLUME, "fail:bad float"); 382 break; 383 } 384 cmd_setvol(fval, 0); 385 break; 386 case MPLAY_ACTION_VOLUME_UP: 387 cmd_setvol(0, 5); 388 break; 389 case MPLAY_ACTION_VOLUME_DOWN: 390 cmd_setvol(0, -5); 391 break; 392 case MPLAY_ACTION_SEEK: 393 get_line(linebuf, sizeof(linebuf)); 394 fval = strtof(linebuf, &end); 395 if (!end || *end) { 396 mplay_status(MPLAY_INFO_SEEK, "fail:bad float"); 397 break; 398 } 399 cmd_seek(fval, 0); 400 break; 401 case MPLAY_ACTION_SEEK_BWD: 402 cmd_seek(0, -5); 403 break; 404 case MPLAY_ACTION_SEEK_FWD: 405 cmd_seek(0, 5); 406 break; 407 case MPLAY_ACTION_PAUSE: 408 cmd_playpause(); 409 break; 410 case MPLAY_ACTION_STATUS: 411 cmd_status(); 412 break; 413 default: 414 mplay_status(MPLAY_INFO_INPUT, "fail"); 415 break; 416 } 417 418 return true; 419} 420 421static void * 422input_worker(void *arg) 423{ 424 input_worker_alive = true; 425 426 setvbuf(stdin, NULL, _IONBF, 0); 427 setvbuf(stdout, NULL, _IONBF, 0); 428 setvbuf(stderr, NULL, _IONBF, 0); 429 430 if (istty) { 431 if (tcgetattr(0, &term_old)) 432 die("tcgetattr:"); 433 term_new = term_old; 434 term_new.c_iflag |= BRKINT; 435 term_new.c_iflag &= ~(0U | IGNBRK); 436 term_set = true; 437 438 term_set_raw(); 439 } 440 441 mplay_status(MPLAY_INFO_READY, NULL); 442 443 while (key_input()); 444 445 return NULL; 446} 447 448static void 449pulse_context_init(void) 450{ 451 pa_mainloop_api *pa_mloop_api; 452 int ret; 453 454 pa_mloop_api = pa_threaded_mainloop_get_api(pa_mloop); 455 if (!pa_mloop_api) die("pa_threaded_mainloop_get_api"); 456 457 pa_ctx = pa_context_new(pa_mloop_api, "mplay.mp3"); 458 if (!pa_ctx) die("pa_context_new"); 459 460 ret = pa_context_connect(pa_ctx, NULL, 0, NULL); 461 if (ret) die("pa_context_connect: %s", 462 pa_strerror(pa_context_errno(pa_ctx))); 463 464 while (pa_context_get_state(pa_ctx) != PA_CONTEXT_READY) { 465 pa_threaded_mainloop_unlock(pa_mloop); 466 pa_threaded_mainloop_wait(pa_mloop); 467 pa_threaded_mainloop_lock(pa_mloop); 468 } 469} 470 471static void 472pulse_stream_init(void) 473{ 474 pa_channel_map pa_chmap; 475 pa_sample_spec pa_spec; 476 int ret; 477 478 pa_spec.format = PA_SAMPLE_S16LE; 479 pa_spec.channels = (uint8_t) decoder.channels; 480 pa_spec.rate = (uint32_t) decoder.rate; 481 482 pa_channel_map_init_stereo(&pa_chmap); 483 484 pa_strm = pa_stream_new(pa_ctx, "mplay.mp3", &pa_spec, &pa_chmap); 485 if (!pa_strm) die("pa_stream_new: %s", 486 pa_strerror(pa_context_errno(pa_ctx))); 487 488 pa_stream_set_write_callback(pa_strm, pa_stream_write_callback, NULL); 489 490 if (verbose) { 491 pa_stream_set_underflow_callback(pa_strm, 492 pa_stream_underflow_callback, NULL); 493 pa_stream_set_overflow_callback(pa_strm, 494 pa_stream_overflow_callback, NULL); 495 } 496 497 ret = pa_stream_connect_playback(pa_strm, NULL, 498 &pa_buf, pa_stream_flags, NULL, NULL); 499 if (ret) die("pa_stream_connect_playback failed"); 500 501 while (pa_stream_get_state(pa_strm) != PA_STREAM_READY) { 502 pa_threaded_mainloop_unlock(pa_mloop); 503 pa_threaded_mainloop_wait(pa_mloop); 504 pa_threaded_mainloop_lock(pa_mloop); 505 } 506} 507 508static void 509sigint_handler(int sig) 510{ 511 exit(0); 512} 513 514static void 515cleanup(void) 516{ 517 if (term_set) tcsetattr(0, TCSANOW, &term_old); 518 519 pa_stream_disconnect(pa_strm); 520 pa_stream_unref(pa_strm); 521 522 pa_context_disconnect(pa_ctx); 523 pa_context_unref(pa_ctx); 524 525 if (input_worker_alive) 526 pthread_kill(input_worker_thread, SIGINT); 527} 528 529static void 530usage(void) 531{ 532 fprintf(stderr, "Usage: mplay.mp3 [" MPLAY_FLAG_HEADLESS "] FILE\n"); 533 exit(1); 534} 535 536int 537main(int argc, const char **argv) 538{ 539 pthread_mutex_t lock; 540 const char **arg; 541 const char *file; 542 543 file = NULL; 544 for (arg = argv + 1; *arg; arg++) { 545 if (!strcmp(*arg, MPLAY_FLAG_HEADLESS)) { 546 headless = true; 547 } else if (!strcmp(*arg, "-v")) { 548 verbose = true; 549 } else { 550 if (file) usage(); 551 file = *arg; 552 } 553 } 554 if (!file) usage(); 555 556 istty = isatty(0); 557 558 decoder_init(file); 559 560 pa_mloop = pa_threaded_mainloop_new(); 561 if (!pa_mloop) die("pa_threaded_mainloop_new"); 562 563 pa_threaded_mainloop_start(pa_mloop); 564 565 pa_threaded_mainloop_lock(pa_mloop); 566 pulse_context_init(); 567 pulse_stream_init(); 568 pa_stream_cork(pa_strm, 0, NULL, NULL); 569 pa_threaded_mainloop_unlock(pa_mloop); 570 571 atexit(cleanup); 572 signal(SIGINT, sigint_handler); 573 574 if (!headless) { 575 if (pthread_create(&input_worker_thread, NULL, input_worker, NULL)) 576 die("pthread_create"); 577 pthread_join(input_worker_thread, NULL); 578 } else { 579 pthread_mutex_init(&lock, NULL); 580 pthread_mutex_lock(&lock); 581 pthread_mutex_lock(&lock); 582 } 583 584 return 0; 585}