LCOV - code coverage report
Current view: top level - src/media - media_filter.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 162 203 79.8 %
Date: 2024-12-21 08:56:24 Functions: 16 17 94.1 %

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

Generated by: LCOV version 1.14