Use EBU R 128 based loudness measurement to implement AGC. (#1024)

* Switch AGC implementation to WebRTC.

* Forgot to check in files.

* Add PR #1024 to changelog.

* WebRTC only supports a limited set of sample rates.

* inMicLevel needs to be 0.

* Forgot some calls to make AGC work.

* Unconditionally enable AGC setting in Filter window.

* Test to increase AGC level.

* Adjust target level so that peaks are +/- 0.4.

* Reduce target level a bit.

* Revert "Reduce target level a bit."

This reverts commit 6282992544.

* Add 4dB pad after AGC.

* More tweaks.

* First pass at AGC implementation using libebur128.

* Try defining CMAKE_POLICY_VERSION_MINIMUM to get around GH build error.

* Swap attack/release naming to match actual definitions.

* Equalizer and speech in plotting should be based on pre-AGC audio.

* Warning cleanup.

* Fix CMake issues on ebur128 in GitHub environment.

* Avoid overshooting target gain.

* Remove CMake debugging.

* current = target AGC check not 100% correct, so don't bother.

* Set limiter level to -1 dB.

* Switch voice keyer button label based on state.

* Add 'Switch to' to beginning of Analog/Digital button.

* User manual update.

* Only update Voice Keyer button on state transitions.

* Revert "Equalizer and speech in plotting should be based on pre-AGC audio."

This reverts commit 206a4d4fcb.

* To Spkr/Hdphns -> Frm Decoder.

* Add 'Modem' to Start/Stop button label.

* all.wav is too long for rade_reporting_mpp, use shorter TX file to improve pass rate.

* Increase silence threshold to -33 LUFS.

* Oops, speech out plot should be before equalizer.

* g_agcEnabled needs memory_order_release on modification.

* Suppress monitor audio from 'From Decoder' plot during TX.
ms-tcp-conn-freeze
Mooneer Salem 2025-09-16 12:17:39 -07:00 committed by GitHub
parent 276ffac3bd
commit 93a01c7e19
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 6989 additions and 67 deletions

View File

@ -44,7 +44,7 @@ jobs:
run: |
sudo apt-get update
sudo apt-get upgrade -y
sudo apt-get install libpulse-dev sox git libasound2-dev libao-dev libgsm1-dev xvfb pipewire pulseaudio-utils pipewire-pulse wireplumber metacity at-spi2-core libdbus-1-dev libgtk-3-dev python3.11-full python3.11-dev python3.11-venv
sudo apt-get install libpulse-dev sox git libasound2-dev libao-dev libgsm1-dev xvfb pipewire pulseaudio-utils pipewire-pulse wireplumber metacity at-spi2-core libdbus-1-dev libgtk-3-dev python3.11-full python3.11-dev python3.11-venv libebur128-dev
- name: Install Python required modules
shell: bash
@ -242,7 +242,7 @@ jobs:
- name: Install common packages
shell: bash
run: |
sudo apt-get install libpulse-dev libspeexdsp-dev libsamplerate0-dev sox git portaudio19-dev libhamlib-dev libasound2-dev libao-dev libgsm1-dev libsndfile-dev xvfb pipewire pulseaudio-utils pipewire-pulse wireplumber metacity at-spi2-core
sudo apt-get install libpulse-dev libspeexdsp-dev libsamplerate0-dev sox git portaudio19-dev libhamlib-dev libasound2-dev libao-dev libgsm1-dev libsndfile-dev xvfb pipewire pulseaudio-utils pipewire-pulse wireplumber metacity at-spi2-core libebur128-dev
- name: Install version-specific packages
if: ${{ matrix.os == 'ubuntu-22.04' }}

View File

@ -27,7 +27,7 @@ jobs:
- uses: gerlero/brew-install@v1
with:
packages: automake libtool numpy sox wxwidgets speexdsp portaudio libsndfile libsamplerate hamlib
packages: automake libtool numpy sox wxwidgets speexdsp portaudio libsndfile libsamplerate hamlib libebur128
- name: Install VB-Cable
shell: bash

View File

@ -354,6 +354,25 @@ include(cmake/BuildRADE.cmake)
#
include(cmake/BuildCodec2.cmake)
# Look for libebur128 (loudness measurement)
if(NOT USE_STATIC_EBUR128)
message(STATUS "Looking for ebur128...")
find_library(LIBEBR128 ebur128)
find_path(LIBEBUR128_INCLUDE_DIR ebur128.h)
message(STATUS " ebur128 library: ${LIBEBUR128}")
message(STATUS " ebur128 headers: ${LIBEBUR128_INCLUDE_DIR}")
if(LIBEBUR128 AND LIBEBUR128_INCLUDE_DIR)
list(APPEND FREEDV_LINK_LIBS ${LIBEBUR128})
include_directories(${LIBEBUR128_INCLUDE_DIR})
else(LIBEBUR128 AND LIBEBUR128_INCLUDE_DIR)
message(STATUS "Will attempt static build of ebur128.")
include(cmake/BuildEbur128.cmake)
endif(LIBEBUR128 AND LIBEBUR128_INCLUDE_DIR)
else(NOT USE_STATIC_LIBEBUR128)
message(STATUS "Will attempt static build of ebur128.")
include(cmake/BuildEbur128.cmake)
endif(NOT USE_STATIC_EBUR128)
if (NOT USE_NATIVE_AUDIO AND LINUX)
message(WARNING "Use of PortAudio on Linux has been deprecated and may be explicitly disallowed in a future release. It is highly recommended that you use FreeDV's built-in PulseAudio support instead as this generally gives better results, especially on distributions that now use pipewire for audio.")
endif(NOT USE_NATIVE_AUDIO AND LINUX)

View File

@ -12,7 +12,8 @@ This document describes how to build the FreeDV GUI program for various operatin
```
$ sudo apt install libspeexdsp-dev libsamplerate0-dev sox git \
libwxgtk3.2-dev libhamlib-dev libasound2-dev libao-dev \
libgsm1-dev libsndfile1-dev cmake module-assistant build-essential
libgsm1-dev libsndfile1-dev cmake module-assistant build-essential \
libebur128-dev
$ git clone https://github.com/drowe67/freedv-gui.git
$ cd freedv-gui
@ -31,7 +32,7 @@ This document describes how to build the FreeDV GUI program for various operatin
$ sudo dnf groupinstall "Development Tools"
$ sudo dnf install cmake wxGTK3-devel libsamplerate-devel \
libsndfile-devel speexdsp-devel hamlib-devel alsa-lib-devel libao-devel \
gsm-devel gcc-c++ sox
gsm-devel gcc-c++ sox libebur128-devel
$ git clone https://github.com/drowe67/freedv-gui.git
$ cd freedv-gui

View File

@ -734,6 +734,14 @@ button will let you hear the received signal from the SSB radio.
Try the Test Wave Files above to get a feel for what a FreeDV signal
looks and sounds like.
## Hearing myself whenever I transmit
Verify audio device settings in FreeDV. If your audio settings are correct,
there are a few other possibilities:
1. If on Windows, make sure all audio effects are disabled on all devices used with FreeDV.
2. Turn off TX monitoring (right-click on PTT button and make sure "Monitor transmitted audio" is unchecked).
## The signal is strong but FreeDV won't get sync and decode
Do you have the correct sideband? See USB or LSB section.
@ -854,7 +862,7 @@ LDPC | Low Density Parity Check Codes - a family of powerful FEC codes
* Add Mic/Speaker volume control to main window. (PR #980, #1028)
* Move less used Spectrum plot configuration to free up space on main window. (PR #996)
* Further audio performance improvements. (PR #975)
* Add Automatic Gain Control (AGC) to microphone input. (PR #997)
* Add Automatic Gain Control (AGC) to microphone input. (PR #997, #1024)
* Linux: Search for and list serial devices from /dev/serial for PTT config. (PR #999)
* Add RADEV1 sample file and remove samples for unsupported modes. (PR #998)
* Various GUI and FreeDV Reporter performance improvements. (PR #1002, #1026)

View File

@ -0,0 +1,36 @@
set(EBUR128_VERSION "1.2.6")
# Static libraries on some platforms need PIC
set(EBUR128_CMAKE_ARGS -DWITH_STATIC_PIC=1 -DBUILD_SHARED_LIBS=0)
if(CMAKE_CROSSCOMPILING)
set(EBUR128_CMAKE_ARGS ${EBUR128_CMAKE_ARGS} -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE})
endif()
# Build ebur128 library
include(ExternalProject)
ExternalProject_Add(build_ebur128
SOURCE_DIR ebur128_src
BINARY_DIR ebur128_build
GIT_REPOSITORY https://github.com/jiixyj/libebur128.git
GIT_TAG "v${EBUR128_VERSION}"
CMAKE_ARGS ${EBUR128_CMAKE_ARGS}
CMAKE_CACHE_ARGS -DCMAKE_OSX_DEPLOYMENT_TARGET:STRING=${CMAKE_OSX_DEPLOYMENT_TARGET} -DCMAKE_OSX_ARCHITECTURES:STRING=${CMAKE_OSX_ARCHITECTURES}
PATCH_COMMAND git apply ${CMAKE_SOURCE_DIR}/cmake/Ebur128_CMake.patch
INSTALL_COMMAND ""
UPDATE_DISCONNECTED 1
)
ExternalProject_Get_Property(build_ebur128 BINARY_DIR)
ExternalProject_Get_Property(build_ebur128 SOURCE_DIR)
add_library(ebur128 STATIC IMPORTED)
add_dependencies(ebur128 build_ebur128)
set_target_properties(ebur128 PROPERTIES
IMPORTED_LOCATION "${BINARY_DIR}/libebur128${CMAKE_STATIC_LIBRARY_SUFFIX}"
IMPORTED_IMPLIB "${BINARY_DIR}/libebur128${CMAKE_IMPORT_LIBRARY_SUFFIX}"
)
set(EBUR128_INCLUDE_DIRS ${CMAKE_BINARY_DIR}/ebur128_src/ebur128 ${CMAKE_BINARY_DIR}/ebur128_build)
list(APPEND FREEDV_LINK_LIBS ebur128)
include_directories(${EBUR128_INCLUDE_DIRS})

View File

@ -0,0 +1,20 @@
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ff8baea..3140d57 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR)
+cmake_minimum_required(VERSION 3.10 FATAL_ERROR)
project(libebur128 C)
option(BUILD_SHARED_LIBS
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 6ffbf17..8eed09d 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.10)
set(ENABLE_TESTS OFF CACHE BOOL "Build test binaries, needs libsndfile")
set(ENABLE_FUZZER OFF CACHE BOOL "Build fuzzer binary")

1
src/3rdparty/CMakeLists.txt vendored 100644
View File

@ -0,0 +1 @@
add_subdirectory(WebRTC_AGC)

View File

@ -0,0 +1,2 @@
add_library(agc STATIC agc.c)
target_include_directories(agc PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

30
src/3rdparty/WebRTC_AGC/LICENSE vendored 100644
View File

@ -0,0 +1,30 @@
============================
Copyright (c) 2011, The WebRTC project authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
* Neither the name of Google nor the names of its contributors may
be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,8 @@
# WebRTC_AGC
Automatic Gain Control Module Port From WebRTC
# Donating
If you found this project useful, consider buying me a coffee
<a href="https://www.buymeacoffee.com/gaozhihan" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/black_img.png" alt="Buy Me A Coffee" style="height: auto !important;width: auto !important;" ></a>

2277
src/3rdparty/WebRTC_AGC/agc.c vendored 100644

File diff suppressed because it is too large Load Diff

430
src/3rdparty/WebRTC_AGC/agc.h vendored 100644
View File

@ -0,0 +1,430 @@
/*
* Copyright (c) 2011 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef MODULES_AUDIO_PROCESSING_AGC_LEGACY_ANALOG_AGC_H_
#define MODULES_AUDIO_PROCESSING_AGC_LEGACY_ANALOG_AGC_H_
//#define MIC_LEVEL_FEEDBACK
#ifdef WEBRTC_AGC_DEBUG_DUMP
#include <stdio.h>
#endif
#include <stdint.h> // NOLINT(build/include)
#include <string.h>
#ifdef WEBRTC_AGC_DEBUG_DUMP
#include <stdio.h>
#endif
#include <stdint.h> // NOLINT(build/include)
#include <assert.h>
// allpass filter coefficients.
static const uint16_t kResampleAllpass1[3] = {3284, 24441, 49528};
static const uint16_t kResampleAllpass2[3] = {12199, 37471, 60255};
typedef struct {
int32_t downState[8];
int16_t HPstate;
int16_t counter;
int16_t logRatio; // log( P(active) / P(inactive) ) (Q10)
int16_t meanLongTerm; // Q10
int32_t varianceLongTerm; // Q8
int16_t stdLongTerm; // Q10
int16_t meanShortTerm; // Q10
int32_t varianceShortTerm; // Q8
int16_t stdShortTerm; // Q10
} AgcVad; // total = 54 bytes
typedef struct {
int32_t capacitorSlow;
int32_t capacitorFast;
int32_t gain;
int32_t gainTable[32];
int16_t gatePrevious;
int16_t agcMode;
AgcVad vadNearend;
AgcVad vadFarend;
#ifdef WEBRTC_AGC_DEBUG_DUMP
FILE* logFile;
int frameCounter;
#endif
} DigitalAgc;
int32_t WebRtcAgc_InitDigital(DigitalAgc *digitalAgcInst, int16_t agcMode);
int32_t WebRtcAgc_ProcessDigital(DigitalAgc *digitalAgcInst,
const int16_t *const *inNear,
size_t num_bands,
int16_t *const *out,
uint32_t FS,
int16_t lowLevelSignal);
int32_t WebRtcAgc_AddFarendToDigital(DigitalAgc *digitalAgcInst,
const int16_t *inFar,
size_t nrSamples);
void WebRtcAgc_InitVad(AgcVad *vadInst);
int16_t WebRtcAgc_ProcessVad(AgcVad *vadInst, // (i) VAD state
const int16_t *in, // (i) Speech signal
size_t nrSamples); // (i) number of samples
int32_t WebRtcAgc_CalculateGainTable(int32_t *gainTable, // Q16
int16_t compressionGaindB, // Q0 (in dB)
int16_t targetLevelDbfs, // Q0 (in dB)
uint8_t limiterEnable,
int16_t analogTarget);
// Errors
#define AGC_UNSPECIFIED_ERROR 18000
#define AGC_UNSUPPORTED_FUNCTION_ERROR 18001
#define AGC_UNINITIALIZED_ERROR 18002
#define AGC_NULL_POINTER_ERROR 18003
#define AGC_BAD_PARAMETER_ERROR 18004
// Warnings
#define AGC_BAD_PARAMETER_WARNING 18050
enum {
kAgcModeUnchanged,
kAgcModeAdaptiveAnalog,
kAgcModeAdaptiveDigital,
kAgcModeFixedDigital
};
enum {
kAgcFalse = 0, kAgcTrue
};
typedef struct {
int16_t targetLevelDbfs; // default 3 (-3 dBOv)
int16_t compressionGaindB; // default 9 dB
uint8_t limiterEnable; // default kAgcTrue (on)
} WebRtcAgcConfig;
#if defined(__cplusplus)
extern "C" {
#endif
/*
* This function analyses the number of samples passed to
* farend and produces any error code that could arise.
*
* Input:
* - agcInst : AGC instance.
* - samples : Number of samples in input vector.
*
* Return value:
* : 0 - Normal operation.
* : -1 - Error.
*/
int WebRtcAgc_GetAddFarendError(void *state, size_t samples);
/*
* This function processes a 10 ms frame of far-end speech to determine
* if there is active speech. The length of the input speech vector must be
* given in samples (80 when FS=8000, and 160 when FS=16000, FS=32000 or
* FS=48000).
*
* Input:
* - agcInst : AGC instance.
* - inFar : Far-end input speech vector
* - samples : Number of samples in input vector
*
* Return value:
* : 0 - Normal operation.
* : -1 - Error
*/
int WebRtcAgc_AddFarend(void *agcInst, const int16_t *inFar, size_t samples);
/*
* This function processes a 10 ms frame of microphone speech to determine
* if there is active speech. The length of the input speech vector must be
* given in samples (80 when FS=8000, and 160 when FS=16000, FS=32000 or
* FS=48000). For very low input levels, the input signal is increased in level
* by multiplying and overwriting the samples in inMic[].
*
* This function should be called before any further processing of the
* near-end microphone signal.
*
* Input:
* - agcInst : AGC instance.
* - inMic : Microphone input speech vector for each band
* - num_bands : Number of bands in input vector
* - samples : Number of samples in input vector
*
* Return value:
* : 0 - Normal operation.
* : -1 - Error
*/
int WebRtcAgc_AddMic(void *agcInst,
int16_t *const *inMic,
size_t num_bands,
size_t samples);
/*
* This function replaces the analog microphone with a virtual one.
* It is a digital gain applied to the input signal and is used in the
* agcAdaptiveDigital mode where no microphone level is adjustable. The length
* of the input speech vector must be given in samples (80 when FS=8000, and 160
* when FS=16000, FS=32000 or FS=48000).
*
* Input:
* - agcInst : AGC instance.
* - inMic : Microphone input speech vector for each band
* - num_bands : Number of bands in input vector
* - samples : Number of samples in input vector
* - micLevelIn : Input level of microphone (static)
*
* Output:
* - inMic : Microphone output after processing (L band)
* - inMic_H : Microphone output after processing (H band)
* - micLevelOut : Adjusted microphone level after processing
*
* Return value:
* : 0 - Normal operation.
* : -1 - Error
*/
int WebRtcAgc_VirtualMic(void *agcInst,
int16_t *const *inMic,
size_t num_bands,
size_t samples,
int32_t micLevelIn,
int32_t *micLevelOut);
/*
* This function processes a 10 ms frame and adjusts (normalizes) the gain both
* analog and digitally. The gain adjustments are done only during active
* periods of speech. The length of the speech vectors must be given in samples
* (80 when FS=8000, and 160 when FS=16000, FS=32000 or FS=48000). The echo
* parameter can be used to ensure the AGC will not adjust upward in the
* presence of echo.
*
* This function should be called after processing the near-end microphone
* signal, in any case after any echo cancellation.
*
* Input:
* - agcInst : AGC instance
* - inNear : Near-end input speech vector for each band
* - num_bands : Number of bands in input/output vector
* - samples : Number of samples in input/output vector
* - inMicLevel : Current microphone volume level
* - echo : Set to 0 if the signal passed to add_mic is
* almost certainly free of echo; otherwise set
* to 1. If you have no information regarding echo
* set to 0.
*
* Output:
* - outMicLevel : Adjusted microphone volume level
* - out : Gain-adjusted near-end speech vector
* : May be the same vector as the input.
* - saturationWarning : A returned value of 1 indicates a saturation event
* has occurred and the volume cannot be further
* reduced. Otherwise will be set to 0.
*
* Return value:
* : 0 - Normal operation.
* : -1 - Error
*/
int WebRtcAgc_Process(void *agcInst,
const int16_t *const *inNear,
size_t num_bands,
size_t samples,
int16_t *const *out,
int32_t inMicLevel,
int32_t *outMicLevel,
int16_t echo,
uint8_t *saturationWarning);
/*
* This function sets the config parameters (targetLevelDbfs,
* compressionGaindB and limiterEnable).
*
* Input:
* - agcInst : AGC instance
* - config : config struct
*
* Output:
*
* Return value:
* : 0 - Normal operation.
* : -1 - Error
*/
int WebRtcAgc_set_config(void *agcInst, WebRtcAgcConfig config);
/*
* This function returns the config parameters (targetLevelDbfs,
* compressionGaindB and limiterEnable).
*
* Input:
* - agcInst : AGC instance
*
* Output:
* - config : config struct
*
* Return value:
* : 0 - Normal operation.
* : -1 - Error
*/
int WebRtcAgc_get_config(void *agcInst, WebRtcAgcConfig *config);
/*
* This function creates and returns an AGC instance, which will contain the
* state information for one (duplex) channel.
*/
void *WebRtcAgc_Create(void);
/*
* This function frees the AGC instance created at the beginning.
*
* Input:
* - agcInst : AGC instance.
*/
void WebRtcAgc_Free(void *agcInst);
/*
* This function initializes an AGC instance.
*
* Input:
* - agcInst : AGC instance.
* - minLevel : Minimum possible mic level
* - maxLevel : Maximum possible mic level
* - agcMode : 0 - Unchanged
* : 1 - Adaptive Analog Automatic Gain Control -3dBOv
* : 2 - Adaptive Digital Automatic Gain Control -3dBOv
* : 3 - Fixed Digital Gain 0dB
* - fs : Sampling frequency
*
* Return value : 0 - Ok
* -1 - Error
*/
int WebRtcAgc_Init(void *agcInst,
int32_t minLevel,
int32_t maxLevel,
int16_t agcMode,
uint32_t fs);
#if defined(__cplusplus)
}
#endif
/* Analog Automatic Gain Control variables:
* Constant declarations (inner limits inside which no changes are done)
* In the beginning the range is narrower to widen as soon as the measure
* 'Rxx160_LP' is inside it. Currently the starting limits are -22.2+/-1dBm0
* and the final limits -22.2+/-2.5dBm0. These levels makes the speech signal
* go towards -25.4dBm0 (-31.4dBov). Tuned with wbfile-31.4dBov.pcm
* The limits are created by running the AGC with a file having the desired
* signal level and thereafter plotting Rxx160_LP in the dBm0-domain defined
* by out=10*log10(in/260537279.7); Set the target level to the average level
* of our measure Rxx160_LP. Remember that the levels are in blocks of 16 in
* Q(-7). (Example matlab code: round(db2pow(-21.2)*16/2^7) )
*/
#define RXX_BUFFER_LEN 10
static const int16_t kMsecSpeechInner = 520;
static const int16_t kMsecSpeechOuter = 340;
static const int16_t kNormalVadThreshold = 400;
static const int16_t kAlphaShortTerm = 6; // 1 >> 6 = 0.0156
static const int16_t kAlphaLongTerm = 10; // 1 >> 10 = 0.000977
typedef struct {
// Configurable parameters/variables
uint32_t fs; // Sampling frequency
int16_t compressionGaindB; // Fixed gain level in dB
int16_t targetLevelDbfs; // Target level in -dBfs of envelope (default -3)
int16_t agcMode; // Hard coded mode (adaptAna/adaptDig/fixedDig)
uint8_t limiterEnable; // Enabling limiter (on/off (default off))
WebRtcAgcConfig defaultConfig;
WebRtcAgcConfig usedConfig;
// General variables
int16_t initFlag;
int16_t lastError;
// Target level parameters
// Based on the above: analogTargetLevel = round((32767*10^(-22/20))^2*16/2^7)
int32_t analogTargetLevel; // = RXX_BUFFER_LEN * 846805; -22 dBfs
int32_t startUpperLimit; // = RXX_BUFFER_LEN * 1066064; -21 dBfs
int32_t startLowerLimit; // = RXX_BUFFER_LEN * 672641; -23 dBfs
int32_t upperPrimaryLimit; // = RXX_BUFFER_LEN * 1342095; -20 dBfs
int32_t lowerPrimaryLimit; // = RXX_BUFFER_LEN * 534298; -24 dBfs
int32_t upperSecondaryLimit; // = RXX_BUFFER_LEN * 2677832; -17 dBfs
int32_t lowerSecondaryLimit; // = RXX_BUFFER_LEN * 267783; -27 dBfs
uint16_t targetIdx; // Table index for corresponding target level
#ifdef MIC_LEVEL_FEEDBACK
uint16_t targetIdxOffset; // Table index offset for level compensation
#endif
int16_t analogTarget; // Digital reference level in ENV scale
// Analog AGC specific variables
int32_t filterState[8]; // For downsampling wb to nb
int32_t upperLimit; // Upper limit for mic energy
int32_t lowerLimit; // Lower limit for mic energy
int32_t Rxx160w32; // Average energy for one frame
int32_t Rxx16_LPw32; // Low pass filtered subframe energies
int32_t Rxx160_LPw32; // Low pass filtered frame energies
int32_t Rxx16_LPw32Max; // Keeps track of largest energy subframe
int32_t Rxx16_vectorw32[RXX_BUFFER_LEN]; // Array with subframe energies
int32_t Rxx16w32_array[2][5]; // Energy values of microphone signal
int32_t env[2][10]; // Envelope values of subframes
int16_t Rxx16pos; // Current position in the Rxx16_vectorw32
int16_t envSum; // Filtered scaled envelope in subframes
int16_t vadThreshold; // Threshold for VAD decision
int16_t inActive; // Inactive time in milliseconds
int16_t msTooLow; // Milliseconds of speech at a too low level
int16_t msTooHigh; // Milliseconds of speech at a too high level
int16_t changeToSlowMode; // Change to slow mode after some time at target
int16_t firstCall; // First call to the process-function
int16_t msZero; // Milliseconds of zero input
int16_t msecSpeechOuterChange; // Min ms of speech between volume changes
int16_t msecSpeechInnerChange; // Min ms of speech between volume changes
int16_t activeSpeech; // Milliseconds of active speech
int16_t muteGuardMs; // Counter to prevent mute action
int16_t inQueue; // 10 ms batch indicator
// Microphone level variables
int32_t micRef; // Remember ref. mic level for virtual mic
uint16_t gainTableIdx; // Current position in virtual gain table
int32_t micGainIdx; // Gain index of mic level to increase slowly
int32_t micVol; // Remember volume between frames
int32_t maxLevel; // Max possible vol level, incl dig gain
int32_t maxAnalog; // Maximum possible analog volume level
int32_t maxInit; // Initial value of "max"
int32_t minLevel; // Minimum possible volume level
int32_t minOutput; // Minimum output volume level
int32_t zeroCtrlMax; // Remember max gain => don't amp low input
int32_t lastInMicLevel;
int16_t scale; // Scale factor for internal volume levels
#ifdef MIC_LEVEL_FEEDBACK
int16_t numBlocksMicLvlSat;
uint8_t micLvlSat;
#endif
// Structs for VAD and digital_agc
AgcVad vadMic;
DigitalAgc digitalAgc;
#ifdef WEBRTC_AGC_DEBUG_DUMP
FILE* fpt;
FILE* agcLog;
int32_t fcount;
#endif
int16_t lowLevelSignal;
} LegacyAgc;
#endif // MODULES_AUDIO_PROCESSING_AGC_LEGACY_ANALOG_AGC_H_

3559
src/3rdparty/WebRTC_AGC/dr_wav.h vendored 100644

File diff suppressed because it is too large Load Diff

268
src/3rdparty/WebRTC_AGC/main.c vendored 100644
View File

@ -0,0 +1,268 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
//采用https://github.com/mackron/dr_libs/blob/master/dr_wav.h 解码
#define DR_WAV_IMPLEMENTATION
#include "dr_wav.h"
#include "agc.h"
#ifndef nullptr
#define nullptr 0
#endif
#ifndef MIN
#define MIN(A, B) ((A) < (B) ? (A) : (B))
#endif
//计时
#include <stdint.h>
#if defined(__APPLE__)
# include <mach/mach_time.h>
#elif defined(_WIN32)
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
#else // __linux
# include <time.h>
# ifndef CLOCK_MONOTONIC //_RAW
# define CLOCK_MONOTONIC CLOCK_REALTIME
# endif
#endif
static
uint64_t nanotimer() {
static int ever = 0;
#if defined(__APPLE__)
static mach_timebase_info_data_t frequency;
if (!ever) {
if (mach_timebase_info(&frequency) != KERN_SUCCESS) {
return 0;
}
ever = 1;
}
return 0;
#elif defined(_WIN32)
static LARGE_INTEGER frequency;
if (!ever) {
QueryPerformanceFrequency(&frequency);
ever = 1;
}
LARGE_INTEGER t;
QueryPerformanceCounter(&t);
return (t.QuadPart * (uint64_t) 1e9) / frequency.QuadPart;
#else // __linux
struct timespec t;
if (!ever) {
if (clock_gettime(CLOCK_MONOTONIC, &t) != 0) {
return 0;
}
ever = 1;
}
clock_gettime(CLOCK_MONOTONIC, &t);
return (t.tv_sec * (uint64_t)1e9) + t.tv_nsec;
#endif
}
static double now() {
static uint64_t epoch = 0;
if (!epoch) {
epoch = nanotimer();
}
return (nanotimer() - epoch) / 1e9;
};
double calcElapsed(double start, double end) {
double took = -start;
return took + end;
}
//写wav文件
void wavWrite_int16(char *filename, int16_t *buffer, size_t sampleRate, size_t totalSampleCount, unsigned int channels) {
drwav_data_format format = {};
format.container = drwav_container_riff; // <-- drwav_container_riff = normal WAV files, drwav_container_w64 = Sony Wave64.
format.format = DR_WAVE_FORMAT_PCM; // <-- Any of the DR_WAVE_FORMAT_* codes.
format.channels = channels;
format.sampleRate = (drwav_uint32) sampleRate;
format.bitsPerSample = 16;
drwav *pWav = drwav_open_file_write(filename, &format);
if (pWav) {
drwav_uint64 samplesWritten = drwav_write(pWav, totalSampleCount, buffer);
drwav_uninit(pWav);
if (samplesWritten != totalSampleCount) {
fprintf(stderr, "ERROR\n");
exit(1);
}
}
}
//读取wav文件
int16_t *wavRead_int16(char *filename, uint32_t *sampleRate, uint64_t *totalSampleCount, unsigned int* channels) {
int16_t *buffer = drwav_open_and_read_file_s16(filename, channels, sampleRate, totalSampleCount);
if (buffer == nullptr) {
printf("读取wav文件失败.");
}
return buffer;
}
//分割路径函数
void splitpath(const char *path, char *drv, char *dir, char *name, char *ext) {
const char *end;
const char *p;
const char *s;
if (path[0] && path[1] == ':') {
if (drv) {
*drv++ = *path++;
*drv++ = *path++;
*drv = '\0';
}
} else if (drv)
*drv = '\0';
for (end = path; *end && *end != ':';)
end++;
for (p = end; p > path && *--p != '\\' && *p != '/';)
if (*p == '.') {
end = p;
break;
}
if (ext)
for (s = end; (*ext = *s++);)
ext++;
for (p = end; p > path;)
if (*--p == '\\' || *p == '/') {
p++;
break;
}
if (name) {
for (s = p; s < end;)
*name++ = *s++;
*name = '\0';
}
if (dir) {
for (s = path; s < p;)
*dir++ = *s++;
*dir = '\0';
}
}
int agcProcess(int16_t *buffer, uint32_t sampleRate, size_t samplesCount, int16_t agcMode) {
if (buffer == nullptr) return -1;
if (samplesCount == 0) return -1;
WebRtcAgcConfig agcConfig;
agcConfig.compressionGaindB = 9; // default 9 dB
agcConfig.limiterEnable = 1; // default kAgcTrue (on)
agcConfig.targetLevelDbfs = 3; // default 3 (-3 dBOv)
int minLevel = 0;
int maxLevel = 255;
size_t samples = MIN(160, sampleRate / 100);
if (samples == 0) return -1;
const int maxSamples = 320;
int16_t *input = buffer;
size_t nTotal = (samplesCount / samples);
void *agcInst = WebRtcAgc_Create();
if (agcInst == NULL) return -1;
int status = WebRtcAgc_Init(agcInst, minLevel, maxLevel, agcMode, sampleRate);
if (status != 0) {
printf("WebRtcAgc_Init fail\n");
WebRtcAgc_Free(agcInst);
return -1;
}
status = WebRtcAgc_set_config(agcInst, agcConfig);
if (status != 0) {
printf("WebRtcAgc_set_config fail\n");
WebRtcAgc_Free(agcInst);
return -1;
}
size_t num_bands = 1;
int inMicLevel, outMicLevel = -1;
int16_t out_buffer[maxSamples];
int16_t *out16 = out_buffer;
uint8_t saturationWarning = 1; //是否有溢出发生增益放大以后的最大值超过了65536
int16_t echo = 0; //增益放大是否考虑回声影响
for (int i = 0; i < nTotal; i++) {
inMicLevel = 0;
int nAgcRet = WebRtcAgc_Process(agcInst, (const int16_t *const *) &input, num_bands, samples,
(int16_t *const *) &out16, inMicLevel, &outMicLevel, echo,
&saturationWarning);
if (nAgcRet != 0) {
printf("failed in WebRtcAgc_Process\n");
WebRtcAgc_Free(agcInst);
return -1;
}
memcpy(input, out_buffer, samples * sizeof(int16_t));
input += samples;
}
const size_t remainedSamples = samplesCount - nTotal * samples;
if (remainedSamples > 0) {
if (nTotal > 0) {
input = input - samples + remainedSamples;
}
inMicLevel = 0;
int nAgcRet = WebRtcAgc_Process(agcInst, (const int16_t *const *) &input, num_bands, samples,
(int16_t *const *) &out16, inMicLevel, &outMicLevel, echo,
&saturationWarning);
if (nAgcRet != 0) {
printf("failed in WebRtcAgc_Process during filtering the last chunk\n");
WebRtcAgc_Free(agcInst);
return -1;
}
memcpy(&input[samples-remainedSamples], &out_buffer[samples-remainedSamples], remainedSamples * sizeof(int16_t));
input += samples;
}
WebRtcAgc_Free(agcInst);
return 1;
}
void auto_gain(char *in_file, char *out_file) {
//音频采样率
uint32_t sampleRate = 0;
//总音频采样数
uint64_t inSampleCount = 0;
unsigned int channels = 0;
int16_t *inBuffer = wavRead_int16(in_file, &sampleRate, &inSampleCount, &channels);
//如果加载成功
if (inBuffer != nullptr) {
// kAgcModeAdaptiveAnalog 模拟音量调节
// kAgcModeAdaptiveDigital 自适应增益
// kAgcModeFixedDigital 固定增益
double startTime = now();
agcProcess(inBuffer, sampleRate, inSampleCount, kAgcModeAdaptiveDigital);
double elapsed_time = calcElapsed(startTime, now());
printf("time: %d ms\n ", (int) (elapsed_time * 1000));
wavWrite_int16(out_file, inBuffer, sampleRate, inSampleCount, channels);
free(inBuffer);
}
}
int main(int argc, char *argv[]) {
printf("WebRTC Automatic Gain Control\n");
printf("博客:http://cpuimage.cnblogs.com/\n");
printf("音频自动增益\n");
if (argc < 2)
return -1;
char *in_file = argv[1];
char drive[3];
char dir[256];
char fname[256];
char ext[256];
char out_file[1024];
splitpath(in_file, drive, dir, fname, ext);
sprintf(out_file, "%s%s%s_out%s", drive, dir, fname, ext);
auto_gain(in_file, out_file);
printf("按任意键退出程序 \n");
getchar();
return 0;
}

View File

@ -23,8 +23,9 @@ set(FREEDV_LINK_LIBS_OSX
add_definitions(-DULOG_HAVE_TIME)
add_definitions(-DULOG_NO_COLOR)
# Include third party headers
# Include third party headers and components
include_directories(3rdparty)
add_subdirectory(3rdparty)
# Include generated headers
include_directories(${CMAKE_BINARY_DIR}/generated)

View File

@ -80,10 +80,10 @@ class PlotScalar: public PlotPanel
int m_bar_graph; // non zero to plot bar graphs
int m_logy; // plot graph on log scale
void draw(wxGraphicsContext* ctx, bool repaintDataOnly = false);
void draw(wxGraphicsContext* ctx, bool repaintDataOnly = false) override;
void drawGraticuleFast(wxGraphicsContext* ctx, bool repaintDataOnly);
void OnSize(wxSizeEvent& event);
void OnShow(wxShowEvent& event);
void OnSize(wxSizeEvent& event) override;
void OnShow(wxShowEvent& event) override;
virtual bool repaintAll_(wxPaintEvent& evt) override;

View File

@ -55,15 +55,15 @@ class PlotWaterfall : public PlotPanel
unsigned heatmap(float val, float min, float max);
void OnSize(wxSizeEvent& event);
void OnShow(wxShowEvent& event);
void drawGraticule(wxGraphicsContext* ctx);
void draw(wxGraphicsContext* gc, bool repaintDataOnly = false);
void OnSize(wxSizeEvent& event) override;
void OnShow(wxShowEvent& event) override;
void drawGraticule(wxGraphicsContext* ctx) override;
void draw(wxGraphicsContext* gc, bool repaintDataOnly = false) override;
void plotPixelData();
void OnMouseLeftDoubleClick(wxMouseEvent& event);
void OnMouseRightDoubleClick(wxMouseEvent& event);
void OnMouseMiddleDown(wxMouseEvent& event);
void OnMouseWheelMoved(wxMouseEvent& event);
void OnMouseWheelMoved(wxMouseEvent& event) override;
void OnKeyDown(wxKeyEvent& event);
virtual bool repaintAll_(wxPaintEvent& evt) override;

View File

@ -752,7 +752,7 @@ void FilterDlg::OnSpeexppEnable(wxScrollEvent& event) {
void FilterDlg::OnAgcEnable(wxScrollEvent& event) {
wxGetApp().appConfiguration.filterConfiguration.agcEnabled = m_ckboxAgcEnabled->GetValue();
g_agcEnabled = wxGetApp().appConfiguration.filterConfiguration.agcEnabled; // forces immediate change at pipeline level
g_agcEnabled.store(wxGetApp().appConfiguration.filterConfiguration.agcEnabled, std::memory_order_release); // forces immediate change at pipeline level
ExchangeData(EXCHANGE_DATA_OUT);
}
@ -766,8 +766,7 @@ void FilterDlg::On700C_EQ(wxScrollEvent& event) {
void FilterDlg::updateControlState()
{
// AGC currently requires Speex.
m_ckboxAgcEnabled->Enable(wxGetApp().appConfiguration.filterConfiguration.speexppEnable);
m_ckboxAgcEnabled->Enable(true);
m_MicInBass.sliderFreq->Enable(wxGetApp().appConfiguration.filterConfiguration.micInChannel.eqEnable);
m_MicInBass.sliderGain->Enable(wxGetApp().appConfiguration.filterConfiguration.micInChannel.eqEnable);

View File

@ -736,7 +736,7 @@ void MainFrame::loadConfiguration_()
});
// Load AGC state
g_agcEnabled = wxGetApp().appConfiguration.filterConfiguration.agcEnabled;
g_agcEnabled.store(wxGetApp().appConfiguration.filterConfiguration.agcEnabled, std::memory_order_release);
g_txLevel = wxGetApp().appConfiguration.transmitLevel;
float dbLoss = g_txLevel / 10.0;
@ -1037,7 +1037,7 @@ MainFrame::MainFrame(wxWindow *parent) : TopFrame(parent, wxID_ANY, _("FreeDV ")
// Add Speech Output window
m_panelSpeechOut = new PlotScalar((wxFrame*) m_auiNbookCtrl, 1, WAVEFORM_PLOT_TIME, 1.0/WAVEFORM_PLOT_FS, -1, 1, 1, 0.2, "%2.1f", 0);
m_auiNbookCtrl->AddPage(m_panelSpeechOut, _("To Spkr/Hdphns"), false, wxNullBitmap);
m_auiNbookCtrl->AddPage(m_panelSpeechOut, _("Frm Decoder"), false, wxNullBitmap);
g_plotSpeechOutFifo = codec2_fifo_create(4*WAVEFORM_PLOT_BUF);
// Add Scatter Plot window
@ -2620,7 +2620,7 @@ void MainFrame::OnTogBtnOnOff(wxCommandEvent& event)
if (m_RxRunning)
{
m_togBtnOnOff->SetLabel(wxT("&Stop"));
m_togBtnOnOff->SetLabel(wxT("&Stop Modem"));
}
m_togBtnOnOff->SetValue(m_RxRunning);
m_togBtnOnOff->Enable(true);
@ -2651,7 +2651,7 @@ void MainFrame::OnTogBtnOnOff(wxCommandEvent& event)
m_btnTogPTT->Enable(m_RxRunning);
optionsDlg->setSessionActive(m_RxRunning);
m_togBtnOnOff->SetValue(m_RxRunning);
m_togBtnOnOff->SetLabel(wxT("&Start"));
m_togBtnOnOff->SetLabel(wxT("&Start Modem"));
m_togBtnOnOff->Enable(true);
if (terminating_)

View File

@ -529,6 +529,7 @@ class MainFrame : public TopFrame
void updateReportingFreqList_();
void initializeFreeDVReporter_();
void updateVoiceKeyerButtonLabel_();
void onFrequencyModeChange_(IRigFrequencyController*, uint64_t freq, IRigFrequencyController::Mode mode);
void onRadioConnected_(IRigController* ptr);

View File

@ -1158,14 +1158,14 @@ void MainFrame::OnTogBtnAnalogClick (wxCommandEvent& event)
m_panelSpectrum->setFreqScale(MODEM_STATS_NSPEC*((float)MAX_F_HZ/(FS/2)));
m_panelWaterfall->setFs(FS);
m_togBtnAnalog->SetLabel(wxT("Di&gital"));
m_togBtnAnalog->SetLabel(wxT("Switch to Di&gital"));
}
else {
g_analog = 0;
m_panelSpectrum->setFreqScale(MODEM_STATS_NSPEC*((float)MAX_F_HZ/(freedvInterface.getRxModemSampleRate()/2)));
m_panelWaterfall->setFs(freedvInterface.getRxModemSampleRate());
m_togBtnAnalog->SetLabel(wxT("A&nalog"));
m_togBtnAnalog->SetLabel(wxT("Switch to A&nalog"));
}
// Report analog change to registered reporters

View File

@ -0,0 +1,188 @@
//=========================================================================
// Name: AgcStep.cpp
// Purpose: Describes an AGC step in the audio pipeline.
//
// Authors: Mooneer Salem
// License:
//
// All rights reserved.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2.1,
// as published by the Free Software Foundation. This program is
// distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, see <http://www.gnu.org/licenses/>.
//
//=========================================================================
#include <atomic>
#include "AgcStep.h"
#include "../defines.h"
#include "../util/logging/ulog.h"
#include "ebur128.h" // from libebur128
#include <assert.h>
// AGC settings
constexpr float AGC_LOUDNESS_TARGET_LUFS = -23.0;
constexpr float AGC_MAX_GAIN_DB = 12.0;
constexpr float AGC_MIN_GAIN_DB = -20.0;
constexpr float AGC_ATTACK_TIME_SEC = 0.5;
constexpr float AGC_RELEASE_TIME_SEC = 6.0;
constexpr float SILENCE_THRESHOLD_LUFS = -33.0;
constexpr int LIMITER_LEVEL_DB = -1;
constexpr int TEN_MS_DIVIDER = 100;
constexpr int MAX_AGC_SAMPLES = 160;
AgcStep::AgcStep(int sampleRate)
: sampleRate_(sampleRate == 8000 || sampleRate == 16000 || sampleRate == 32000 || sampleRate == 48000 ? sampleRate : 48000)
, targetGainDb_(0.0)
, currentGainDb_(0.0)
, inputSampleFifo_(sampleRate / 2)
{
numSamplesPerRun_ = std::min(MAX_AGC_SAMPLES, sampleRate_ / TEN_MS_DIVIDER); // 10ms blocks, 160 max samples
assert(numSamplesPerRun_ > 0);
// Configure WebRTC as solely a limiter (i.e. no AGC)
agcState_ = WebRtcAgc_Create();
assert(agcState_ != nullptr);
auto status = WebRtcAgc_Init(agcState_, 0, 255, kAgcModeUnchanged, sampleRate_);
if (status != 0)
{
log_error("Could not initialize WebRTC AGC (err = %d)", status);
WebRtcAgc_Free(agcState_);
agcState_ = nullptr;
}
// Set AGC configuration
agcConfig_.compressionGaindB = 0; // default 9 dB
agcConfig_.limiterEnable = 1; // default kAgcTrue (on)
agcConfig_.targetLevelDbfs = -LIMITER_LEVEL_DB; // default 3 (-3 dBOv)
status = WebRtcAgc_set_config(agcState_, agcConfig_);
if (status != 0)
{
log_error("Could not initialize WebRTC AGC config (err = %d)", status);
WebRtcAgc_Free(agcState_);
agcState_ = nullptr;
}
ebur128State_ = ebur128_init(1, sampleRate_, EBUR128_MODE_S);
assert(ebur128State_ != nullptr);
// Pre-allocate buffers so we don't have to do so during real-time operation.
outputSamples_ = std::make_unique<short[]>(sampleRate);
assert(outputSamples_ != nullptr);
tmpInput_ = std::make_unique<short[]>(numSamplesPerRun_);
assert(tmpInput_ != nullptr);
}
AgcStep::~AgcStep()
{
outputSamples_ = nullptr;
WebRtcAgc_Free(agcState_);
ebur128_destroy((ebur128_state**)&ebur128State_);
}
int AgcStep::getInputSampleRate() const
{
return sampleRate_;
}
int AgcStep::getOutputSampleRate() const
{
return sampleRate_;
}
short* AgcStep::execute(short* inputSamples, int numInputSamples, int* numOutputSamples)
{
ebur128_state* state = static_cast<ebur128_state*>(ebur128State_);
*numOutputSamples = 0;
short* outputSamples = outputSamples_.get();
int numRuns = (inputSampleFifo_.numUsed() + numInputSamples) / numSamplesPerRun_;
if (numRuns > 0)
{
*numOutputSamples = numRuns * numSamplesPerRun_;
short* tmpOutput = outputSamples;
short* tmpInput = tmpInput_.get();
inputSampleFifo_.write(inputSamples, numInputSamples);
while (inputSampleFifo_.numUsed() >= numSamplesPerRun_)
{
inputSampleFifo_.read(tmpInput, numSamplesPerRun_);
// Step 1: feed samples into ebur128 and return current
// loudness in LUFS.
ebur128_add_frames_short(state, tmpInput, numSamplesPerRun_);
double lufs = 0.0;
auto result = ebur128_loudness_momentary(state, &lufs);
if (result == EBUR128_SUCCESS && lufs != -HUGE_VAL && lufs > SILENCE_THRESHOLD_LUFS)
{
// Returned loudness is valid.
// Step 2: calculate target gain. Assume LUFS = dbFS (?)
targetGainDb_ = AGC_LOUDNESS_TARGET_LUFS - lufs;
if (targetGainDb_ >= AGC_MAX_GAIN_DB) targetGainDb_ = AGC_MAX_GAIN_DB;
if (targetGainDb_ <= AGC_MIN_GAIN_DB) targetGainDb_ = AGC_MIN_GAIN_DB;
// Step 3: increment/decrement current gain in the direction of target.
float agcInterval = 0;
if (targetGainDb_ < currentGainDb_)
{
agcInterval = AGC_ATTACK_TIME_SEC;
}
else
{
agcInterval = AGC_RELEASE_TIME_SEC;
}
currentGainDb_ += ((targetGainDb_ - currentGainDb_) / agcInterval) * ((float)numSamplesPerRun_ / sampleRate_);
//log_info("LUFS: %f, targetGain: %f, currentGain: %f", lufs, targetGainDb_, currentGainDb_);
}
// Scale samples based on current gain.
float scaleFactor = exp(currentGainDb_/20.0 * log(10.0));
for (auto ctr = 0; ctr < numSamplesPerRun_; ctr++)
{
tmpInput[ctr] *= scaleFactor;
}
// Run WebRTC to make sure we don't clip.
int outMicLevel = 0;
int inMicLevel = 0;
short echo = 0;
unsigned char saturationWarning = 1;
auto status = WebRtcAgc_Process(
agcState_, const_cast<const int16_t *const *>(&tmpInput), 1, numSamplesPerRun_,
const_cast<int16_t *const *>(&tmpOutput), inMicLevel, &outMicLevel, echo, &saturationWarning);
if (status != 0)
{
// XXX - not RT-safe
log_error("Failed processing AGC (err = %d)", status);
}
tmpOutput += numSamplesPerRun_;
}
}
else if (numInputSamples > 0 && inputSamples != nullptr)
{
inputSampleFifo_.write(inputSamples, numInputSamples);
}
return outputSamples;
}
void AgcStep::reset()
{
inputSampleFifo_.reset();
currentGainDb_ = 0;
targetGainDb_ = 0;
}

View File

@ -0,0 +1,59 @@
//=========================================================================
// Name: AgcStep.h
// Purpose: Describes an AGC step in the audio pipeline.
//
// Authors: Mooneer Salem
// License:
//
// All rights reserved.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2.1,
// as published by the Free Software Foundation. This program is
// distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, see <http://www.gnu.org/licenses/>.
//
//=========================================================================
#ifndef AUDIO_PIPELINE__AGC_STEP_H
#define AUDIO_PIPELINE__AGC_STEP_H
#include "IPipelineStep.h"
#include "../util/GenericFIFO.h"
#include "agc.h"
#include <memory>
class AgcStep : public IPipelineStep
{
public:
AgcStep(int sampleRate);
virtual ~AgcStep();
virtual int getInputSampleRate() const override;
virtual int getOutputSampleRate() const override;
virtual short* execute(short* inputSamples, int numInputSamples, int* numOutputSamples) override;
virtual void reset() override;
private:
int sampleRate_;
float targetGainDb_;
float currentGainDb_;
WebRtcAgcConfig agcConfig_;
void* agcState_;
void* ebur128State_;
int numSamplesPerRun_;
GenericFIFO<short> inputSampleFifo_;
std::unique_ptr<short[]> outputSamples_;
std::unique_ptr<short[]> tmpInput_;
};
#endif // AUDIO_PIPELINE__AGC_STEP_H

View File

@ -1,4 +1,5 @@
add_library(fdv_audio_pipeline STATIC
AgcStep.cpp
AudioPipeline.cpp
ComputeRfSpectrumStep.cpp
EitherOrStep.cpp
@ -23,9 +24,10 @@ add_library(fdv_audio_pipeline STATIC
rade_text.c
)
target_link_libraries(fdv_audio_pipeline PRIVATE ebur128 agc)
target_include_directories(fdv_audio_pipeline PRIVATE ${CODEC2_INCLUDE_DIRS} ${CMAKE_CURRENT_SOURCE_DIR}/.. ${CMAKE_CURRENT_BINARY_DIR}/..)
add_dependencies(fdv_audio_pipeline rade opus codec2)
add_dependencies(fdv_audio_pipeline rade opus codec2 ebur128 agc)
if(BOOTSTRAP_WXWIDGETS)
add_dependencies(fdv_audio_pipeline wx::core wx::base wx::aui wx::html wx::net wx::adv wx::propgrid wx::xrc)

View File

@ -27,8 +27,6 @@
#include <assert.h>
extern std::atomic<bool> g_agcEnabled;
SpeexStep::SpeexStep(int sampleRate)
: sampleRate_(sampleRate)
, numSamplesPerSpeexRun_((FRAME_DURATION_MS * sampleRate_) / MS_TO_SEC)
@ -41,8 +39,6 @@ SpeexStep::SpeexStep(int sampleRate)
sampleRate_);
assert(speexStateObj_ != nullptr);
updateAgcState_();
// Pre-allocate buffers so we don't have to do so during real-time operation.
outputSamples_ = std::make_unique<short[]>(sampleRate);
assert(outputSamples_ != nullptr);
@ -66,8 +62,6 @@ int SpeexStep::getOutputSampleRate() const
short* SpeexStep::execute(short* inputSamples, int numInputSamples, int* numOutputSamples)
{
updateAgcState_();
*numOutputSamples = 0;
short* outputSamples = outputSamples_.get();
@ -99,20 +93,3 @@ void SpeexStep::reset()
{
inputSampleFifo_.reset();
}
void SpeexStep::updateAgcState_()
{
int32_t newAgcState = g_agcEnabled ? 1 : 0;
speex_preprocess_ctl(speexStateObj_, SPEEX_PREPROCESS_SET_AGC, &newAgcState);
if (newAgcState)
{
// Experimentally determined to be such that normal speaking creates peaks +/- ~0.4.
// Used MacBook Pro built-in microphone for tests.
float newAgcLevel = 12000;
speex_preprocess_ctl(speexStateObj_, SPEEX_PREPROCESS_SET_AGC_LEVEL, &newAgcLevel);
uint32_t maxGainDb = 40;
speex_preprocess_ctl(speexStateObj_, SPEEX_PREPROCESS_SET_AGC_MAX_GAIN, &maxGainDb);
}
}

View File

@ -46,8 +46,6 @@ private:
int numSamplesPerSpeexRun_;
GenericFIFO<short> inputSampleFifo_;
std::unique_ptr<short[]> outputSamples_;
void updateAgcState_();
};

View File

@ -23,6 +23,10 @@
#include <chrono>
using namespace std::chrono_literals;
// WebRTC uses FS, which is defined in defines.h. Thus, it needs to be included
// first.
#include "AgcStep.h"
// This forces us to use freedv-gui's version rather than another one.
// TBD -- may not be needed once we fully switch over to the audio pipeline.
#include "../defines.h"
@ -101,6 +105,7 @@ extern float g_RxFreqOffsetHz;
extern float g_sig_pwr_av;
extern std::atomic<bool> g_voice_keyer_tx;
extern bool g_eoo_enqueued;
extern std::atomic<bool> g_agcEnabled;
#include <speex/speex_preprocess.h>
@ -196,7 +201,20 @@ void TxRxThread::initializePipeline_()
eitherOrProcessSpeex,
eitherOrBypassSpeex);
pipeline_->appendPipelineStep(eitherOrSpeexStep);
// AGC step (optional)
auto eitherOrProcessAgc = new AudioPipeline(inputSampleRate_, inputSampleRate_);
auto eitherOrBypassAgc = new AudioPipeline(inputSampleRate_, inputSampleRate_);
auto agcStep = new AgcStep(inputSampleRate_);
eitherOrProcessAgc->appendPipelineStep(agcStep);
auto eitherOrAgcStep = new EitherOrStep(
[]() { return g_agcEnabled.load(std::memory_order_acquire); },
eitherOrProcessAgc,
eitherOrBypassAgc);
pipeline_->appendPipelineStep(eitherOrAgcStep);
// Equalizer step (optional based on filter state)
auto equalizerStep = new EqualizerStep(
inputSampleRate_,
@ -388,6 +406,18 @@ void TxRxThread::initializePipeline_()
helper_
);
rfDemodulationPipeline->appendPipelineStep(rfDemodulationStep);
// Resample for plot step (speech out)
auto resampleForPlotOutStep = new ResampleForPlotStep(g_plotSpeechOutFifo);
auto resampleForPlotOutPipeline = new AudioPipeline(outputSampleRate_, resampleForPlotOutStep->getOutputSampleRate());
#if defined(ENABLE_FASTER_PLOTS)
auto resampleForPlotOutResampler = new ResampleStep(outputSampleRate_, resampleForPlotOutStep->getInputSampleRate(), true); // need to create manually to get access to "plot only" optimizations
resampleForPlotOutPipeline->appendPipelineStep(resampleForPlotOutResampler);
#endif // defined(ENABLE_FASTER_PLOTS)
resampleForPlotOutPipeline->appendPipelineStep(resampleForPlotOutStep);
auto resampleForPlotOutTap = new TapStep(outputSampleRate_, resampleForPlotOutPipeline);
rfDemodulationPipeline->appendPipelineStep(resampleForPlotOutTap);
// Replace received audio with microphone audio if we're monitoring TX/voice keyer recording.
if (equalizedMicAudioLink_ != nullptr)
@ -456,18 +486,6 @@ void TxRxThread::initializePipeline_()
&g_rxUserdata->sbqSpkOutVol);
pipeline_->appendPipelineStep(equalizerStep);
// Resample for plot step (speech out)
auto resampleForPlotOutStep = new ResampleForPlotStep(g_plotSpeechOutFifo);
auto resampleForPlotOutPipeline = new AudioPipeline(outputSampleRate_, resampleForPlotOutStep->getOutputSampleRate());
#if defined(ENABLE_FASTER_PLOTS)
auto resampleForPlotOutResampler = new ResampleStep(outputSampleRate_, resampleForPlotOutStep->getInputSampleRate(), true); // need to create manually to get access to "plot only" optimizations
resampleForPlotOutPipeline->appendPipelineStep(resampleForPlotOutResampler);
#endif // defined(ENABLE_FASTER_PLOTS)
resampleForPlotOutPipeline->appendPipelineStep(resampleForPlotOutStep);
auto resampleForPlotOutTap = new TapStep(outputSampleRate_, resampleForPlotOutPipeline);
pipeline_->appendPipelineStep(resampleForPlotOutTap);
// Clear anything in the FIFO before resuming decode.
clearFifos_();
}

View File

@ -732,21 +732,21 @@ TopFrame::TopFrame(wxWindow* parent, wxWindowID id, const wxString& title, const
//-------------------------------
// Stop/Stop signal processing (rx and tx)
//-------------------------------
m_togBtnOnOff = new wxToggleButton(controlBox, wxID_ANY, _("&Start"), wxDefaultPosition, wxDefaultSize, 0);
m_togBtnOnOff = new wxToggleButton(controlBox, wxID_ANY, _("&Start Modem"), wxDefaultPosition, wxDefaultSize, 0);
m_togBtnOnOff->SetToolTip(_("Begin/End receiving data."));
sbSizer5->Add(m_togBtnOnOff, 0, wxALL | wxEXPAND, 5);
//------------------------------
// Analog Passthrough Toggle
//------------------------------
m_togBtnAnalog = new wxToggleButton(controlBox, wxID_ANY, _("A&nalog"), wxDefaultPosition, wxDefaultSize, 0);
m_togBtnAnalog = new wxToggleButton(controlBox, wxID_ANY, _("Switch to A&nalog"), wxDefaultPosition, wxDefaultSize, 0);
m_togBtnAnalog->SetToolTip(_("Toggle analog/digital operation."));
sbSizer5->Add(m_togBtnAnalog, 0, wxALL | wxEXPAND, 5);
//------------------------------
// Voice Keyer Toggle
//------------------------------
m_togBtnVoiceKeyer = new wxToggleButton(controlBox, wxID_ANY, _("Voice &Keyer"), wxDefaultPosition, wxDefaultSize, 0);
m_togBtnVoiceKeyer = new wxToggleButton(controlBox, wxID_ANY, _("Start Voice &Keyer"), wxDefaultPosition, wxDefaultSize, 0);
m_togBtnVoiceKeyer->SetToolTip(_("Toggle Voice Keyer. Right-click for additional options."));
sbSizer5->Add(m_togBtnVoiceKeyer, 0, wxALL | wxEXPAND, 5);
@ -989,7 +989,7 @@ TopFrame::~TopFrame()
void TopFrame::setVoiceKeyerButtonLabel_(wxString filename)
{
wxString vkLabel = _("Voice Keyer");
wxString vkLabel = _("Start Voice &Keyer");
int vkLabelWidth = 0;
int filenameWidth = 0;
int tmp = 0;

View File

@ -53,6 +53,11 @@ void MainFrame::OnTogBtnVoiceKeyerClick (wxCommandEvent& event)
}
m_togBtnVoiceKeyer->SetValue(true);
auto currentLabel = m_togBtnVoiceKeyer->GetLabel();
currentLabel.Replace(_("Start Voice Keyer"), _("Stop Voice Keyer"), false);
m_togBtnVoiceKeyer->SetLabel(currentLabel);
VoiceKeyerProcessEvent(VK_START);
}
else
@ -256,6 +261,15 @@ int MainFrame::VoiceKeyerStartTx(void)
return next_state;
}
void MainFrame::updateVoiceKeyerButtonLabel_()
{
if (!m_togBtnVoiceKeyer->GetValue())
{
auto currentLabel = m_togBtnVoiceKeyer->GetLabel();
currentLabel.Replace(_("Stop Voice &Keyer"), _("Start Voice &Keyer"), false);
m_togBtnVoiceKeyer->SetLabel(currentLabel);
}
}
void MainFrame::VoiceKeyerProcessEvent(int vk_event) {
int next_state = vk_state;
@ -288,6 +302,7 @@ void MainFrame::VoiceKeyerProcessEvent(int vk_event) {
togglePTT();
m_togBtnVoiceKeyer->SetValue(false);
m_togBtnVoiceKeyer->SetBackgroundColour(wxNullColour);
updateVoiceKeyerButtonLabel_();
next_state = VK_IDLE;
CallAfter([&]() { StopPlayFileToMicIn(); });
}
@ -301,6 +316,7 @@ void MainFrame::VoiceKeyerProcessEvent(int vk_event) {
if (vk_repeat_counter > vk_repeats) {
m_togBtnVoiceKeyer->SetValue(false);
m_togBtnVoiceKeyer->SetBackgroundColour(wxNullColour);
updateVoiceKeyerButtonLabel_();
next_state = VK_IDLE;
}
else {
@ -333,6 +349,7 @@ void MainFrame::VoiceKeyerProcessEvent(int vk_event) {
if (vk_event == VK_SPACE_BAR) {
m_togBtnVoiceKeyer->SetValue(false);
m_togBtnVoiceKeyer->SetBackgroundColour(wxNullColour);
updateVoiceKeyerButtonLabel_();
next_state = VK_IDLE;
}
@ -347,6 +364,7 @@ void MainFrame::VoiceKeyerProcessEvent(int vk_event) {
if (vk_event == VK_SPACE_BAR) {
m_togBtnVoiceKeyer->SetValue(false);
m_togBtnVoiceKeyer->SetBackgroundColour(wxNullColour);
updateVoiceKeyerButtonLabel_();
next_state = VK_IDLE;
}
@ -364,6 +382,7 @@ void MainFrame::VoiceKeyerProcessEvent(int vk_event) {
if (vk_rx_sync_time >= VK_SYNC_WAIT_TIME) {
m_togBtnVoiceKeyer->SetValue(false);
m_togBtnVoiceKeyer->SetBackgroundColour(wxNullColour);
updateVoiceKeyerButtonLabel_();
next_state = VK_IDLE;
}
}
@ -378,6 +397,7 @@ void MainFrame::VoiceKeyerProcessEvent(int vk_event) {
togglePTT();
m_togBtnVoiceKeyer->SetValue(false);
m_togBtnVoiceKeyer->SetBackgroundColour(wxNullColour);
updateVoiceKeyerButtonLabel_();
next_state = VK_IDLE;
g_voice_keyer_tx.store(false, std::memory_order_release);
}