blob: 4a06914687894bac105e3af8e159058440c139c6 [file] [log] [blame]
agsantosc9181b42020-11-26 12:03:04 -05001/*
Sébastien Blincb783e32021-02-12 11:34:10 -05002 * Copyright (C) 2020-2021 Savoir-faire Linux Inc.
agsantosc9181b42020-11-26 12:03:04 -05003 *
4 * Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 */
20
21#include "frameFilter.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
34#include "pluglog.h"
35
36namespace jami {
37
38FrameFilter::FrameFilter() {}
39
40FrameFilter::~FrameFilter()
41{
42 clean();
43}
44
45int
46FrameFilter::initialize(const std::string& filterDesc, std::vector<MediaStream> msps)
47{
48 int ret = 0;
49 desc_ = filterDesc;
50 graph_ = avfilter_graph_alloc();
51
52 if (!graph_)
53 return fail("Failed to allocate filter graph", AVERROR(ENOMEM));
54
55 graph_->nb_threads = std::max(1u, std::min(8u, std::thread::hardware_concurrency() / 2));
56
57 AVFilterInOut* in;
58 AVFilterInOut* out;
59 if ((ret = avfilter_graph_parse2(graph_, desc_.c_str(), &in, &out)) < 0)
60 return fail("Failed to parse filter graph", ret);
61
62 using AVFilterInOutPtr = std::unique_ptr<AVFilterInOut, std::function<void(AVFilterInOut*)>>;
63 AVFilterInOutPtr outputs(out, [](AVFilterInOut* f) { avfilter_inout_free(&f); });
64 AVFilterInOutPtr inputs(in, [](AVFilterInOut* f) { avfilter_inout_free(&f); });
65
66 if (outputs && outputs->next)
67 return fail("Filters with multiple outputs are not supported", AVERROR(ENOTSUP));
68
69 if ((ret = initOutputFilter(outputs.get())) < 0)
70 return fail("Failed to create output for filter graph", ret);
71 // make sure inputs linked list is the same size as msps
72 size_t count = 0;
73 AVFilterInOut* dummyInput = inputs.get();
74 while (dummyInput && ++count) // increment count before evaluating its value
75 dummyInput = dummyInput->next;
76 if (count != msps.size())
77 return fail(
78 "Size mismatch between number of inputs in filter graph and input parameter array",
79 AVERROR(EINVAL));
80 for (AVFilterInOut* current = inputs.get(); current; current = current->next) {
81 if (!current->name)
82 return fail("Filters require non empty names", AVERROR(EINVAL));
83 std::string name = current->name;
84 const auto& it = std::find_if(msps.begin(), msps.end(), [name](const MediaStream& msp) {
85 return msp.name == name;
86 });
87 if (it != msps.end()) {
88 if ((ret = initInputFilter(current, *it)) < 0) {
89 std::string msg = "Failed to initialize input: " + name;
90 return fail(msg, ret);
91 }
92 } else {
93 std::string msg = "Failed to find matching parameters for: " + name;
94 return fail(msg, ret);
95 }
96 }
97
98 if ((ret = avfilter_graph_config(graph_, nullptr)) < 0)
99 return fail("Failed to configure filter graph", ret);
100
101 initialized_ = true;
102 return 0;
103}
104
105MediaStream
106FrameFilter::getInputParams(const std::string& inputName) const
107{
108 for (auto ms : inputParams_)
109 if (ms.name == inputName)
110 return ms;
111 return {};
112}
113
114MediaStream
115FrameFilter::getOutputParams() const
116{
117 MediaStream output;
118 if (!output_ || !initialized_) {
119 fail("Filter not initialized", -1);
120 return output;
121 }
122
123 switch (av_buffersink_get_type(output_)) {
124 case AVMEDIA_TYPE_VIDEO:
125 output.name = "videoOutput";
126 output.format = av_buffersink_get_format(output_);
127 output.isVideo = true;
128 output.timeBase = av_buffersink_get_time_base(output_);
129 output.width = av_buffersink_get_w(output_);
130 output.height = av_buffersink_get_h(output_);
131 output.bitrate = 0;
132 output.frameRate = av_buffersink_get_frame_rate(output_);
133 break;
134 case AVMEDIA_TYPE_AUDIO:
135 output.name = "audioOutput";
136 output.format = av_buffersink_get_format(output_);
137 output.isVideo = false;
138 output.timeBase = av_buffersink_get_time_base(output_);
139 output.sampleRate = av_buffersink_get_sample_rate(output_);
140 output.nbChannels = av_buffersink_get_channels(output_);
141 break;
142 default:
143 output.format = -1;
144 break;
145 }
146 return output;
147}
148
149int
150FrameFilter::feedEOF(const std::string& inputName)
151{
152 int ret = 0;
153 if (!initialized_)
154 return fail("Filter not initialized", -1);
155
156 for (size_t i = 0; i < inputs_.size(); ++i) {
157 auto& ms = inputParams_[i];
158 if (ms.name != inputName)
159 continue;
160
161 if ((ret = av_buffersrc_add_frame_flags(inputs_[i], nullptr, AV_BUFFERSRC_FLAG_PUSH)) < 0)
162 return fail("Could not pass frame to filters", ret);
163 else
164 return 0;
165 }
166
167 std::stringstream ss;
168 ss << "Specified filter (" << inputName << ") not found";
169 return fail(ss.str(), AVERROR(EINVAL));
170}
171
172int
173FrameFilter::feedInput(AVFrame* frame, const std::string& inputName)
174{
175 int ret = 0;
176 if (!initialized_)
177 return fail("Filter not initialized", -1);
178
179 if (!frame)
180 return 0;
181
182 for (size_t i = 0; i < inputs_.size(); ++i) {
183 auto& ms = inputParams_[i];
184 if (ms.name != inputName)
185 continue;
186
187 if (ms.format != frame->format
188 || (ms.isVideo && (ms.width != frame->width || ms.height != frame->height))
189 || (!ms.isVideo
Aline Gondim Santosd251ea62023-03-02 09:40:56 -0300190 && (ms.sampleRate != frame->sample_rate || ms.nbChannels != frame->ch_layout.nb_channels))) {
agsantosc9181b42020-11-26 12:03:04 -0500191 ms.update(frame);
192 if ((ret = reinitialize()) < 0)
193 return fail("Failed to reinitialize filter with new input parameters", ret);
194 }
195
196 if ((ret = av_buffersrc_add_frame_flags(inputs_[i], frame, AV_BUFFERSRC_FLAG_KEEP_REF)) < 0)
197 return fail("Could not pass frame to filters", ret);
198 else
199 return 0;
200 }
201
202 std::stringstream ss;
203 ss << "Specified filter (" << inputName << ") not found";
204 return fail(ss.str(), AVERROR(EINVAL));
205}
206
207AVFrame*
208FrameFilter::readOutput()
209{
210 if (!initialized_) {
211 fail("Not properly initialized", -1);
212 return nullptr;
213 }
214
215 AVFrame* frame = av_frame_alloc();
216
217 auto type = av_buffersink_get_type(output_);
218 if (type != AVMEDIA_TYPE_VIDEO && type != AVMEDIA_TYPE_AUDIO) {
Aline Gondim Santos9bd153e2022-08-25 10:49:23 -0300219 av_frame_unref(frame);
220 av_frame_free(&frame);
agsantosc9181b42020-11-26 12:03:04 -0500221 return nullptr;
222 }
223 auto err = av_buffersink_get_frame(output_, frame);
224 if (err >= 0) {
225 return frame;
226 } else if (err == AVERROR(EAGAIN)) {
227 fail("no data. ", err);
228 } else if (err == AVERROR_EOF) {
229 fail("eof. ", err);
230 } else {
231 fail("Error occurred while pulling from filter graph", err);
232 }
Aline Gondim Santos9bd153e2022-08-25 10:49:23 -0300233 av_frame_unref(frame);
234 av_frame_free(&frame);
agsantosc9181b42020-11-26 12:03:04 -0500235 return nullptr;
236}
237
238int
239FrameFilter::initOutputFilter(AVFilterInOut* out)
240{
241 int ret = 0;
242 const AVFilter* buffersink;
agsantosb3c90842020-12-10 14:19:41 -0500243 AVFilterContext* buffersinkCtx{};
agsantosc9181b42020-11-26 12:03:04 -0500244 AVMediaType mediaType = avfilter_pad_get_type(out->filter_ctx->input_pads, out->pad_idx);
245
246 if (mediaType == AVMEDIA_TYPE_VIDEO)
247 buffersink = avfilter_get_by_name("buffersink");
248 else
249 buffersink = avfilter_get_by_name("abuffersink");
250
251 if ((ret
252 = avfilter_graph_create_filter(&buffersinkCtx, buffersink, "out", nullptr, nullptr, graph_))
253 < 0) {
254 avfilter_free(buffersinkCtx);
255 return fail("Failed to create buffer sink", ret);
256 }
257
258 if ((ret = avfilter_link(out->filter_ctx, out->pad_idx, buffersinkCtx, 0)) < 0) {
259 avfilter_free(buffersinkCtx);
260 return fail("Could not link buffer sink to graph", ret);
261 }
262
263 output_ = buffersinkCtx;
264 return ret;
265}
266
267int
268FrameFilter::initInputFilter(AVFilterInOut* in, MediaStream msp)
269{
270 int ret = 0;
271 AVBufferSrcParameters* params = av_buffersrc_parameters_alloc();
272 if (!params)
273 return -1;
274
275 const AVFilter* buffersrc;
276 AVMediaType mediaType = avfilter_pad_get_type(in->filter_ctx->input_pads, in->pad_idx);
277 params->format = msp.format;
278 params->time_base = msp.timeBase;
279 if (mediaType == AVMEDIA_TYPE_VIDEO) {
280 params->width = msp.width;
281 params->height = msp.height;
282 params->frame_rate = msp.frameRate;
283 buffersrc = avfilter_get_by_name("buffer");
284 } else {
285 params->sample_rate = msp.sampleRate;
Aline Gondim Santosd251ea62023-03-02 09:40:56 -0300286 av_channel_layout_default(&(params->ch_layout), msp.nbChannels);
agsantosc9181b42020-11-26 12:03:04 -0500287 buffersrc = avfilter_get_by_name("abuffer");
288 }
289
agsantosb3c90842020-12-10 14:19:41 -0500290 AVFilterContext* buffersrcCtx{};
agsantosc9181b42020-11-26 12:03:04 -0500291 if (buffersrc) {
292 char name[128];
293 snprintf(name, sizeof(name), "buffersrc_%s_%d", in->name, in->pad_idx);
294 buffersrcCtx = avfilter_graph_alloc_filter(graph_, buffersrc, name);
295 }
296 if (!buffersrcCtx) {
297 av_free(params);
298 return fail("Failed to allocate filter graph input", AVERROR(ENOMEM));
299 }
300 ret = av_buffersrc_parameters_set(buffersrcCtx, params);
301 av_free(params);
302 if (ret < 0)
303 return fail("Failed to set filter graph input parameters", ret);
304
305 if ((ret = avfilter_init_str(buffersrcCtx, nullptr)) < 0)
306 return fail("Failed to initialize buffer source", ret);
307
308 if ((ret = avfilter_link(buffersrcCtx, 0, in->filter_ctx, in->pad_idx)) < 0)
309 return fail("Failed to link buffer source to graph", ret);
310
311 inputs_.push_back(buffersrcCtx);
312 msp.name = in->name;
313 inputParams_.push_back(msp);
314 return ret;
315}
316
317int
318FrameFilter::reinitialize()
319{
320 // keep parameters needed for initialization before clearing filter
321 auto params = std::move(inputParams_);
322 auto desc = std::move(desc_);
323 clean();
324 auto ret = initialize(desc, params);
325 return ret;
326}
327
328int
329FrameFilter::fail(std::string msg, int err) const
330{
331 if (!msg.empty())
332 Plog::log(Plog::LogPriority::INFO, "Frame Filter: ", "("+std::to_string(err)+") "+msg);
333 return err;
334}
335
336void
337FrameFilter::clean()
338{
339 initialized_ = false;
340 avfilter_graph_free(&graph_); // frees inputs_ and output_
341 desc_.clear();
342 inputs_.clear(); // don't point to freed memory
343 output_ = nullptr; // don't point to freed memory
344 inputParams_.clear();
345}
346} // namespace jami