Signed-off-by: Michael Niedermayer <mich...@niedermayer.cc> --- 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: "); -- 2.31.1 _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org https://ffmpeg.org/mailman/listinfo/ffmpeg-devel To unsubscribe, visit link above, or email ffmpeg-devel-requ...@ffmpeg.org with subject "unsubscribe".