diff --git a/README.html b/README.html index fb7ddaa..0becf32 100644 --- a/README.html +++ b/README.html @@ -521,7 +521,8 @@ the audio played by the server. 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).

+is linear in decibels). The option -taper provides a +“tapered” AirPlay volume-control profile some users may prefer.

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 @@ -957,6 +958,13 @@ 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”.

+

-taper Provides a “tapered” Airplay volume-control +profile (matching the one called “dasl-tapering” in shairport-sync): +each time the length of the volume slider (or the number of steps above +mute, where 16 steps = full volume) is reduced by 50%, the perceived +volume is halved (a 10dB attenuation). (This is modified at low volumes, +to use the “untapered” volume if it is louder.)

-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 @@ -1510,13 +1518,14 @@ 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-26 Introduced a simpler (default) method for generating +

1.68 2023-12-29 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.

+with new option -db low[:high] for “flat” rescaling of the dB range. Add +-taper option for a “tapered” AirPlay volume-control profile.

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 8fadec5..b7530ea 100644 --- a/README.md +++ b/README.md @@ -416,7 +416,8 @@ 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). +option "`-db _Low_`" or "``-db _Low_:_High_``" (Rescaling is linear in decibels). The option ```-taper``` provides a "tapered" AirPlay volume-control +profile some users may prefer. The -vsync and -async options also allow an optional positive (or negative) audio-delay adjustment in _milliseconds_ for fine-tuning : `-vsync 20.5` @@ -751,6 +752,11 @@ using UxPlay as a second monitor for a mac computer, or monitoring a webcam; wit 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". +**-taper** Provides a "tapered" Airplay volume-control profile (matching the one called "dasl-tapering" + in [shairport-sync](https://github.com/mikebrady/shairport-sync)): each time the length of the + volume slider (or the number of steps above mute, where 16 steps = full volume) is reduced by 50%, the perceived volume is halved (a 10dB attenuation). + (This is modified at low volumes, to use the "untapered" volume if it is louder.) + **-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 @@ -1188,11 +1194,12 @@ tvOS 12.2.1), so it does not seem to matter what version UxPlay claims to be. # Changelog -1.68 2023-12-26 Introduced a simpler (default) method for generating a persistent public key from the server MAC +1.68 2023-12-29 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. + with new option -db low[:high] for "flat" rescaling of the dB range. Add -taper option for a "tapered" + AirPlay volume-control profile. 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 b2e488a..d1fe26f 100644 --- a/README.txt +++ b/README.txt @@ -513,7 +513,8 @@ helped to prevent this previously when timestamps were not being used.) 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). +(Rescaling is linear in decibels). The option `-taper` provides a +"tapered" AirPlay volume-control profile some users may prefer. The -vsync and -async options also allow an optional positive (or negative) audio-delay adjustment in *milliseconds* for fine-tuning : @@ -960,6 +961,14 @@ it cannot exceed +20dB). The rescaling is "flat", so that for -db 10dB augmentation, and Airplay -30dB would become -50dB. Note that the minimum AirPlay value (-30dB exactly) is translated to "mute". +**-taper** Provides a "tapered" Airplay volume-control profile (matching +the one called "dasl-tapering" in +[shairport-sync](https://github.com/mikebrady/shairport-sync)): each +time the length of the volume slider (or the number of steps above mute, +where 16 steps = full volume) is reduced by 50%, the perceived volume is +halved (a 10dB attenuation). (This is modified at low volumes, to use +the "untapered" volume if it is louder.) + **-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 @@ -1547,13 +1556,14 @@ what version UxPlay claims to be. # Changelog -1.68 2023-12-26 Introduced a simpler (default) method for generating a +1.68 2023-12-29 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. +Add -taper option for a "tapered" AirPlay volume-control profile. 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 0786283..232da28 100644 --- a/renderers/audio_renderer.h +++ b/renderers/audio_renderer.h @@ -33,12 +33,11 @@ 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, - float db_low, float db_high); +void audio_renderer_init(logger_t *logger, const char* audiosink, const bool *audio_sync, const bool *video_sync); 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); -void audio_renderer_set_volume(float volume); +void audio_renderer_set_volume(double volume); void audio_renderer_flush(); void audio_renderer_destroy(); diff --git a/renderers/audio_renderer_gstreamer.c b/renderers/audio_renderer_gstreamer.c index dd8f658..c390e68 100644 --- a/renderers/audio_renderer_gstreamer.c +++ b/renderers/audio_renderer_gstreamer.c @@ -40,7 +40,6 @@ 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; @@ -126,16 +125,13 @@ 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, const float db_low, const float db_high) { +void audio_renderer_init(logger_t *render_logger, const char* audiosink, const bool* audio_sync, const bool* video_sync) { 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); @@ -359,16 +355,10 @@ void audio_renderer_render_buffer(unsigned char* data, int *data_len, unsigned s } } -void audio_renderer_set_volume(float volume) { - /* 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_set_volume(double volume) { + volume = (volume > 10.0) ? 10.0 : volume; + volume = (volume < 0.0) ? 0.0 : volume; + g_object_set(renderer->volume, "volume", volume, NULL); } void audio_renderer_flush() { diff --git a/uxplay.1 b/uxplay.1 index 5c817e7..b7dbebd 100644 --- a/uxplay.1 +++ b/uxplay.1 @@ -36,6 +36,8 @@ UxPlay 1.68: An open\-source AirPlay mirroring (+ audio streaming) server: optional: set maximum to h dB (+ or -); default -30.0:0.0 .PP .TP +\fB\-taper\fR Use a "tapered" AirPlay volume-control profile. +.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 78111b4..e43cb10 100644 --- a/uxplay.cpp +++ b/uxplay.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #ifdef _WIN32 /*modifications for Windows compilation */ #include @@ -136,8 +137,9 @@ 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; +static double db_low = -30.0; +static double db_high = 0.0; +static bool taper_volume = true; /* logging */ @@ -577,7 +579,8 @@ static void print_info (char *name) { 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(" optional: set maximum to h dB (+ or -) default: -30.0:0.0 dB\n"); + printf("-taper Use a \"tapered\" AirPlay volume-qcontrol profile\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"); @@ -1094,22 +1097,24 @@ static void parse_arguments (int argc, char *argv[]) { dacpfile.append(get_homedir()); dacpfile.append("/.uxplay.dacp"); } + } else if (arg == "-taper") { + taper_volume = true; } else if (arg == "-db") { bool db_bad = true; - float db1, db2; + double db1, db2; char *start = NULL; if ( i < argc -1) { char *end1, *end2; start = argv[i+1]; - db1 = strtof(start, &end1); + db1 = strtod(start, &end1); if (end1 > start && *end1 == ':') { - db2 = strtof(++end1, &end2); + db2 = strtod(++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; + db2 = 0.0; } } if (db_bad) { @@ -1504,9 +1509,48 @@ extern "C" void video_flush (void *cls) { } extern "C" void audio_set_volume (void *cls, float volume) { - if (use_audio) { - audio_renderer_set_volume(volume); + double db, db_flat, frac, gst_volume; + if (!use_audio) { + return; } + /* convert from AirPlay dB volume in range {-30dB : 0dB}, to GStreamer volume */ + if (volume == -144.0f) { /* AirPlay "mute" signal */ + frac = 0.0; + } else if (volume < -30.0f) { + LOGE(" invalid AirPlay volume %f", volume); + frac = 0.0; + } else if (volume > 0.0f) { + LOGE(" invalid AirPlay volume %f", volume); + frac = 1.0; + } else if (volume == -30.0f) { + frac = 0.0; + } else if (volume == 0.0f) { + frac = 1.0; + } else { + frac = (double) ( (30.0f + volume) / 30.0f); + frac = (frac > 1.0) ? 1.0 : frac; + } + + /* frac is length of volume slider as fraction of max length */ + /* also (steps/16) where steps is number of discrete steps above mute (16 = full volume) */ + if (frac == 0.0) { + gst_volume = 0.0; + } else { + /* flat rescaling of decibel range from {-30dB : 0dB} to {db_low : db_high} */ + db_flat = db_low + (db_high-db_low) * frac; + if (taper_volume) { + /* taper the volume reduction by the (rescaled) Airplay {-30:0} range so each reduction of + * the remaining slider length by 50% reduces the perceived volume by 50% (-10dB gain) + * (This is the "dasl-tapering" scheme offered by shairport-sync) */ + db = db_high + 10.0 * (log10(frac) / log10(2.0)); + db = (db > db_flat) ? db : db_flat; + } else { + db = db_flat; + } + /* conversion from (gain) decibels to GStreamer's linear volume scale */ + gst_volume = pow(10.0, 0.05*db); + } + audio_renderer_set_volume(gst_volume); } extern "C" void audio_get_format (void *cls, unsigned char *ct, unsigned short *spf, bool *usingScreen, bool *isMedia, uint64_t *audioFormat) { @@ -1959,7 +2003,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, db_low, db_high); + audio_renderer_init(render_logger, audiosink.c_str(), &audio_sync, &video_sync); } else { LOGI("audio_disabled"); }