LCOV - code coverage report
Current view: top level - src/media/audio - resampler.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 7 92 7.6 %
Date: 2024-12-21 08:56:24 Functions: 2 10 20.0 %

          Line data    Source code
       1             : /*
       2             :  *  Copyright (C) 2004-2024 Savoir-faire Linux Inc.
       3             :  *
       4             :  *  This program is free software: you can redistribute it and/or modify
       5             :  *  it under the terms of the GNU General Public License as published by
       6             :  *  the Free Software Foundation, either version 3 of the License, or
       7             :  *  (at your option) any later version.
       8             :  *
       9             :  *  This program is distributed in the hope that it will be useful,
      10             :  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
      12             :  *  GNU General Public License for more details.
      13             :  *
      14             :  *  You should have received a copy of the GNU General Public License
      15             :  *  along with this program. If not, see <https://www.gnu.org/licenses/>.
      16             :  */
      17             : 
      18             : #include "libav_deps.h"
      19             : #include "logger.h"
      20             : #include "resampler.h"
      21             : 
      22             : extern "C" {
      23             : #include <libswresample/swresample.h>
      24             : }
      25             : 
      26             : namespace jami {
      27             : 
      28         863 : Resampler::Resampler()
      29         863 :     : swrCtx_(swr_alloc())
      30         863 :     , initCount_(0)
      31         863 : {}
      32             : 
      33         863 : Resampler::~Resampler()
      34             : {
      35         863 :     swr_free(&swrCtx_);
      36         863 : }
      37             : 
      38             : void
      39           0 : Resampler::reinit(const AVFrame* in, const AVFrame* out)
      40             : {
      41             :     // NOTE swr_set_matrix should be called on an uninitialized context
      42           0 :     auto swrCtx = swr_alloc();
      43           0 :     if (!swrCtx) {
      44           0 :         JAMI_ERR() << "Unable to allocate resampler context";
      45           0 :         throw std::bad_alloc();
      46             :     }
      47             : 
      48           0 :     av_opt_set_chlayout(swrCtx, "ichl", &in->ch_layout, 0);
      49           0 :     av_opt_set_int(swrCtx, "isr", in->sample_rate, 0);
      50           0 :     av_opt_set_sample_fmt(swrCtx, "isf", static_cast<AVSampleFormat>(in->format), 0);
      51             : 
      52           0 :     av_opt_set_chlayout(swrCtx, "ochl", &out->ch_layout, 0);
      53           0 :     av_opt_set_int(swrCtx, "osr", out->sample_rate, 0);
      54           0 :     av_opt_set_sample_fmt(swrCtx, "osf", static_cast<AVSampleFormat>(out->format), 0);
      55             : 
      56             :     /**
      57             :      * Downmixing from 5.1 requires extra setup, since libswresample is unable to do it
      58             :      * automatically (not yet implemented).
      59             :      *
      60             :      * Source: https://www.atsc.org/wp-content/uploads/2015/03/A52-201212-17.pdf
      61             :      * Section 7.8.2 for the algorithm
      62             :      * Tables 5.9 and 5.10 for the coefficients clev and slev
      63             :      *
      64             :      * LFE downmixing is optional, so any coefficient can be used, we use +6dB for mono and
      65             :      * +0dB in each channel for stereo.
      66             :      */
      67           0 :     if (in->ch_layout.u.mask == AV_CH_LAYOUT_5POINT1
      68           0 :         || in->ch_layout.u.mask == AV_CH_LAYOUT_5POINT1_BACK) {
      69             :         // NOTE: MSVC is unable to allocate dynamic size arrays on the stack
      70           0 :         if (out->ch_layout.nb_channels == 2) {
      71             :             double matrix[2][6];
      72             :             // L = 1.0*FL + 0.707*FC + 0.707*BL + 1.0*LFE
      73           0 :             matrix[0][0] = 1;
      74           0 :             matrix[0][1] = 0;
      75           0 :             matrix[0][2] = 0.707;
      76           0 :             matrix[0][3] = 1;
      77           0 :             matrix[0][4] = 0.707;
      78           0 :             matrix[0][5] = 0;
      79             :             // R = 1.0*FR + 0.707*FC + 0.707*BR + 1.0*LFE
      80           0 :             matrix[1][0] = 0;
      81           0 :             matrix[1][1] = 1;
      82           0 :             matrix[1][2] = 0.707;
      83           0 :             matrix[1][3] = 1;
      84           0 :             matrix[1][4] = 0;
      85           0 :             matrix[1][5] = 0.707;
      86           0 :             swr_set_matrix(swrCtx, matrix[0], 6);
      87             :         } else {
      88             :             double matrix[1][6];
      89             :             // M = 1.0*FL + 1.414*FC + 1.0*FR + 0.707*BL + 0.707*BR + 2.0*LFE
      90           0 :             matrix[0][0] = 1;
      91           0 :             matrix[0][1] = 1;
      92           0 :             matrix[0][2] = 1.414;
      93           0 :             matrix[0][3] = 2;
      94           0 :             matrix[0][4] = 0.707;
      95           0 :             matrix[0][5] = 0.707;
      96           0 :             swr_set_matrix(swrCtx, matrix[0], 6);
      97             :         }
      98             :     }
      99             : 
     100           0 :     if (swr_init(swrCtx) >= 0) {
     101           0 :         std::swap(swrCtx_, swrCtx);
     102           0 :         swr_free(&swrCtx);
     103           0 :         ++initCount_;
     104             :     } else {
     105           0 :         std::string msg = "Failed to initialize resampler context";
     106           0 :         JAMI_ERR() << msg;
     107           0 :         throw std::runtime_error(msg);
     108           0 :     }
     109           0 : }
     110             : 
     111             : int
     112           0 : Resampler::resample(const AVFrame* input, AVFrame* output)
     113             : {
     114           0 :     if (!initCount_)
     115           0 :         reinit(input, output);
     116             : 
     117           0 :     int ret = swr_convert_frame(swrCtx_, output, input);
     118           0 :     if (ret & AVERROR_INPUT_CHANGED || ret & AVERROR_OUTPUT_CHANGED) {
     119             :         // Under certain conditions, the resampler reinits itself in an infinite loop. This is
     120             :         // indicative of an underlying problem in the code. This check is so the backtrace
     121             :         // doesn't get mangled with a bunch of calls to Resampler::resample
     122           0 :         if (initCount_ > 1) {
     123           0 :             JAMI_ERROR("Infinite loop detected in audio resampler, please open an issue on https://git.jami.net");
     124           0 :             throw std::runtime_error("Resampler");
     125             :         }
     126           0 :         reinit(input, output);
     127           0 :         return resample(input, output);
     128           0 :     } else if (ret < 0) {
     129           0 :         JAMI_ERROR("Failed to resample frame");
     130           0 :         return -1;
     131             :     }
     132             : 
     133             :     // Resampling worked, reset count to 1 so reinit isn't called again
     134           0 :     initCount_ = 1;
     135           0 :     return 0;
     136             : }
     137             : 
     138             : std::unique_ptr<AudioFrame>
     139           0 : Resampler::resample(std::unique_ptr<AudioFrame>&& in, const AudioFormat& format)
     140             : {
     141           0 :     if (in->pointer()->sample_rate == (int) format.sample_rate
     142           0 :         && in->pointer()->ch_layout.nb_channels == (int) format.nb_channels
     143           0 :         && (AVSampleFormat) in->pointer()->format == format.sampleFormat) {
     144           0 :         return std::move(in);
     145             :     }
     146           0 :     auto output = std::make_unique<AudioFrame>(format);
     147           0 :     resample(in->pointer(), output->pointer());
     148           0 :     output->has_voice = in->has_voice;
     149           0 :     return output;
     150           0 : }
     151             : 
     152             : std::shared_ptr<AudioFrame>
     153           0 : Resampler::resample(std::shared_ptr<AudioFrame>&& in, const AudioFormat& format)
     154             : {
     155           0 :     if (not in) {
     156           0 :         return {};
     157             :     }
     158           0 :     auto inPtr = in->pointer();
     159           0 :     if (inPtr == nullptr) {
     160           0 :         return {};
     161             :     }
     162             : 
     163           0 :     if (inPtr->sample_rate == (int) format.sample_rate
     164           0 :         && inPtr->ch_layout.nb_channels == (int) format.nb_channels
     165           0 :         && (AVSampleFormat) inPtr->format == format.sampleFormat) {
     166           0 :         return std::move(in);
     167             :     }
     168             : 
     169           0 :     auto output = std::make_shared<AudioFrame>(format);
     170           0 :     if (auto outPtr = output->pointer()) {
     171           0 :         resample(inPtr, outPtr);
     172           0 :         output->has_voice = in->has_voice;
     173           0 :         return output;
     174             :     }
     175           0 :     return {};
     176           0 : }
     177             : 
     178             : } // namespace jami

Generated by: LCOV version 1.14