diff --git a/Makefile.pa b/Makefile.pa index f6a0a03..636be5a 100644 --- a/Makefile.pa +++ b/Makefile.pa @@ -1,5 +1,5 @@ # Make with portaudio rather than direct alsa -OPTS = -DPORTAUDIO +OPTS += -DPORTAUDIO LDFLAGS = -lportaudio -lpthread -ldl -lrt EXECUTABLE = squeezelite-pa diff --git a/main.c b/main.c index cc26011..558ccc2 100644 --- a/main.c +++ b/main.c @@ -65,6 +65,9 @@ " \t\t\t stopband_start = number in percent (Aliasing/imaging control. > passband_end),\n" " \t\t\t phase_response = 0-100 (0 = minimum / 50 = linear / 100 = maximum)\n" #endif +#if VISEXPORT + " -v \t\t\tVisulizer support\n" +#endif #if LINUX " -z \t\t\tDaemonize\n" #endif @@ -100,6 +103,9 @@ #endif #if FFMPEG " FFMPEG" +#endif +#if VISEXPORT + " VISEXPORT" #endif "\n\n", argv0); @@ -152,6 +158,9 @@ unsigned pa_latency = 0; int pa_osx_playnice = -1; #endif +#if VISEXPORT + bool visexport = false; +#endif log_level log_output = lWARN; log_level log_stream = lWARN; @@ -168,11 +177,14 @@ if (strstr("oabcdfmnprs", opt) && optind < argc - 1) { optarg = argv[optind + 1]; optind += 2; -#if RESAMPLE - } else if (strstr("ltuz", opt)) { -#else - } else if (strstr("ltz", opt)) { -#endif + } else if (strstr("ltz" +#if RESAMPLE + "u" +#endif +#if VISEXPORT + "v" +#endif + , opt)) { optarg = NULL; optind += 1; } else { @@ -279,6 +291,11 @@ } break; #endif +#if VISEXPORT + case 'v': + visexport = true; + break; +#endif #if LINUX case 'z': daemonize = true; @@ -337,6 +354,12 @@ output_init(log_output, output_device, output_buf_size, pa_latency, pa_osx_playnice, max_rate); #endif +#if VISEXPORT + if (visexport) { + output_vis_init(mac); + } +#endif + decode_init(log_decode, codecs); #if RESAMPLE diff --git a/output.c b/output.c index f03701e..60b09b4 100644 --- a/output.c +++ b/output.c @@ -34,6 +34,10 @@ #include #endif #endif +#if VISEXPORT && !ALSA +#include +#include +#endif #if ALSA @@ -74,6 +78,26 @@ } pa; #endif // PORTAUDIO + +#if VISEXPORT + +#define VIS_BUF_SIZE 16384 +#define VIS_LOCK_NS 1000000 // ns to wait for vis wrlock + +static struct vis_t { + pthread_rwlock_t rwlock; + u32_t buf_size; + u32_t buf_index; + bool running; + u32_t rate; + time_t updated; + s16_t buffer[VIS_BUF_SIZE]; +} *vis_mmap = NULL; + +static char vis_shm_path[40]; +static int vis_fd = -1; + +#endif // VISEXPORT static log_level loglevel; @@ -735,6 +759,13 @@ pcmp = NULL; output_off = true; LOG_INFO("disabling output"); +#if VISEXPORT + if (vis_mmap) { + pthread_rwlock_wrlock(&vis_mmap->rwlock); + vis_mmap->running = false; + pthread_rwlock_unlock(&vis_mmap->rwlock); + } +#endif continue; } @@ -1286,6 +1317,60 @@ } size -= out_frames; + +#if VISEXPORT + // attempt to write audio to vis_mmap but do not wait more than VIS_LOCK_NS to get wrlock + // this can result in missing audio export to the mmap region, but this is preferable dropping audio + if (vis_mmap) { + int err; + err = pthread_rwlock_trywrlock(&vis_mmap->rwlock); + if (err) { + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_nsec += VIS_LOCK_NS; + if (ts.tv_nsec > 1000000000) { + ts.tv_sec += 1; + ts.tv_nsec -= 1000000000; + } + err = pthread_rwlock_timedwrlock(&vis_mmap->rwlock, &ts); + } + + if (err) { + LOG_DEBUG("failed to get wrlock - skipping visulizer export"); + + } else { + + if (silence) { + vis_mmap->running = false; + } else { + frames_t vis_cnt = out_frames; + s32_t *ptr = (s32_t *) outputbuf->readp; + unsigned i = vis_mmap->buf_index; + + if (!output.current_replay_gain) { + while (vis_cnt--) { + vis_mmap->buffer[i++] = *(ptr++) >> 16; + vis_mmap->buffer[i++] = *(ptr++) >> 16; + if (i == VIS_BUF_SIZE) i = 0; + } + } else { + while (vis_cnt--) { + vis_mmap->buffer[i++] = gain(*(ptr++), output.current_replay_gain) >> 16; + vis_mmap->buffer[i++] = gain(*(ptr++), output.current_replay_gain) >> 16; + if (i == VIS_BUF_SIZE) i = 0; + } + } + + vis_mmap->updated = time(NULL); + vis_mmap->running = true; + vis_mmap->buf_index = i; + vis_mmap->rate = output.current_sample_rate; + } + + pthread_rwlock_unlock(&vis_mmap->rwlock); + } + } +#endif if (!silence) { _buf_inc_readp(outputbuf, out_frames * BYTES_PER_FRAME); @@ -1391,7 +1476,7 @@ const char *alsa_sample_fmt, bool mmap, unsigned max_rate, unsigned rt_priority) { #endif #if PORTAUDIO - void output_init(log_level level, const char *device, unsigned output_buf_size, unsigned latency, int osx_playnice, unsigned max_rate) { +void output_init(log_level level, const char *device, unsigned output_buf_size, unsigned latency, int osx_playnice, unsigned max_rate) { PaError err; #endif loglevel = level; @@ -1504,6 +1589,36 @@ UNLOCK; } +#if VISEXPORT +void output_vis_init(u8_t *mac) { + sprintf(vis_shm_path, "/squeezelite-%02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + + LOCK; + vis_fd = shm_open(vis_shm_path, O_CREAT | O_RDWR, 0666); + if (vis_fd != -1) { + if (ftruncate(vis_fd, sizeof(struct vis_t)) == 0) { + vis_mmap = (struct vis_t *)mmap(NULL, sizeof(struct vis_t), PROT_READ | PROT_WRITE, MAP_SHARED, vis_fd, 0); + } + } + + if (vis_mmap > 0) { + pthread_rwlockattr_t attr; + pthread_rwlockattr_init(&attr); + pthread_rwlockattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); + pthread_rwlock_init(&vis_mmap->rwlock, &attr); + vis_mmap->buf_size = VIS_BUF_SIZE; + vis_mmap->running = false; + vis_mmap->rate = output.current_sample_rate; + pthread_rwlockattr_destroy(&attr); + LOG_INFO("opened visulizer shared memory as %s", vis_shm_path); + } else { + LOG_WARN("unable to open visualizer shared memory"); + vis_mmap = NULL; + } + UNLOCK; +} +#endif + void output_flush(void) { LOG_INFO("flush output buffer"); buf_flush(outputbuf); @@ -1547,5 +1662,17 @@ UNLOCK; #endif +#if VISEXPORT + if (vis_mmap) { + pthread_rwlock_destroy(&vis_mmap->rwlock); + munmap(vis_mmap, sizeof(struct vis_t)); + } + + if (vis_fd != -1) { + shm_unlink(vis_shm_path); + close(vis_fd); + } +#endif + buf_destroy(outputbuf); } diff --git a/squeezelite.h b/squeezelite.h index 3c6f6bb..e34cfda 100644 --- a/squeezelite.h +++ b/squeezelite.h @@ -78,6 +78,14 @@ #else #undef FFMPEG #define FFMPEG 0 +#endif + +#if LINUX && defined(VISEXPORT) +#undef VISEXPORT +#define VISEXPORT 1 // visulizer export support uses linux shared memory +#else +#undef VISEXPORT +#define VISEXPORT 0 #endif // dynamically loaded libraries @@ -488,6 +496,9 @@ #if PORTAUDIO void output_init(log_level level, const char *device, unsigned output_buf_size, unsigned latency, int osx_playnice, unsigned max_rate); #endif +#if VISEXPORT +void output_vis_init(u8_t *mac); +#endif void output_flush(void); void output_close(void); // _* called with mutex locked