LCOV - code coverage report
Current view: top level - src/media - media_filter.cpp (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 77.6 % 205 159
Test Date: 2026-06-13 09:18:46 Functions: 78.6 % 28 22

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

Generated by: LCOV version 2.0-1