From patchwork Mon Jul 17 00:27:03 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Michael Niedermayer X-Patchwork-Id: 42778 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a05:6a20:6da1:b0:131:a7d0:bc6d with SMTP id gl33csp5078686pzb; Sun, 16 Jul 2023 17:28:02 -0700 (PDT) X-Google-Smtp-Source: APBJJlGbPPMuDaJ7TEXNcHY2Kk3/xADUQoe4MRQROfcd8CkIMsAxpA53hSbXiqjl7SW7yA0kGU8n X-Received: by 2002:a05:6512:3a85:b0:4f7:4098:9905 with SMTP id q5-20020a0565123a8500b004f740989905mr7638363lfu.65.1689553681854; Sun, 16 Jul 2023 17:28:01 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1689553681; cv=none; d=google.com; s=arc-20160816; b=HHQZ2CKDFKq7sia2QQzGgWj/qoeDZI7qGD6ORs//oemCEasYYqeaj1wGrBfSAc/AgU EdYDhH5UeQzFI/Oc/RsPoa/Hej9TeRIEtI2F2q88OW5JV8psEDQh5UIIRgoeSwiZV75D Sg2Eqh5wr67qRIxvl6xyyrzFFA5rK9KtrEEOHlL/bHjGwn5zyhP5U95inpHvG8bx3zZc R/ditGasay8Yty6PUMMsfisXC7UmQ3oKTg+fDJ6zf/jpmCfaqfQcgQPElXH0VxL+8jx0 UrJXajdV1D2QYpe+AcbG3QtB1ZJ2mlK6FMyTMRE1DZ6BkaBS8p7/nPByqBgXfzU/SD05 sLpA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=sender:errors-to:content-transfer-encoding:reply-to:list-subscribe :list-help:list-post:list-archive:list-unsubscribe:list-id :precedence:subject:mime-version:references:in-reply-to:message-id :date:to:from:delivered-to; bh=oOZX40WrYLJmpCh94ktvxqswOOoE4nmaTiGWTk6gWsw=; fh=YYwLYmpaV0Fpw/rxmSKNRLS2XzDkAlGbHATiKOPtZrY=; b=sjpYu3iwg5mew2gdoyVrLXw1hFbiLmkclC94zehhb9wsYdvEd9VkS+dS7JekXDD9SI 5u2tLYRnczQbkZ0IYuVOX+10NPKml62UvLMzqFhpt1paSy8MwsSC/NTlRMB1kv+RSF9i B5JPAy3Z0GGyAtdMnI98zH0dZLlIX6sVjtuLXMq5+LetrmpP6iBBCzMaNlM+nQQAbjsx 1e6VHFPvhE1V79cK2qD1w586xN6FKzynpGeWVRLZC+hOmDbwrFEGN70WigddLXTmvr+g 3hbo3CmYk8l5KzX7cmyx2WnDqK7x6em2F+xVLZqYB3pvKQS9VGalSRDGtQ/rBrA714Hu fOmw== ARC-Authentication-Results: i=1; mx.google.com; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id e25-20020a056402089900b00521830574c0si2091464edy.204.2023.07.16.17.28.01; Sun, 16 Jul 2023 17:28:01 -0700 (PDT) Received-SPF: pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) client-ip=79.124.17.100; Authentication-Results: mx.google.com; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 6B7FC68C69A; Mon, 17 Jul 2023 03:27:21 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from relay6-d.mail.gandi.net (relay6-d.mail.gandi.net [217.70.183.198]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 94A1F68C584 for ; Mon, 17 Jul 2023 03:27:11 +0300 (EEST) Received: by mail.gandi.net (Postfix) with ESMTPSA id 0164CC0003 for ; Mon, 17 Jul 2023 00:27:10 +0000 (UTC) From: Michael Niedermayer To: FFmpeg development discussions and patches Date: Mon, 17 Jul 2023 02:27:03 +0200 Message-Id: <20230717002704.3092192-6-michael@niedermayer.cc> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20230717002704.3092192-1-michael@niedermayer.cc> References: <20230717002704.3092192-1-michael@niedermayer.cc> MIME-Version: 1.0 X-GND-Sasl: michael@niedermayer.cc Subject: [FFmpeg-devel] [PATCH 6/7] avradio/sdr: Support setting gain value manually and automatic X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" X-TUID: DgM3qAss7eur Signed-off-by: Michael Niedermayer --- libavradio/sdr.h | 23 +++++++++++++++++- libavradio/sdrdemux.c | 53 ++++++++++++++++++++++++++++++++++++++++- libavradio/sdrinradio.c | 41 +++++++++++++++++++++++++++++-- 3 files changed, 113 insertions(+), 4 deletions(-) diff --git a/libavradio/sdr.h b/libavradio/sdr.h index dc20415457..395b056531 100644 --- a/libavradio/sdr.h +++ b/libavradio/sdr.h @@ -68,6 +68,12 @@ typedef enum Modulation { //QAM, PSK, ... } Modulation; +typedef enum SDR_GAIN { + GAIN_DEFAULT = -3, + GAIN_SW_AGC = -2, + GAIN_SDR_AGC = -1, +} SDR_GAIN; + #define HISTOGRAMM_SIZE 9 typedef struct Station { @@ -105,6 +111,7 @@ typedef struct Station { typedef struct FIFOElement { int64_t center_frequency; + float gain; void *halfblock; } FIFOElement; @@ -146,7 +153,15 @@ typedef struct SDRContext { int64_t min_center_freq; int64_t max_center_freq; int sdr_sample_rate; - int sdr_agc; + float min_gain; + float max_gain; + int sdr_gain; + float agc_min_headroom; + float agc_max_headroom; + float agc_max_headroom_time; + int agc_low_time; + float agc_gain; ///< current gain, should be accessed only by buffer thread after init + atomic_int wanted_gain; int sdr_adcc; int64_t bandwidth; int64_t last_pts; @@ -212,6 +227,12 @@ typedef struct SDRContext { */ int64_t (*set_frequency_callback)(struct SDRContext *sdr, int64_t frequency); + /** + * Setup the hardware for the requested gain + * This must only be called from the buffer thread after setup (or more mutex calls are needed) + */ + int (*set_gain_callback)(struct SDRContext *sdr, float gain); + /** * Read from the hardware, block if nothing available with a reasonable timeout * diff --git a/libavradio/sdrdemux.c b/libavradio/sdrdemux.c index a0b80785ef..5214aea7be 100644 --- a/libavradio/sdrdemux.c +++ b/libavradio/sdrdemux.c @@ -1409,6 +1409,7 @@ static void *soapy_needs_bigger_buffers_worker(SDRContext *sdr) FIFOElement fifo_element; int remaining, ret; int empty_blocks, full_blocks; + float wanted_gain = atomic_load(&sdr->wanted_gain) / 65536.0; //i wish av_fifo was thread safe pthread_mutex_lock(&sdr->mutex); @@ -1444,9 +1445,17 @@ static void *soapy_needs_bigger_buffers_worker(SDRContext *sdr) //And theres not much else we can do, an error message was already printed by ff_sdr_set_freq() in that case block_counter = 0; // we just changed the frequency, do not trust the next blocks content } + if (sdr->sdr_gain == GAIN_SW_AGC && + fabs(wanted_gain - sdr->agc_gain) > 0.001 && + sdr->set_gain_callback + ) { + sdr->set_gain_callback(sdr, wanted_gain); + sdr->agc_gain = wanted_gain; + } pthread_mutex_unlock(&sdr->mutex); fifo_element.center_frequency = block_counter > 0 ? sdr->freq : 0; + fifo_element.gain = sdr->agc_gain; //we make only small changes so slightly mixing should be ok remaining = sdr->block_size; while (remaining && !atomic_load(&sdr->close_requested)) { @@ -1624,6 +1633,7 @@ int ff_sdr_common_init(AVFormatContext *s) av_fifo_auto_grow_limit(sdr-> full_block_fifo, sdr->sdr_sample_rate / sdr->block_size); atomic_init(&sdr->close_requested, 0); + atomic_init(&sdr->wanted_gain, lrint((sdr->min_gain + sdr->max_gain) * 65536 / 2)); ret = pthread_mutex_init(&sdr->mutex, NULL); if (ret) { av_log(s, AV_LOG_ERROR, "pthread_mutex_init failed: %s\n", strerror(ret)); @@ -1886,6 +1896,37 @@ process_next_block: } } + float smaller_block_gain = FFMIN(fifo_element[0].gain, fifo_element[1].gain); + float bigger_block_gain = FFMAX(fifo_element[0].gain, fifo_element[1].gain); + + if (sdr->sdr_gain == GAIN_SW_AGC) { + float inmax = 0; + float wanted_gain = atomic_load(&sdr->wanted_gain) / 65536.0; + // We only check 25% of the data to safe computations + int start = 3*sdr->block_size / 4; + int end = 5*sdr->block_size / 4; + for (i = start; i < end; i++) { + float v = fmaxf(fabsf(sdr->windowed_block[i].re), fabsf(sdr->windowed_block[i].im)); + inmax = fmaxf(inmax, v); + } + + if (inmax > 1.0 - sdr->agc_min_headroom && wanted_gain > sdr->min_gain) { + //according to docs this is a dB scale, in reality it beheaves differnt to that + //Because of this we will try to just make small changes and not assume too much + wanted_gain = FFMIN(wanted_gain, FFMAX(smaller_block_gain - 1.0, smaller_block_gain * 0.9)); + + sdr->agc_low_time = 0; + } else if (inmax < 1.0 - sdr->agc_max_headroom && wanted_gain < sdr->max_gain) { + sdr->agc_low_time += sdr->block_size; + if (sdr->agc_low_time > sdr->agc_max_headroom_time * sdr->sdr_sample_rate) { + sdr->agc_low_time = 0; + wanted_gain = FFMAX(wanted_gain, FFMIN(bigger_block_gain + 1.0, bigger_block_gain * 1.1)); + } + } else + sdr->agc_low_time = 0; + atomic_store(&sdr->wanted_gain, (int)lrint(wanted_gain * 65536)); + } + inject_block_into_fifo(sdr, sdr->empty_block_fifo, &fifo_element[0], "Cannot pass next buffer, freeing it\n"); #ifdef SYN_TEST //synthetic test signal static int64_t synp=0; @@ -2141,7 +2182,17 @@ const AVOption ff_sdr_options[] = { { "rtlsdr_fixes" , "workaround rtlsdr issues", OFFSET(rtlsdr_fixes), AV_OPT_TYPE_INT , {.i64 = -1}, -1, 1, DEC}, { "sdr_sr" , "sdr sample rate" , OFFSET(sdr_sample_rate ), AV_OPT_TYPE_INT , {.i64 = 0}, 0, INT_MAX, DEC}, { "sdr_freq", "sdr frequency" , OFFSET(wanted_freq), AV_OPT_TYPE_INT64 , {.i64 = 9000000}, 0, INT64_MAX, DEC}, - { "sdr_agc" , "sdr automatic gain control", OFFSET(sdr_agc), AV_OPT_TYPE_BOOL , {.i64 = 1}, -1, 1, DEC}, + { "gain" , "sdr overall gain", OFFSET(sdr_gain), AV_OPT_TYPE_INT , {.i64 = GAIN_SDR_AGC}, -3, INT_MAX, DEC, "gain"}, + { "sdr_agc", "SDR AGC (if supported)", 0, AV_OPT_TYPE_CONST, {.i64 = GAIN_SDR_AGC}, 0, 0, DEC, "gain"}, + { "sw_agc", "Software AGC", 0, AV_OPT_TYPE_CONST, {.i64 = GAIN_SW_AGC}, 0, 0, DEC, "gain"}, + { "default_gain", "Never touch gain", 0, AV_OPT_TYPE_CONST, {.i64 = GAIN_DEFAULT}, 0, 0, DEC, "gain"}, + + { "agc_min_headroom", "AGC min headroom", OFFSET(agc_min_headroom), AV_OPT_TYPE_FLOAT, {.dbl = 0.4}, 0, 1.0, DEC}, + { "agc_max_headroom", "AGC max headroom", OFFSET(agc_max_headroom), AV_OPT_TYPE_FLOAT, {.dbl = 0.8}, 0, 1.0, DEC}, + { "agc_max_headroom_time", "AGC max headroom time", OFFSET(agc_max_headroom_time), AV_OPT_TYPE_FLOAT, {.dbl = 0.1}, 0, INT_MAX, DEC}, + { "min_gain", "minimum gain", OFFSET(min_gain ), AV_OPT_TYPE_FLOAT , {.dbl = 0}, 0, INT_MAX, DEC}, + { "max_gain", "maximum gain", OFFSET(max_gain ), AV_OPT_TYPE_FLOAT , {.dbl = 0}, 0, INT_MAX, DEC}, + { "sdr_adcc" ,"sdr automatic dc correction", OFFSET(sdr_adcc), AV_OPT_TYPE_BOOL , {.i64 = -1}, -1, 1, DEC}, { "min_freq", "minimum frequency", OFFSET(min_freq ), AV_OPT_TYPE_INT64 , {.i64 = 0}, 0, INT64_MAX, DEC}, { "max_freq", "maximum frequency", OFFSET(max_freq ), AV_OPT_TYPE_INT64 , {.i64 = 0}, 0, INT64_MAX, DEC}, diff --git a/libavradio/sdrinradio.c b/libavradio/sdrinradio.c index 3956c18375..63a9cade78 100644 --- a/libavradio/sdrinradio.c +++ b/libavradio/sdrinradio.c @@ -68,6 +68,30 @@ static int sdrindev_read_callback(SDRContext *sdr, FIFOElement *fifo_element, in return ret; } +static int sdrindev_set_gain_callback(SDRContext *sdr, float gain) +{ + AVFormatContext *avfmt = sdr->avfmt; + SoapySDRDevice *soapy = sdr->soapy; + + if (sdr->sdr_gain == GAIN_DEFAULT) + return 0; + + if (soapy) { + int ret = SoapySDRDevice_setGainMode(soapy, SOAPY_SDR_RX, 0, sdr->sdr_gain == GAIN_SDR_AGC); + if (ret) { + av_log(avfmt, AV_LOG_WARNING, "Failed to set gain mode %d (%s)\n", sdr->sdr_gain == GAIN_SDR_AGC, SoapySDRDevice_lastError()); + } + + if (sdr->sdr_gain != GAIN_SDR_AGC) { + ret = SoapySDRDevice_setGain(soapy, SOAPY_SDR_RX, 0, gain); + if (ret) { + av_log(avfmt, AV_LOG_WARNING, "Failed to set gain to %f (%s)\n", gain, SoapySDRDevice_lastError()); + } + } + } + return 0; +} + static int64_t sdrindev_set_frequency_callback(SDRContext *sdr, int64_t freq) { AVFormatContext *avfmt = sdr->avfmt; @@ -135,6 +159,7 @@ static int sdrindev_initial_hw_setup(AVFormatContext *s) sdr->read_callback = sdrindev_read_callback; sdr->set_frequency_callback = sdrindev_set_frequency_callback; + sdr->set_gain_callback = sdrindev_set_gain_callback; // Go over all available soapy devices // Print the usable ones, and choose one unless the user has choosen one @@ -195,8 +220,10 @@ static int sdrindev_initial_hw_setup(AVFormatContext *s) //Inform the user if AGC is supported and setup AGC as requested by the user has_agc = SoapySDRDevice_hasGainMode(soapy, SOAPY_SDR_RX, 0); av_log(s, AV_LOG_INFO, "RX AGC Supported: %s\n", has_agc ? "yes" : "no"); - if (has_agc && sdr->sdr_agc >= 0) - SoapySDRDevice_setGainMode(soapy, SOAPY_SDR_RX, 0, sdr->sdr_agc); + if (!has_agc && sdr->sdr_gain == GAIN_SDR_AGC) { + av_log(s, AV_LOG_WARNING, "hardware AGC unsupported switching to software AGC\n"); + sdr->sdr_gain = GAIN_SW_AGC; + } //Inform the user if automatic DC correction is supported and setup DC correction as requested by the user has_adcc = SoapySDRDevice_hasDCOffsetMode(soapy, SOAPY_SDR_RX, 0); @@ -208,6 +235,16 @@ static int sdrindev_initial_hw_setup(AVFormatContext *s) range = SoapySDRDevice_getGainRange(soapy, SOAPY_SDR_RX, 0); av_log(s, AV_LOG_INFO, "Rx Gain range: %f dB - %f dB\n", range.minimum, range.maximum); + if (!sdr->min_gain) + sdr->min_gain = range.minimum; + + if (!sdr->max_gain) + sdr->max_gain = range.maximum; + if (sdr->min_gain > sdr->max_gain) { + av_log(s, AV_LOG_ERROR, "Invalid gain range\n"); + return AVERROR(EINVAL); + } + //Inform the user about the Frequency ranges available, verify the range requested by the user and set the range if the user has not specified one ranges = SoapySDRDevice_getFrequencyRange(soapy, SOAPY_SDR_RX, 0, &length); av_log(s, AV_LOG_INFO, "Rx freq ranges: ");