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