Ring Daemon
Loading...
Searching...
No Matches
resampler.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2004-2026 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 <libavutil/frame.h>
21#include <libavutil/mathematics.h>
22#include <libavutil/opt.h>
23#include "resampler.h"
24#include "libav_utils.h"
25
26extern "C" {
27#include <libswresample/swresample.h>
28}
29
30namespace jami {
31
33 : swrCtx_(swr_alloc())
34 , initCount_(0)
35{}
36
38{
39 swr_free(&swrCtx_);
40}
41
42void
43Resampler::reinit(const AVFrame* in, const AVFrame* out)
44{
45 // NOTE swr_set_matrix should be called on an uninitialized context
46 auto* swrCtx = swr_alloc();
47 if (!swrCtx) {
48 JAMI_ERROR("[{}] Unable to allocate resampler context", fmt::ptr(this));
49 throw std::bad_alloc();
50 }
51
52 int ret = av_opt_set_chlayout(swrCtx, "ichl", &in->ch_layout, 0);
53 if (ret < 0) {
55 char layout_buf[64];
57 JAMI_ERROR("[{}] Failed to set input channel layout {}: {}",
58 fmt::ptr(this),
61 throw std::runtime_error("Failed to set input channel layout");
62 }
63 ret = av_opt_set_int(swrCtx, "isr", in->sample_rate, 0);
64 if (ret < 0) {
66 JAMI_ERROR("[{}] Failed to set input sample rate {}: {}",
67 fmt::ptr(this),
68 in->sample_rate,
70 throw std::runtime_error("Failed to set input sample rate");
71 }
72 ret = av_opt_set_sample_fmt(swrCtx, "isf", static_cast<AVSampleFormat>(in->format), 0);
73 if (ret < 0) {
75 JAMI_ERROR("[{}] Failed to set input sample format {}: {}",
76 fmt::ptr(this),
77 av_get_sample_fmt_name(static_cast<AVSampleFormat>(in->format)),
79 throw std::runtime_error("Failed to set input sample format");
80 }
81
82 ret = av_opt_set_chlayout(swrCtx, "ochl", &out->ch_layout, 0);
83 if (ret < 0) {
85 char layout_buf[64];
87 JAMI_ERROR("[{}] Failed to set output channel layout {}: {}",
88 fmt::ptr(this),
91 throw std::runtime_error("Failed to set output channel layout");
92 }
93 ret = av_opt_set_int(swrCtx, "osr", out->sample_rate, 0);
94 if (ret < 0) {
96 JAMI_ERROR("[{}] Failed to set output sample rate {}: {}",
97 fmt::ptr(this),
98 out->sample_rate,
100 throw std::runtime_error("Failed to set output sample rate");
101 }
102 ret = av_opt_set_sample_fmt(swrCtx, "osf", static_cast<AVSampleFormat>(out->format), 0);
103 if (ret < 0) {
105 JAMI_ERROR("[{}] Failed to set output sample format {}: {}",
106 fmt::ptr(this),
107 av_get_sample_fmt_name(static_cast<AVSampleFormat>(out->format)),
109 throw std::runtime_error("Failed to set output sample format");
110 }
111
123 if (in->ch_layout.u.mask == AV_CH_LAYOUT_5POINT1 || in->ch_layout.u.mask == AV_CH_LAYOUT_5POINT1_BACK) {
124 int ret = 0;
125 // NOTE: MSVC is unable to allocate dynamic size arrays on the stack
126 if (out->ch_layout.nb_channels == 2) {
127 double matrix[2][6];
128 // L = 1.0*FL + 0.707*FC + 0.707*BL + 1.0*LFE
129 matrix[0][0] = 1;
130 matrix[0][1] = 0;
131 matrix[0][2] = 0.707;
132 matrix[0][3] = 1;
133 matrix[0][4] = 0.707;
134 matrix[0][5] = 0;
135 // R = 1.0*FR + 0.707*FC + 0.707*BR + 1.0*LFE
136 matrix[1][0] = 0;
137 matrix[1][1] = 1;
138 matrix[1][2] = 0.707;
139 matrix[1][3] = 1;
140 matrix[1][4] = 0;
141 matrix[1][5] = 0.707;
143 } else {
144 double matrix[1][6];
145 // M = 1.0*FL + 1.414*FC + 1.0*FR + 0.707*BL + 0.707*BR + 2.0*LFE
146 matrix[0][0] = 1;
147 matrix[0][1] = 1;
148 matrix[0][2] = 1.414;
149 matrix[0][3] = 2;
150 matrix[0][4] = 0.707;
151 matrix[0][5] = 0.707;
153 }
154 if (ret < 0) {
156 JAMI_ERROR("[{}] Failed to set mixing matrix: {}", fmt::ptr(this), libav_utils::getError(ret));
157 throw std::runtime_error("Failed to set mixing matrix");
158 }
159 }
160
162 if (ret >= 0) {
163 std::swap(swrCtx_, swrCtx);
165 JAMI_DEBUG("[{}] Succesfully (re)initialized resampler context from {} to {}",
166 fmt::ptr(this),
169 ++initCount_;
170 } else {
172 JAMI_ERROR("[{}] Runtime error: Failed to initialize resampler context: {}",
173 fmt::ptr(this),
175 throw std::runtime_error("Failed to initialize resampler context");
176 }
177}
178
179int
181{
182 bool firstFrame = (initCount_ == 0);
183 if (!initCount_)
184 reinit(input, output);
185
186 int ret = swr_convert_frame(swrCtx_, output, input);
188 // Under certain conditions, the resampler reinits itself in an infinite loop. This is
189 // indicative of an underlying problem in the code. This check is so the backtrace
190 // doesn't get mangled with a bunch of calls to Resampler::resample
191 if (initCount_ > 1) {
192 // JAMI_ERROR("Infinite loop detected in audio resampler, please open an issue on https://git.jami.net");
193 JAMI_ERROR("[{}] Loop detected in audio resampler when resampling from {} to {}",
194 fmt::ptr(this),
197 throw std::runtime_error("Infinite loop detected in audio resampler");
198 }
199 reinit(input, output);
200 return resample(input, output);
201 }
202
203 if (ret < 0) {
204 JAMI_ERROR("[{}] Failed to resample frame: {}", fmt::ptr(this), libav_utils::getError(ret));
205 return -1;
206 }
207
208 if (firstFrame) {
209 // we just resampled the first frame
210 auto targetOutputLength = av_rescale_rnd(input->nb_samples,
211 output->sample_rate,
212 input->sample_rate,
214 if (output->nb_samples < targetOutputLength) {
215 // create new frame with more samples, padded with silence
216 JAMI_WARNING("[{}] Adding {} samples of silence at beginning of first frame to reach {} samples",
217 fmt::ptr(this),
218 targetOutputLength - output->nb_samples,
220 auto* newOutput = av_frame_alloc();
221 if (!newOutput) {
222 JAMI_ERROR("[{}] Failed to clone output frame for resizing", fmt::ptr(this));
223 return -1;
224 }
226 newOutput->format = output->format;
227 newOutput->nb_samples = static_cast<int>(targetOutputLength);
228 newOutput->ch_layout = output->ch_layout;
229 newOutput->channel_layout = output->channel_layout;
230 newOutput->sample_rate = output->sample_rate;
232 if (bufferRet < 0) {
233 JAMI_ERROR("[{}] Failed to allocate new output frame buffer: {}",
234 fmt::ptr(this),
237 return -1;
238 }
239 auto sampleOffset = targetOutputLength - output->nb_samples;
241 0,
242 static_cast<int>(sampleOffset),
243 output->ch_layout.nb_channels,
244 static_cast<AVSampleFormat>(output->format));
245 if (bufferRet < 0) {
246 JAMI_ERROR("[{}] Failed to set silence on new output frame: {}",
247 fmt::ptr(this),
250 return -1;
251 }
252 // copy old data to new frame at offset sampleOffset
254 output->data,
255 static_cast<int>(sampleOffset),
256 0,
257 output->nb_samples,
258 output->ch_layout.nb_channels,
259 static_cast<AVSampleFormat>(output->format));
260 if (bufferRet < 0) {
261 JAMI_ERROR("[{}] Failed to copy data to new output frame: {}",
262 fmt::ptr(this),
265 return -1;
266 }
267 JAMI_DEBUG("[{}] Resampled first frame. Resized from {} to {} samples",
268 fmt::ptr(this),
269 output->nb_samples,
270 newOutput->nb_samples);
271 // replace output frame buffer
275 }
276 }
277
278 // Resampling worked, reset count to 1 so reinit isn't called again
279 initCount_ = 1;
280 return 0;
281}
282
283std::unique_ptr<AudioFrame>
284Resampler::resample(std::unique_ptr<AudioFrame>&& in, const AudioFormat& format)
285{
286 if (in->pointer()->sample_rate == (int) format.sample_rate
287 && in->pointer()->ch_layout.nb_channels == (int) format.nb_channels
288 && (AVSampleFormat) in->pointer()->format == format.sampleFormat) {
289 return std::move(in);
290 }
291 auto output = std::make_unique<AudioFrame>(format);
292 resample(in->pointer(), output->pointer());
293 output->has_voice = in->has_voice;
294 return output;
295}
296
297std::shared_ptr<AudioFrame>
298Resampler::resample(std::shared_ptr<AudioFrame>&& in, const AudioFormat& format)
299{
300 if (not in) {
301 return {};
302 }
303 auto* inPtr = in->pointer();
304 if (inPtr == nullptr) {
305 return {};
306 }
307
308 if (inPtr->sample_rate == (int) format.sample_rate && inPtr->ch_layout.nb_channels == (int) format.nb_channels
309 && (AVSampleFormat) inPtr->format == format.sampleFormat) {
310 return std::move(in);
311 }
312
313 auto output = std::make_shared<AudioFrame>(format);
314 if (auto* outPtr = output->pointer()) {
316 output->has_voice = in->has_voice;
317 return output;
318 }
319 return {};
320}
321
322} // namespace jami
int resample(const AVFrame *input, AVFrame *output)
Resample a frame.
#define JAMI_ERROR(formatstr,...)
Definition logger.h:243
#define JAMI_DEBUG(formatstr,...)
Definition logger.h:238
#define JAMI_WARNING(formatstr,...)
Definition logger.h:242
AudioFormat getFormat(const AVFrame *frame)
std::string getError(int err)
static constexpr std::string_view toString(AuthDecodingState state)
void emitSignal(Args... args)
Definition jami_signal.h:64
Structure to hold sample rate and channel number associated with audio data.
AVSampleFormat sampleFormat
void av_frame_free(AVFrame **frame)