Improve metadata output -- maybe it works reliably now. Add call to get audio parameters from back ends, to be able to give volume metadata.
This commit is contained in:
@@ -3,6 +3,16 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
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);
|
||||
|
||||
+13
-1
@@ -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);
|
||||
|
||||
+2
-1
@@ -127,5 +127,6 @@ audio_output audio_ao = {
|
||||
.flush = NULL,
|
||||
.delay = NULL,
|
||||
.play = &play,
|
||||
.volume = NULL
|
||||
.volume = NULL,
|
||||
.parameters = NULL
|
||||
};
|
||||
|
||||
+2
-1
@@ -81,5 +81,6 @@ audio_output audio_dummy = {
|
||||
.flush = NULL,
|
||||
.delay = NULL,
|
||||
.play = &play,
|
||||
.volume = NULL
|
||||
.volume = NULL,
|
||||
.parameters = NULL
|
||||
};
|
||||
|
||||
+2
-1
@@ -86,5 +86,6 @@ audio_output audio_pipe = {
|
||||
.flush = NULL,
|
||||
.delay = NULL,
|
||||
.play = &play,
|
||||
.volume = NULL
|
||||
.volume = NULL,
|
||||
.parameters = NULL
|
||||
};
|
||||
|
||||
+2
-1
@@ -148,5 +148,6 @@ audio_output audio_pulse = {
|
||||
.flush = NULL,
|
||||
.delay = NULL,
|
||||
.play = &play,
|
||||
.volume = NULL
|
||||
.volume = NULL,
|
||||
.parameters = NULL
|
||||
};
|
||||
|
||||
+2
-1
@@ -82,5 +82,6 @@ audio_output audio_sndio = {
|
||||
.flush = NULL,
|
||||
.delay = NULL,
|
||||
.play = &play,
|
||||
.volume = &volume
|
||||
.volume = &volume,
|
||||
.parameters = NULL
|
||||
};
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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; i<frame_size+stuff; i++) {
|
||||
@@ -780,6 +782,7 @@ static void *player_thread_func(void *arg) {
|
||||
int64_t minimum_dac_queue_size = 1000000;
|
||||
int32_t minimum_buffer_occupancy = BUFFER_FRAMES;
|
||||
int32_t maximum_buffer_occupancy = 0;
|
||||
audio_information.valid=0;
|
||||
|
||||
int play_samples;
|
||||
int64_t current_delay;
|
||||
@@ -1031,25 +1034,42 @@ void player_volume(double f) {
|
||||
// Here, we ask for an attenuation we will apply in software. The dB range of a value from 1 to 65536 is about 48.1 dB (log10 of 65536 is 4.8164).
|
||||
// Thus, we ask our vol2attn function for an appropriate dB between -48.1 and 0 dB and translate it back to a number.
|
||||
|
||||
double linear_volume = pow(10,vol2attn(f,0,-4810)/1000);
|
||||
double scaled_volume = vol2attn(f,0,-4810);
|
||||
double linear_volume = pow(10,scaled_volume/1000);
|
||||
|
||||
if(f == -144.0)
|
||||
linear_volume = 0.0;
|
||||
|
||||
if (config.output->volume) {
|
||||
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) {
|
||||
|
||||
@@ -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,"<type>%x</type><code>%x</code><length>%u</length>\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,"<data encoding=\"base64\">\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,"</data>\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");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user