Skip to content

Commit

Permalink
Merge pull request #197 from francescovannini/noisegate
Browse files Browse the repository at this point in the history
Feature: Noise Gate. Mute sound when below a threshold.

Fixes #135
  • Loading branch information
iizukanao authored Feb 16, 2023
2 parents eab5065 + 6481a02 commit 8bb943a
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 4 deletions.
26 changes: 24 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,9 @@ Options:
--alsadev <dev> ALSA microphone device (default: hw:0,0)
--volume <num> Amplify audio by multiplying the volume by <num>
(default: 1.0)
--ngate <t,a,h,r> Enable noise gate and set <threshold volume, attack/hold/release times>
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 <dev> Audio preview output device (default: plughw:0,0)
Expand Down Expand Up @@ -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_<value>`, where `<value>` is the name of exposure control.
Expand All @@ -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

Expand Down Expand Up @@ -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:
Expand Down
49 changes: 47 additions & 2 deletions audio/audio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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];
Expand All @@ -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) {
Expand Down
7 changes: 7 additions & 0 deletions audio/audio.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
60 changes: 60 additions & 0 deletions picam_option/picam_option.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ void PicamOption::print_usage() {
log_info(" --alsadev <dev> ALSA microphone device (default: %s)\n", defaultOption.alsa_dev);
log_info(" --volume <num> Amplify audio by multiplying the volume by <num>\n");
log_info(" (default: %.1f)\n", defaultOption.audio_volume_multiply);
log_info(" --ngate <t,a,h,r> Enable noise gate and set <threshold volume, attack/hold/release times>\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 <dev> Audio preview output device (default: %s)\n", defaultOption.audio_preview_dev);
Expand Down Expand Up @@ -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 },
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down
7 changes: 7 additions & 0 deletions picam_option/picam_option.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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] = {
Expand Down

0 comments on commit 8bb943a

Please sign in to comment.