diff --git a/README.md b/README.md index feb541d..2af8d97 100644 --- a/README.md +++ b/README.md @@ -227,6 +227,9 @@ Options: --alsadev ALSA microphone device (default: hw:0,0) --volume Amplify audio by multiplying the volume by (default: 1.0) + --ngate Enable noise gate and set + optional parameters. Defaults: 1.00, 0.20, 1.00, 0.50. + Enter - to use a parameter default. --noaudio Disable audio capturing --audiopreview Enable audio preview --audiopreviewdev Audio preview output device (default: plughw:0,0) @@ -378,7 +381,6 @@ Available cameras 'SRGGB8' : 640x480 1640x1232 1920x1080 3280x2464 ``` - #### Exposure control Camera exposure control can be set either via command line option (e.g. `--ex long`) or hooks. To change the exposure control while picam is running, start picam with `--vfr` or `--ex` option, then create `hooks/ex_`, where `` is the name of exposure control. @@ -391,6 +393,27 @@ $ touch hooks/ex_long For the list of available exposure control values, see `picam --help`. +#### Noise Gate + +`ngate` parameter allows you to enable and tune the [noise gate](https://en.wikipedia.org/wiki/Noise_gate). The noise gate will close if the input sound level is below a certain threshold for a determined amount of time, effectively muting sound entirely. Similarly, the noise gate will open again if the level raises above the threshold. This is useful to suppress background noise and save streaming bandwidth. Four optional parameters can be set, each parameter accepts '-' meaning default will be used. + +| Param | Description | Default value | +| :---- | :-------------------------------------------------------------------------------------------------- | :----------------- | +| t | Threshold, expressed as volume between 0.0 and 1.0 | 1.0 (ng disabled) | +| a | Attack time; when gate opens, sound is faded in for this duration in seconds | 0.20 | +| h | Hold time; gate is kept open for this duration in seconds after the level falls below the threshold | 1.0 | +| r | Release time; when gate closes, sound is faded out for this duration in seconds | 0.50 | + +If level goes above threshold again during hold or release time, the hold time restarts and the attack envelope is applied if necessary. + +Example: + +```sh +$ ./picam --ngate 0.3,-,2 +``` + +Sound will be muted if the volume level is below 30% of the scale; when sound rises above, it will be faded in for 0.2 seconds (the default). Sound is allowed to fall below the threshold for 2 uninterrupted seconds, after which the gate will close and a fade out of 1 second will be applied (the default) + #### Recordbuf @@ -539,7 +562,6 @@ $ echo 1.5 > hooks/sharpness $ echo 0 > hooks/sharpness ``` - ### HTTP Live Streaming (HLS) HTTP Live Streaming is disabled by default. To enable HTTP Live Streaming and generate files in /run/shm/hls, run: diff --git a/audio/audio.cpp b/audio/audio.cpp index ee4192b..1fc8df6 100644 --- a/audio/audio.cpp +++ b/audio/audio.cpp @@ -490,6 +490,23 @@ int Audio::configure_audio_capture_device() { snd_pcm_dump(capture_handle, output); } + if (this->option->ng_thresh_volume != 1.0f) { + // Noise gate settings + this->ng_thresh = INT16_MAX * this->option->ng_thresh_volume; + this->ng_attack_rate = this->option->ng_attack_time == 0 + ? 1.0f + : (1.0f / (float) audio_sample_rate / this->option->ng_attack_time); + this->ng_release_rate = this->option->ng_release_time == 0 + ? 1.0f + : (1.0f / (float) audio_sample_rate / this->option->ng_release_time); + + this->ng_hold_samples = this->option->ng_hold_time * (float) audio_sample_rate; + + log_debug("Noise gate enabled!\n"); + } else { + this->ng_thresh = 0; + } + return 0; } @@ -764,8 +781,8 @@ int Audio::read_audio_poll_mmap() { } } - if (this->option->audio_volume_multiply != 1.0f) { - int total_samples = this->option->audio_period_size * this->get_audio_channels(); + int total_samples = this->option->audio_period_size * this->get_audio_channels(); + if (this->option->audio_volume_multiply != 1.0f) { int i; for (i = 0; i < total_samples; i++) { int16_t value = (int16_t)this_samples[i]; @@ -781,6 +798,34 @@ int Audio::read_audio_poll_mmap() { this_samples[i] = (uint16_t)value; } } + + if (this->ng_thresh != 0) { // Noise Gate + for (int i = 0; i < total_samples; i++) { + int16_t sample = (int16_t)this_samples[i]; + uint16_t s_level = abs(sample); + + if (this->ng_open) { + if (s_level < this->ng_thresh) { + this->ng_held_samples = this->ng_hold_samples; + this->ng_open = false; + } + } else { + if (s_level >= this->ng_thresh) { + this->ng_open = true; + } + } + if ((this->ng_held_samples <= 0) && !this->ng_open) { // Start release + this->ng_attenuation = fmaxf(0.0f, this->ng_attenuation - this->ng_release_rate); + } else { + if (!this->ng_open) { + this->ng_held_samples--; + } // Attack can continue during hold time to reduce gate "chatter" + this->ng_attenuation = fminf(1.0f, this->ng_attenuation + this->ng_attack_rate); + } + sample = (int16_t)(sample * this->ng_attenuation); + this_samples[i] = (uint16_t)sample; + } + } #if AUDIO_BUFFER_CHUNKS > 0 if (++audio_buffer_index == AUDIO_BUFFER_CHUNKS) { diff --git a/audio/audio.hpp b/audio/audio.hpp index d2e9135..9433dbe 100644 --- a/audio/audio.hpp +++ b/audio/audio.hpp @@ -50,4 +50,11 @@ class Audio void setup_av_frame(AVFormatContext *format_ctx); HTTPLiveStreaming *hls; bool is_muted = false; + uint16_t ng_thresh = 0; + float ng_attack_rate; + float ng_release_rate; + int ng_hold_samples; + bool ng_open = false; + float ng_attenuation = 0.0f; + int ng_held_samples; }; diff --git a/picam_option/picam_option.cpp b/picam_option/picam_option.cpp index 6da38ea..6608c97 100644 --- a/picam_option/picam_option.cpp +++ b/picam_option/picam_option.cpp @@ -60,6 +60,11 @@ void PicamOption::print_usage() { log_info(" --alsadev ALSA microphone device (default: %s)\n", defaultOption.alsa_dev); log_info(" --volume Amplify audio by multiplying the volume by \n"); log_info(" (default: %.1f)\n", defaultOption.audio_volume_multiply); + log_info(" --ngate Enable noise gate and set \n"); + log_info(" optional parameters. Defaults: %.02f, %.02f, %.02f, %.02f.\n", + defaultOption.ng_thresh_volume, defaultOption.ng_attack_time, + defaultOption.ng_hold_time, defaultOption.ng_release_time); + log_info(" Enter - to use a parameter default.\n"); log_info(" --noaudio Disable audio capturing\n"); log_info(" --audiopreview Enable audio preview\n"); log_info(" --audiopreviewdev Audio preview output device (default: %s)\n", defaultOption.audio_preview_dev); @@ -267,6 +272,7 @@ int PicamOption::parse(int argc, char **argv) { { "statedir", required_argument, NULL, 0 }, { "hooksdir", required_argument, NULL, 0 }, { "volume", required_argument, NULL, 0 }, + { "ngate", required_argument, NULL, 0 }, { "noaudio", no_argument, NULL, 0 }, { "audiopreview", no_argument, NULL, 0 }, { "audiopreviewdev", required_argument, NULL, 0 }, @@ -849,6 +855,56 @@ int PicamOption::parse(int argc, char **argv) { return EXIT_FAILURE; } audio_volume_multiply = value; + } else if (strcmp(long_options[option_index].name, "ngate") == 0) { // Noise Gate + int i = 0; + float val; + char *str_ptr = optarg; + char *parm = strtok(str_ptr, ","); + while ((parm != nullptr) && (i < 4)) { + if ((parm[0] != '-') && (parm[0] != 0)) { + val = strtof(parm, nullptr); + if (errno == ERANGE) { // parse error + log_fatal( + "error: invalid --ngate: value must be in t,a,h,r " + "format\n"); + return EXIT_FAILURE; + } + if (i > 0) { // Times can't be negative + if (val < 0.0f) { + log_fatal( + "error: invalid --ngate: %f (time cannot be less than " + "zero)\n", + val); + return EXIT_FAILURE; + } + } else { + if (val < 0.0f || val > 1.0f) { + log_fatal( + "error: invalid --ngate: %f (must be between 0.0 and " + "1.0)\n", + val); + return EXIT_FAILURE; + } + } + + switch (i) { + case 0: + ng_thresh_volume = val; + break; + case 1: + ng_attack_time = val; + break; + case 2: + ng_hold_time = val; + break; + case 3: + ng_release_time = val; + break; + } + } + i++; + parm = strtok(NULL, ","); + } } else if (strcmp(long_options[option_index].name, "noaudio") == 0) { disable_audio_capturing = 1; } else if (strcmp(long_options[option_index].name, "audiopreview") == 0) { @@ -1256,6 +1312,10 @@ int PicamOption::parse(int argc, char **argv) { log_debug("audio_sample_rate=%d\n", audio_sample_rate); log_debug("audio_bitrate=%ld\n", audio_bitrate); log_debug("audio_volume_multiply=%f\n", audio_volume_multiply); + log_debug("ng_thresh_volume=%f\n", ng_thresh_volume); + log_debug("ng_attack_time=%f\n", ng_attack_time); + log_debug("ng_hold_time=%f\n", ng_hold_time); + log_debug("ng_release_time=%f\n", ng_release_time); log_debug("is_hlsout_enabled=%d\n", is_hlsout_enabled); log_debug("is_hls_encryption_enabled=%d\n", is_hls_encryption_enabled); log_debug("hls_keyframes_per_segment=%d\n", hls_keyframes_per_segment); diff --git a/picam_option/picam_option.hpp b/picam_option/picam_option.hpp index 3898f39..dabdf1a 100644 --- a/picam_option/picam_option.hpp +++ b/picam_option/picam_option.hpp @@ -211,6 +211,13 @@ class PicamOption float audio_volume_multiply = 1.0f; int audio_min_value = SHRT_MIN; // -32768 int audio_max_value = SHRT_MAX; // 32767 + + // Noise Gate + float ng_thresh_volume = 1.0f; + float ng_attack_time = 0.2f; + float ng_hold_time = 1.0f; + float ng_release_time = 0.5f; + int is_hls_encryption_enabled = 0; char hls_encryption_key_uri[256] = "stream.key"; uint8_t hls_encryption_key[16] = {