Ring Daemon
Loading...
Searching...
No Matches
media_filter.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" // MUST BE INCLUDED FIRST
19#include "logger.h"
20#include "media_filter.h"
21#include "media_buffer.h"
22
23extern "C" {
24#include <libavfilter/buffersink.h>
25#include <libavfilter/buffersrc.h>
26}
27
28#include <algorithm>
29#include <functional>
30#include <memory>
31#include <thread>
32
33namespace jami {
34
36
38{
39 clean();
40}
41
42std::string
44{
45 return desc_;
46}
47
48int
49MediaFilter::initialize(const std::string& filterDesc, const std::vector<MediaStream>& msps)
50{
51 int ret = 0;
52 desc_ = filterDesc;
53 graph_ = avfilter_graph_alloc();
54
55 if (!graph_)
56 return fail("Failed to allocate filter graph", AVERROR(ENOMEM));
57
58 graph_->nb_threads = std::max(1, std::min(8, static_cast<int>(std::thread::hardware_concurrency()) / 2));
59
62 if ((ret = avfilter_graph_parse2(graph_, desc_.c_str(), &in, &out)) < 0)
63 return fail("Failed to parse filter graph", ret);
64
65 using AVFilterInOutPtr = std::unique_ptr<AVFilterInOut, std::function<void(AVFilterInOut*)>>;
68
69 if (outputs && outputs->next)
70 return fail("Filters with multiple outputs are not supported", AVERROR(ENOTSUP));
71
72 if ((ret = initOutputFilter(outputs.get())) < 0)
73 return fail("Failed to create output for filter graph", ret);
74
75 // make sure inputs linked list is the same size as msps
76 size_t count = 0;
78 while (dummyInput && ++count) // increment count before evaluating its value
79 dummyInput = dummyInput->next;
80 if (count != msps.size())
81 return fail("Size mismatch between number of inputs in filter graph and input parameter array", AVERROR(EINVAL));
82
83 for (AVFilterInOut* current = inputs.get(); current; current = current->next) {
84 if (!current->name)
85 return fail("Filters require non empty names", AVERROR(EINVAL));
86 std::string_view name = current->name;
87 auto it = std::find_if(msps.begin(), msps.end(), [name](const MediaStream& msp) { return msp.name == name; });
88 if (it != msps.end()) {
89 if ((ret = initInputFilter(current, *it)) < 0) {
90 return fail(fmt::format("Failed to initialize input: {}", name), ret);
91 }
92 } else {
93 return fail(fmt::format("Failed to find matching parameters for: {}", name), ret);
94 }
95 }
96
97 if ((ret = avfilter_graph_config(graph_, nullptr)) < 0)
98 return fail("Failed to configure filter graph", ret);
99
100 JAMI_DBG() << "Filter graph initialized with: " << desc_;
101 initialized_ = true;
102 return 0;
103}
104
105bool
106MediaFilter::needsReinitForNewStream(const std::string& name) const
107{
108 for (const auto& ms : inputParams_)
109 if (ms.name == name)
110 return false;
111 return true;
112}
113
114const MediaStream&
115MediaFilter::getInputParams(const std::string& inputName) const
116{
117 for (const auto& ms : inputParams_)
118 if (ms.name == inputName)
119 return ms;
120 throw std::out_of_range("Input '" + inputName + "' not found");
121}
122
125{
127 if (!output_ || !initialized_) {
128 fail("Filter not initialized", -1);
129 return output;
130 }
131
132 switch (av_buffersink_get_type(output_)) {
134 output.name = "videoOutput";
135 output.format = av_buffersink_get_format(output_);
136 output.isVideo = true;
137 output.timeBase = av_buffersink_get_time_base(output_);
138 output.width = av_buffersink_get_w(output_);
139 output.height = av_buffersink_get_h(output_);
140 output.bitrate = 0;
141 output.frameRate = av_buffersink_get_frame_rate(output_);
142 break;
144 output.name = "audioOutput";
145 output.format = av_buffersink_get_format(output_);
146 output.isVideo = false;
147 output.timeBase = av_buffersink_get_time_base(output_);
148 output.sampleRate = av_buffersink_get_sample_rate(output_);
149 output.nbChannels = av_buffersink_get_channels(output_);
150 break;
151 default:
152 output.format = -1;
153 break;
154 }
155 return output;
156}
157
158int
160{
161 int ret = 0;
162 if (!initialized_)
163 return fail("Filter not initialized", -1);
164
165 if (!frame)
166 return 0;
167
168 for (size_t i = 0; i < inputs_.size(); ++i) {
169 auto& ms = inputParams_[i];
170 if (ms.name != inputName)
171 continue;
172
173 bool formatChanged = ms.format != frame->format;
174 bool videoParamsChanged = ms.isVideo && (ms.width != frame->width || ms.height != frame->height);
175 bool audioParamsChanged = !ms.isVideo
176 && (ms.sampleRate != frame->sample_rate
177 || ms.nbChannels != frame->ch_layout.nb_channels);
178
180 ms.update(frame);
181 if ((ret = reinitialize()) < 0)
182 return fail("Failed to reinitialize filter with new input parameters", ret);
183 }
184
185 int flags = AV_BUFFERSRC_FLAG_KEEP_REF;
186 if ((ret = av_buffersrc_add_frame_flags(inputs_[i], frame, flags)) < 0)
187 return fail("Unable to pass frame to filters", ret);
188 else
189 return 0;
190 }
191 return fail(fmt::format("Specified filter '{}' not found", inputName), AVERROR(EINVAL));
192}
193
194std::unique_ptr<MediaFrame>
196{
197 if (!initialized_) {
198 fail("Not properly initialized", -1);
199 return {};
200 }
201
202 std::unique_ptr<MediaFrame> frame;
203 switch (av_buffersink_get_type(output_)) {
204#ifdef ENABLE_VIDEO
206 frame = std::make_unique<libjami::VideoFrame>();
207 break;
208#endif
210 frame = std::make_unique<AudioFrame>();
211 break;
212 default:
213 return {};
214 }
215 auto err = av_buffersink_get_frame(output_, frame->pointer());
216 if (err >= 0) {
217 return frame;
218 } else if (err == AVERROR(EAGAIN)) {
219 // no data available right now, try again
220 } else if (err == AVERROR_EOF) {
221 JAMI_WARN() << "Filters have reached EOF, no more frames will be output";
222 } else {
223 fail("Error occurred while pulling from filter graph", err);
224 }
225 return {};
226}
227
228void
230{
231 for (size_t i = 0; i < inputs_.size(); ++i) {
232 int ret = av_buffersrc_add_frame_flags(inputs_[i], nullptr, 0);
233 if (ret < 0) {
234 JAMI_ERR() << "Failed to flush filter '" << inputParams_[i].name << "': " << libav_utils::getError(ret);
235 }
236 }
237}
238
239int
240MediaFilter::initOutputFilter(AVFilterInOut* out)
241{
242 int ret = 0;
243 const AVFilter* buffersink;
245 AVMediaType mediaType = avfilter_pad_get_type(out->filter_ctx->input_pads, out->pad_idx);
246
247 if (mediaType == AVMEDIA_TYPE_VIDEO)
248 buffersink = avfilter_get_by_name("buffersink");
249 else
250 buffersink = avfilter_get_by_name("abuffersink");
251
252 if ((ret = avfilter_graph_create_filter(&buffersinkCtx, buffersink, "out", nullptr, nullptr, graph_)) < 0) {
254 return fail("Failed to create buffer sink", ret);
255 }
256
257 if ((ret = avfilter_link(out->filter_ctx, out->pad_idx, buffersinkCtx, 0)) < 0) {
259 return fail("Unable to link buffer sink to graph", ret);
260 }
261
262 output_ = buffersinkCtx;
263 return ret;
264}
265
266int
267MediaFilter::initInputFilter(AVFilterInOut* in, const MediaStream& msp)
268{
269 int ret = 0;
271 if (!params)
272 return -1;
273
274 const AVFilter* buffersrc;
275 AVMediaType mediaType = avfilter_pad_get_type(in->filter_ctx->input_pads, in->pad_idx);
276 params->format = msp.format;
277 params->time_base = msp.timeBase;
278 if (mediaType == AVMEDIA_TYPE_VIDEO) {
279 params->width = msp.width;
280 params->height = msp.height;
281 params->frame_rate = msp.frameRate;
283 } else {
284 params->sample_rate = msp.sampleRate;
285 av_channel_layout_default(&params->ch_layout, msp.nbChannels);
286 buffersrc = avfilter_get_by_name("abuffer");
287 }
288
289 AVFilterContext* buffersrcCtx = nullptr;
290 if (buffersrc) {
291 char name[128];
292 snprintf(name, sizeof(name), "buffersrc_%s_%d", in->name, in->pad_idx);
294 }
295 if (!buffersrcCtx) {
297 return fail("Failed to allocate filter graph input", AVERROR(ENOMEM));
298 }
301 if (ret < 0)
302 return fail("Failed to set filter graph input parameters", ret);
303
304 if ((ret = avfilter_init_str(buffersrcCtx, nullptr)) < 0)
305 return fail("Failed to initialize buffer source", ret);
306
307 if ((ret = avfilter_link(buffersrcCtx, 0, in->filter_ctx, in->pad_idx)) < 0)
308 return fail("Failed to link buffer source to graph", ret);
309
310 inputs_.push_back(buffersrcCtx);
311 inputParams_.emplace_back(msp);
312 inputParams_.back().name = in->name;
313 return ret;
314}
315
316int
317MediaFilter::reinitialize()
318{
319 // keep parameters needed for initialization before clearing filter
320 auto params = std::move(inputParams_);
321 auto desc = std::move(desc_);
322 clean();
323 auto ret = initialize(desc, params);
324 if (ret >= 0)
325 JAMI_DBG() << "Filter graph reinitialized";
326 return ret;
327}
328
329int
330MediaFilter::fail(std::string_view msg, int err) const
331{
332 if (!msg.empty())
333 JAMI_ERR() << msg << ": " << libav_utils::getError(err);
334 return err;
335}
336
337void
338MediaFilter::clean()
339{
340 initialized_ = false;
341 avfilter_graph_free(&graph_); // frees inputs_ and output_
342 desc_.clear();
343 inputs_.clear(); // don't point to freed memory
344 output_ = nullptr; // don't point to freed memory
345 inputParams_.clear();
346}
347
348} // namespace jami
std::unique_ptr< MediaFrame > readOutput()
Pull a frame from the filter graph.
MediaStream getOutputParams() const
Returns a MediaStream struct describing the frames that will be output.
std::string getFilterDesc() const
Returns the current filter graph string.
void flush()
Flush filter to indicate EOF.
int initialize(const std::string &filterDesc, const std::vector< MediaStream > &msps)
Initializes the filter graph with one or more inputs and one output.
bool needsReinitForNewStream(const std::string &name) const
Determines if the media filter needs to be reinitialized for a new stream.
int feedInput(AVFrame *frame, const std::string &inputName)
Give the specified source filter an input frame.
const MediaStream & getInputParams(const std::string &inputName) const
Returns a MediaStream object describing the input specified by @inputName.
#define JAMI_ERR(...)
Definition logger.h:230
#define JAMI_DBG(...)
Definition logger.h:228
#define JAMI_WARN(...)
Definition logger.h:229
std::string getError(int err)
void emitSignal(Args... args)
Definition jami_signal.h:64