From a2790fc102fdf649f0972f8e514a06f98159d762 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 16 Mar 2015 11:11:54 +0000 Subject: [PATCH] Improve metadata output -- maybe it works reliably now. Add call to get audio parameters from back ends, to be able to give volume metadata. --- audio.h | 13 +++++++++++++ audio_alsa.c | 14 +++++++++++++- audio_ao.c | 3 ++- audio_dummy.c | 3 ++- audio_pipe.c | 3 ++- audio_pulse.c | 3 ++- audio_sndio.c | 3 ++- common.c | 2 ++ player.c | 38 +++++++++++++++++++++++++++++--------- rtsp.c | 26 ++++++++++++++++---------- 10 files changed, 83 insertions(+), 25 deletions(-) diff --git a/audio.h b/audio.h index cfe12721..96b56d61 100644 --- a/audio.h +++ b/audio.h @@ -3,6 +3,16 @@ #include +typedef struct { + double airplay_volume; + double current_volume_dB; + int minimum_volume_dB; + int maximum_volume_dB; + int has_true_mute; + int is_muted; + int valid; +} audio_parameters; + typedef struct { void (*help)(void); char *name; @@ -28,6 +38,9 @@ typedef struct { // may be NULL, in which case soft volume is applied void (*volume)(double vol); + + // may be NULL, in which case soft volume parameters are used + void (*parameters)(audio_parameters* info); } audio_output; audio_output *audio_get_output(char *name); diff --git a/audio_alsa.c b/audio_alsa.c index d7c23236..9d29381d 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -43,6 +43,7 @@ static void stop(void); static void flush(void); static uint32_t delay(void); static void volume(double vol); +static void parameters(audio_parameters* info); static int has_mute=0; static int has_db_vol=0; static double set_volume; @@ -57,7 +58,8 @@ audio_output audio_alsa = { .flush = &flush, .delay = &delay, .play = &play, - .volume = NULL + .volume = NULL, // to be set later on... + .parameters = NULL // to be set later on... }; static pthread_mutex_t alsa_mutex = PTHREAD_MUTEX_INITIALIZER; @@ -158,6 +160,7 @@ static int init(int argc, char **argv) { (snd_mixer_selem_ask_playback_vol_dB(alsa_mix_elem,alsa_mix_maxv,&alsa_mix_maxdb)==0)) { has_db_vol=1; audio_alsa.volume = &volume; // insert the volume function now we know it can do dB stuff + audio_alsa.parameters = ¶meters; // likewise the parameters stuff if (alsa_mix_mindb==-9999999) { // trying to say that the lowest vol is mute, maybe? Raspberry Pi does this if (snd_mixer_selem_ask_playback_vol_dB(alsa_mix_elem,alsa_mix_minv+1,&alsa_mix_mindb)==0) @@ -314,6 +317,15 @@ static void stop(void) { // close_alsa_device(); } +static void parameters(audio_parameters* info) { + info->has_true_mute=has_mute; + info->is_muted = ((has_mute) && (set_volume == -144.0)); + info->minimum_volume_dB=alsa_mix_mindb; + info->maximum_volume_dB=alsa_mix_maxdb; + info->airplay_volume=set_volume; + info->current_volume_dB=vol2attn(set_volume,alsa_mix_maxdb,alsa_mix_mindb); +} + static void volume(double vol) { set_volume=vol; double vol_setting = vol2attn(vol,alsa_mix_maxdb,alsa_mix_mindb); diff --git a/audio_ao.c b/audio_ao.c index 9ed0e288..d8b0cbd5 100644 --- a/audio_ao.c +++ b/audio_ao.c @@ -127,5 +127,6 @@ audio_output audio_ao = { .flush = NULL, .delay = NULL, .play = &play, - .volume = NULL + .volume = NULL, + .parameters = NULL }; diff --git a/audio_dummy.c b/audio_dummy.c index 630dd896..92129404 100644 --- a/audio_dummy.c +++ b/audio_dummy.c @@ -81,5 +81,6 @@ audio_output audio_dummy = { .flush = NULL, .delay = NULL, .play = &play, - .volume = NULL + .volume = NULL, + .parameters = NULL }; diff --git a/audio_pipe.c b/audio_pipe.c index d85f1083..46527611 100644 --- a/audio_pipe.c +++ b/audio_pipe.c @@ -86,5 +86,6 @@ audio_output audio_pipe = { .flush = NULL, .delay = NULL, .play = &play, - .volume = NULL + .volume = NULL, + .parameters = NULL }; diff --git a/audio_pulse.c b/audio_pulse.c index 5a06f647..993130c1 100644 --- a/audio_pulse.c +++ b/audio_pulse.c @@ -148,5 +148,6 @@ audio_output audio_pulse = { .flush = NULL, .delay = NULL, .play = &play, - .volume = NULL + .volume = NULL, + .parameters = NULL }; diff --git a/audio_sndio.c b/audio_sndio.c index 21ba6466..564c7939 100644 --- a/audio_sndio.c +++ b/audio_sndio.c @@ -82,5 +82,6 @@ audio_output audio_sndio = { .flush = NULL, .delay = NULL, .play = &play, - .volume = &volume + .volume = &volume, + .parameters = NULL }; diff --git a/common.c b/common.c index f268cc88..4d1c985d 100644 --- a/common.c +++ b/common.c @@ -399,6 +399,8 @@ void command_stop(void) { // See http://tangentsoft.net/audio/atten.html for data on good attenuators. // We want a smooth attenuation function, like, for example, the ALPS RK27 Potentiometer transfer functions referred to at the link above. +// Note that the max_db and min_db are given as dB*100 + double vol2attn(double vol, long max_db, long min_db) { // We use a little coordinate geometry to build a transfer function from the volume passed in to the device's dynamic range. diff --git a/player.c b/player.c index 6d6b38da..6c3f7f22 100644 --- a/player.c +++ b/player.c @@ -95,7 +95,7 @@ static int32_t last_seqno_read; // interthread variables -static double volume = 1.0; +static double software_mixer_volume = 1.0; static int fix_volume = 0x10000; static pthread_mutex_t vol_mutex = PTHREAD_MUTEX_INITIALIZER; @@ -133,6 +133,8 @@ static pthread_cond_t flowcontrol; static int64_t first_packet_time_to_play; // nanoseconds +static audio_parameters audio_information; + // stats static uint64_t missing_packets,late_packets,too_late_packets,resend_requests; @@ -740,7 +742,7 @@ static int stuff_buffer_soxr(short *inptr, short *outptr, int stuff) { } // finally, adjust the volume, if necessary - if (volume!=1.0) { + if (software_mixer_volume!=1.0) { // pthread_mutex_lock(&vol_mutex); op=outptr; for (i=0; ivolume) { - config.output->volume(f); - linear_volume=1.0; // no attenuation needed - } + config.output->volume(f); // volume will be sent as metadata by the config.output device + linear_volume=1.0; // no attenuation needed -- this value is used as a flag to avoid calculations + } + + + if (config.output->parameters) + config.output->parameters(&audio_information); + else { + audio_information.airplay_volume = f; + audio_information.minimum_volume_dB = -4810; + audio_information.maximum_volume_dB = 0; + audio_information.current_volume_dB = scaled_volume; + audio_information.has_true_mute = 0; + audio_information.is_muted = 0; + } + audio_information.valid=1; pthread_mutex_lock(&vol_mutex); - volume = linear_volume; - fix_volume = 65536.0 * volume; + software_mixer_volume = linear_volume; + fix_volume = 65536.0 * software_mixer_volume; pthread_mutex_unlock(&vol_mutex); + char *dv = malloc(64); // will be freed in the metadata thread if (dv) { memset(dv,0,64); - snprintf(dv,63,"%.2f",f); + snprintf(dv,63,"%.2f,%.2f,%.2f,%.2f",audio_information.airplay_volume,audio_information.current_volume_dB/100.0,audio_information.minimum_volume_dB/100.0,audio_information.maximum_volume_dB/100.0); send_ssnc_metadata('pvol',dv,strlen(dv),1); } + + } void player_flush(uint32_t timestamp) { diff --git a/rtsp.c b/rtsp.c index ff39554b..57303e9c 100644 --- a/rtsp.c +++ b/rtsp.c @@ -797,7 +797,12 @@ static void handle_set_parameter_parameter(rtsp_conn_info *conn, // 'pend' -- play stream end. No arguments // 'pfls' -- play stream flush. No arguments // 'prsm' -- play stream resume. No arguments -// 'pvol' -- play volume. The volume is sent as a string. +// 'pvol' -- play volume. The volume is sent as a string -- "airplay_volume,volume,lowest_volume,highest_volume,has_true_mute,is_muted" +// volume, lowest_volume and highest_volume are given in dB +// is_muted is 1 if [true] mute is enabled, 0 otherwise. +// The "airplay_volume" is what's sent to the player, and is from 0.00 down to -30.00, with -144.00 meaning mute. +// This is linear on the volume control slider of iTunes or iOS AirPLay +// // 'mdst' -- a sequence of metadata is about to start // 'mden' -- a sequence of metadata has ended // 'snam' -- the name of the originator -- e.g. "Joe's iPhone" or "iTunes...". @@ -894,7 +899,7 @@ void metadata_open(void) { char* path = malloc(pl+1); snprintf(path, pl+1, "%s/%s", config.meta_dir, fn); - fd = open(path, O_WRONLY); + fd = open(path, O_WRONLY | O_NONBLOCK); //if (fd < 0) // debug(1, "Could not open metadata FIFO %s. Will try again later.", path); @@ -907,18 +912,19 @@ static void metadata_close(void) { } ssize_t non_blocking_write(int fd, const void *buf, size_t count) { -// debug(1,"writing %u to pipe...",count); + // debug(1,"writing %u to pipe...",count); // we are assuming that the count is always smaller than the FIFO's buffer struct pollfd ufds[1]; ssize_t reply; do { ufds[0].fd=fd; ufds[0].events = POLLOUT; - int rv = poll(ufds,1,1000); + int rv = poll(ufds,1,5000); if (rv==-1) debug(1,"error waiting for pipe to unblock..."); if (rv==0) debug(1,"timeout waiting for pipe to unblock"); + reply=write(fd,buf,count); if ((reply==-1) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) debug(1,"writing to pipe will block..."); // else @@ -940,12 +946,12 @@ void metadata_process(uint32_t type,uint32_t code,char *data,uint32_t length) { return; char thestring[1024]; snprintf(thestring,1024,"%x%x%u\n",type,code,length); - ret = write(fd, thestring, strlen(thestring)); + ret = non_blocking_write(fd, thestring, strlen(thestring)); if (ret < 1) return; if ((data!=NULL) && (length>0)) { snprintf(thestring,1024,"\n"); - ret = write(fd, thestring, strlen(thestring)); + ret = non_blocking_write(fd, thestring, strlen(thestring)); if (ret < 1) // no reader return; // here, we write the data in base64 form using our nice base64 encoder @@ -963,7 +969,7 @@ void metadata_process(uint32_t type,uint32_t code,char *data,uint32_t length) { if (base64_encode_so(remaining_data, towrite_count, outbuf, &outbuf_size)==NULL) debug(1,"Error encoding base64 data."); //debug(1,"Remaining count: %d ret: %d, outbuf_size: %d.",remaining_count,ret,outbuf_size); - ret = write(fd,outbuf,outbuf_size); + ret = non_blocking_write(fd,outbuf,outbuf_size); if (ret<0) return; remaining_data+=towrite_count; @@ -973,7 +979,7 @@ void metadata_process(uint32_t type,uint32_t code,char *data,uint32_t length) { // return; } snprintf(thestring,1024,"\n"); - ret = write(fd, thestring, strlen(thestring)); + ret = non_blocking_write(fd, thestring, strlen(thestring)); if (ret < 1) // no reader return; } @@ -1070,8 +1076,8 @@ static void handle_set_parameter_metadata(rtsp_conn_info *conn, static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { - if (!req->contentlength) - debug(1, "received empty SET_PARAMETER request."); + //if (!req->contentlength) + // debug(1, "received empty SET_PARAMETER request."); char *ct = msg_get_header(req, "Content-Type");