From 624fd4138636cff74b3f52fa6e93be0fa2fdd702 Mon Sep 17 00:00:00 2001 From: "F. Duncanh" Date: Tue, 26 Dec 2023 19:09:06 -0500 Subject: [PATCH] correct volume control to match airplay spec, add a -db l:h option --- README.html | 36 +++++++++++++++++++++------- README.md | 27 ++++++++++++++------- README.txt | 36 +++++++++++++++++++++------- renderers/audio_renderer.h | 3 ++- renderers/audio_renderer_gstreamer.c | 22 +++++++++++------ uxplay.1 | 5 ++++ uxplay.cpp | 32 ++++++++++++++++++++++++- 7 files changed, 126 insertions(+), 35 deletions(-) diff --git a/README.html b/README.html index 515febb..fb7ddaa 100644 --- a/README.html +++ b/README.html @@ -9,13 +9,13 @@ href="https://github.com/FDH2/UxPlay">https://github.com/FDH2/UxPlay (where ALL user issues should be posted, and latest versions can be found).

Highlights:

+

AirPlay volume-control attenuates volume (gain) by up to -30dB: the +range -30dB:0dB can be rescaled from Low:0 (Low < +0), or Low:High, using the option +“-db _Low_” or “-db _Low_:_High_” (Rescaling +is linear in decibels).

The -vsync and -async options also allow an optional positive (or negative) audio-delay adjustment in milliseconds for fine-tuning : -vsync 20.5 delays audio relative to video by @@ -941,6 +946,17 @@ have any effect.

Audio-only mode, but this option may be useful as a command-line option to switch off a -async option set in a “uxplayrc” configuration file.

+

-db low[:high] Rescales the +AirPlay volume-control attenuation (gain) from -30dB:0dB to +low:0dB or low:high. The lower limit +low must be negative (attenuation); the upper limit +high can be either sign. (GStreamer restricts +volume-augmentation by high so that it cannot exceed +20dB). +The rescaling is “flat”, so that for -db -50:10, a change in Airplay +attenuation by -7dB is translated to a -7 x (60/30) = -14dB attenuation, +and the maximum volume (AirPlay 0dB) is a 10dB augmentation, and Airplay +-30dB would become -50dB. Note that the minimum AirPlay value (-30dB +exactly) is translated to “mute”.

-s wxh (e.g. -s 1920x1080 , which is the default ) sets the display resolution (width and height, in pixels). (This may be a request made to the AirPlay client, and perhaps will not be the final @@ -1494,11 +1510,13 @@ an AppleTV6,2 with sourceVersion 380.20.1 (an AppleTV 4K 1st gen, introduced 2017, running tvOS 12.2.1), so it does not seem to matter what version UxPlay claims to be.

Changelog

-

1.68 2023-12-25 Introduced a simpler (default) method for generating +

1.68 2023-12-26 Introduced a simpler (default) method for generating a persistent public key from the server MAC address (which can now be set with the -m option). (The previous pem-file method is still available with -key option). New option -reg to maintain a register of -pin-authenticated clients.

+pin-authenticated clients. Corrected volume-control: now inteprets +AirPlay volume range -30dB:0dB as (gain/amplitude) decibel attenuation, +with new option -db low[:high] for “flat” rescaling of the dB range.

1.67 2023-11-30 Add support for Apple-style one-time pin authentication of clients with option “-pin”: (uses SRP6a authentication protocol and public key persistence). Detection with error message of diff --git a/README.md b/README.md index eb97463..8fadec5 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,10 @@ ### Now developed at the GitHub site [https://github.com/FDH2/UxPlay](https://github.com/FDH2/UxPlay) (where ALL user issues should be posted, and latest versions can be found). - * _**NEW in v1.68**: improved support for one-time Apple-style "pin" codes introduced in 1.67: a register of pin-registered clients is - now optionally maintained to check returning clients; a simpler method for generating a persistent public key (based on - the MAC address, which now can be set in the UxPlay startup file) is now the default. (The pem-file method introduced in 1.67 is - still available with the '-key" option.)_ + * _**NEW in v1.68**: Volume-control improvements, plus improved support for Apple-style one-time "pin" codes introduced in 1.67: a + register of pin-registered clients can now optionally be maintained to check returning clients; a simpler method for generating + a persistent public key (based on the MAC address, which can be set in the UxPlay startup file) is now the default. (The OpenSSL + "pem-file" method introduced in 1.67 is still available with the '-key" option.)_ ## Highlights: @@ -415,6 +415,9 @@ if you want to follow the Apple Music lyrics on the client while listening to su delays the video on the client to match audio on the server, so leads to a slight delay before a pause or track-change initiated on the client takes effect on the audio played by the server. +AirPlay volume-control attenuates volume (gain) by up to -30dB: the range -30dB:0dB can be rescaled from _Low_:0 (_Low_ < 0), or _Low_:_High_, using the +option "`-db _Low_`" or "``-db _Low_:_High_``" (Rescaling is linear in decibels). + The -vsync and -async options also allow an optional positive (or negative) audio-delay adjustment in _milliseconds_ for fine-tuning : `-vsync 20.5` delays audio relative to video by 0.0205 secs; a negative value advances it.) @@ -741,6 +744,13 @@ using UxPlay as a second monitor for a mac computer, or monitoring a webcam; wit **-async no**. This is the still the default behavior in Audio-only mode, but this option may be useful as a command-line option to switch off a `-async` option set in a "uxplayrc" configuration file. +**-db _low_[:_high_]** Rescales the AirPlay volume-control attenuation (gain) from -30dB:0dB to _low_:0dB or _low_:_high_. The lower limit _low_ + must be negative (attenuation); the upper limit _high_ can be either sign. (GStreamer restricts volume-augmentation by _high_ so that it + cannot exceed +20dB). + The rescaling is "flat", so that for -db -50:10, a change in Airplay attenuation by -7dB is translated to a -7 x (60/30) = -14dB attenuation, + and the maximum volume (AirPlay 0dB) is a 10dB augmentation, and Airplay -30dB would become -50dB. Note that the minimum AirPlay value (-30dB exactly) + is translated to "mute". + **-s wxh** (e.g. -s 1920x1080 , which is the default ) sets the display resolution (width and height, in pixels). (This may be a request made to the AirPlay client, and perhaps will not @@ -1178,10 +1188,11 @@ tvOS 12.2.1), so it does not seem to matter what version UxPlay claims to be. # Changelog -1.68 2023-12-25 Introduced a simpler (default) method for generating a persistent public key from the server MAC - address (which can now be set with the -m option). - (The previous pem-file method is still available with -key option). - New option -reg to maintain a register of pin-authenticated clients. +1.68 2023-12-26 Introduced a simpler (default) method for generating a persistent public key from the server MAC + address (which can now be set with the -m option). (The previous pem-file method is still available + with -key option). New option -reg to maintain a register of pin-authenticated clients. Corrected + volume-control: now inteprets AirPlay volume range -30dB:0dB as (gain/amplitude) decibel attenuation, + with new option -db low[:high] for "flat" rescaling of the dB range. 1.67 2023-11-30 Add support for Apple-style one-time pin authentication of clients with option "-pin": (uses SRP6a authentication protocol and public key persistence). Detection with error message diff --git a/README.txt b/README.txt index 664bd5c..b2e488a 100644 --- a/README.txt +++ b/README.txt @@ -2,13 +2,14 @@ ### Now developed at the GitHub site (where ALL user issues should be posted, and latest versions can be found). -- ***NEW in v1.68**: improved support for one-time Apple-style "pin" - codes introduced in 1.67: a register of pin-registered clients is - now optionally maintained to check returning clients; a simpler - method for generating a persistent public key (based on the MAC - address, which now can be set in the UxPlay startup file) is now the - default. (The pem-file method introduced in 1.67 is still available - with the '-key" option.)* +- ***NEW in v1.68**: Volume-control improvements, plus improved + support for Apple-style one-time "pin" codes introduced in 1.67: a + register of pin-registered clients can now optionally be maintained + to check returning clients; a simpler method for generating a + persistent public key (based on the MAC address, which can be set in + the UxPlay startup file) is now the default. (The OpenSSL "pem-file" + method introduced in 1.67 is still available with the '-key" + option.)* ## Highlights: @@ -509,6 +510,11 @@ helped to prevent this previously when timestamps were not being used.) slight delay before a pause or track-change initiated on the client takes effect on the audio played by the server. +AirPlay volume-control attenuates volume (gain) by up to -30dB: the +range -30dB:0dB can be rescaled from *Low*:0 (*Low* \< 0), or +*Low*:*High*, using the option "`-db _Low_`" or "`-db _Low_:_High_`" +(Rescaling is linear in decibels). + The -vsync and -async options also allow an optional positive (or negative) audio-delay adjustment in *milliseconds* for fine-tuning : `-vsync 20.5` delays audio relative to video by 0.0205 secs; a negative @@ -944,6 +950,16 @@ changing this does not seem to have any effect*. mode, but this option may be useful as a command-line option to switch off a `-async` option set in a "uxplayrc" configuration file. +**-db *low*\[:*high*\]** Rescales the AirPlay volume-control attenuation +(gain) from -30dB:0dB to *low*:0dB or *low*:*high*. The lower limit +*low* must be negative (attenuation); the upper limit *high* can be +either sign. (GStreamer restricts volume-augmentation by *high* so that +it cannot exceed +20dB). The rescaling is "flat", so that for -db +-50:10, a change in Airplay attenuation by -7dB is translated to a -7 x +(60/30) = -14dB attenuation, and the maximum volume (AirPlay 0dB) is a +10dB augmentation, and Airplay -30dB would become -50dB. Note that the +minimum AirPlay value (-30dB exactly) is translated to "mute". + **-s wxh** (e.g. -s 1920x1080 , which is the default ) sets the display resolution (width and height, in pixels). (This may be a request made to the AirPlay client, and perhaps will not be the final resolution you @@ -1531,11 +1547,13 @@ what version UxPlay claims to be. # Changelog -1.68 2023-12-25 Introduced a simpler (default) method for generating a +1.68 2023-12-26 Introduced a simpler (default) method for generating a persistent public key from the server MAC address (which can now be set with the -m option). (The previous pem-file method is still available with -key option). New option -reg to maintain a register of -pin-authenticated clients. +pin-authenticated clients. Corrected volume-control: now inteprets +AirPlay volume range -30dB:0dB as (gain/amplitude) decibel attenuation, +with new option -db low\[:high\] for "flat" rescaling of the dB range. 1.67 2023-11-30 Add support for Apple-style one-time pin authentication of clients with option "-pin": (uses SRP6a authentication protocol and diff --git a/renderers/audio_renderer.h b/renderers/audio_renderer.h index b405dfe..0786283 100644 --- a/renderers/audio_renderer.h +++ b/renderers/audio_renderer.h @@ -33,7 +33,8 @@ extern "C" { #include "../lib/logger.h" bool gstreamer_init(); -void audio_renderer_init(logger_t *logger, const char* audiosink, const bool *audio_sync, const bool *video_sync); +void audio_renderer_init(logger_t *logger, const char* audiosink, const bool *audio_sync, const bool *video_sync, + float db_low, float db_high); void audio_renderer_start(unsigned char* compression_type); void audio_renderer_stop(); void audio_renderer_render_buffer(unsigned char* data, int *data_len, unsigned short *seqnum, uint64_t *ntp_time); diff --git a/renderers/audio_renderer_gstreamer.c b/renderers/audio_renderer_gstreamer.c index 2b131e1..dd8f658 100644 --- a/renderers/audio_renderer_gstreamer.c +++ b/renderers/audio_renderer_gstreamer.c @@ -40,6 +40,7 @@ static gboolean render_audio = FALSE; static gboolean async = FALSE; static gboolean vsync = FALSE; static gboolean sync = FALSE; +static float vol_low, vol_high; typedef struct audio_renderer_s { GstElement *appsrc; @@ -125,14 +126,17 @@ bool gstreamer_init(){ return (bool) check_plugins (); } -void audio_renderer_init(logger_t *render_logger, const char* audiosink, const bool* audio_sync, const bool* video_sync) { +void audio_renderer_init(logger_t *render_logger, const char* audiosink, const bool* audio_sync, + const bool* video_sync, const float db_low, const float db_high) { GError *error = NULL; GstCaps *caps = NULL; GstClock *clock = gst_system_clock_obtain(); g_object_set(clock, "clock-type", GST_CLOCK_TYPE_REALTIME, NULL); logger = render_logger; - + vol_low = db_low; + vol_high = db_high; + aac = check_plugin_feature (avdec_aac); alac = check_plugin_feature (avdec_alac); @@ -356,11 +360,15 @@ void audio_renderer_render_buffer(unsigned char* data, int *data_len, unsigned s } void audio_renderer_set_volume(float volume) { - float avol; - if (fabs(volume) < 28) { - avol=floorf(((28-fabs(volume))/28)*10)/10; - g_object_set(renderer->volume, "volume", avol, NULL); - } + /* scale volume from range -30dB:0dB to vol_low: vol_high */ + double vol = (double) vol_low; + if ((volume <= 0) && (volume > -30)) { + vol = (double) (vol_low + ((vol_high - vol_low) * (30.0 + volume) / 30)); + } + gdouble avol = (gdouble) pow(10, vol/20); + if (avol > 10) avol = 10; + if (volume <= -30) avol = 0; + g_object_set(renderer->volume, "volume", avol, NULL); } void audio_renderer_flush() { diff --git a/uxplay.1 b/uxplay.1 index 99f3a79..5c817e7 100644 --- a/uxplay.1 +++ b/uxplay.1 @@ -31,6 +31,11 @@ UxPlay 1.68: An open\-source AirPlay mirroring (+ audio streaming) server: .TP \fB\-async\fR no Switch off audio/(client)video timestamp synchronization. .TP +\fB\-db\fI l[:h]\fR Set minumum volume attenuation to l dB (decibels, negative); +.IP + optional: set maximum to h dB (+ or -); default -30.0:0.0 +.PP +.TP \fB\-s\fR wxh[@r]Set display resolution [refresh_rate] default 1920x1080[@60] .TP \fB\-o\fR Set display "overscanned" mode on (not usually needed) diff --git a/uxplay.cpp b/uxplay.cpp index 623d220..78111b4 100644 --- a/uxplay.cpp +++ b/uxplay.cpp @@ -136,6 +136,8 @@ static std::string dacpfile = ""; static bool registration_list = false; static std::string pairing_register = ""; static std::vector registered_keys; +static float db_low = -30; +static float db_high = 0; /* logging */ @@ -574,6 +576,8 @@ static void print_info (char *name) { printf("-vsync no Switch off audio/(server)video timestamp synchronization \n"); printf("-async [x]Audio-Only mode: sync audio to client video (default: no)\n"); printf("-async no Switch off audio/(client)video timestamp synchronization\n"); + printf("-db l[:h] Set minimum volume attenuation to l dB (decibels, negative);\n"); + printf(" optional: set maximum to h dB (+ or -); default -30.0:0.0 dB\n"); printf("-s wxh[@r]Set display resolution [refresh_rate] default 1920x1080[@60]\n"); printf("-o Set display \"overscanned\" mode on (not usually needed)\n"); printf("-fs Full-screen (only works with X11, Wayland and VAAPI)\n"); @@ -1090,6 +1094,32 @@ static void parse_arguments (int argc, char *argv[]) { dacpfile.append(get_homedir()); dacpfile.append("/.uxplay.dacp"); } + } else if (arg == "-db") { + bool db_bad = true; + float db1, db2; + char *start = NULL; + if ( i < argc -1) { + char *end1, *end2; + start = argv[i+1]; + db1 = strtof(start, &end1); + if (end1 > start && *end1 == ':') { + db2 = strtof(++end1, &end2); + if ( *end2 == '\0' && end2 > end1 && db1 < 0 && db1 < db2) { + db_bad = false; + } + } else if (*end1 =='\0' && end1 > start && db1 < 0 ) { + db_bad = false; + db2 = 0; + } + } + if (db_bad) { + fprintf(stderr, "%s %s requires argument \"low\" or \"low:high\" where low < 0 and high > low are decibel gain values\n", argv[i], start); + exit(1); + } + i++; + db_low = db1; + db_high = db2; + printf("db range %f:%f\n", db_low, db_high); } else { fprintf(stderr, "unknown option %s, stopping (for help use option \"-h\")\n",argv[i]); exit(1); @@ -1929,7 +1959,7 @@ int main (int argc, char *argv[]) { logger_set_level(render_logger, log_level); if (use_audio) { - audio_renderer_init(render_logger, audiosink.c_str(), &audio_sync, &video_sync); + audio_renderer_init(render_logger, audiosink.c_str(), &audio_sync, &video_sync, db_low, db_high); } else { LOGI("audio_disabled"); }