From 98518e998f26ae23eb82667d6d767be19a7a35aa Mon Sep 17 00:00:00 2001
From: philippegorley <philippe.gorley@savoirfairelinux.com>
Date: Tue, 11 Sep 2018 15:48:56 -0400
Subject: [PATCH] filter: add automatic reinitialization

If one of the inputs has changed, the filter will reinitialize itself.

Change-Id: I884195b6fdf19ad72329364eeeefd33e3b3bbfff
---
 src/media/media_filter.cpp                | 35 +++++++++++++++++++----
 src/media/media_filter.h                  |  7 ++++-
 test/unitTest/media/test_media_filter.cpp | 32 +++++++++++++++++++++
 3 files changed, 68 insertions(+), 6 deletions(-)

diff --git a/src/media/media_filter.cpp b/src/media/media_filter.cpp
index cd08b275a6..fb0744f8d0 100644
--- a/src/media/media_filter.cpp
+++ b/src/media/media_filter.cpp
@@ -155,19 +155,27 @@ MediaFilter::getOutputParams() const
 }
 
 int
-MediaFilter::feedInput(AVFrame* frame, std::string inputName)
+MediaFilter::feedInput(AVFrame* frame, const std::string& inputName)
 {
     int ret = 0;
     if (!initialized_)
         return fail("Filter not initialized", -1);
 
     for (size_t i = 0; i < inputs_.size(); ++i) {
-        auto filterCtx = inputs_[i];
-        if (inputParams_[i].name != inputName)
+        auto& ms = inputParams_[i];
+        if (ms.name != inputName)
             continue;
 
+        if (ms.format != frame->format
+            || (ms.isVideo && (ms.width != frame->width || ms.height != frame->height))
+            || (!ms.isVideo && (ms.sampleRate != frame->sample_rate || ms.nbChannels != frame->channels))) {
+            ms.update(frame);
+            if ((ret = reinitialize()) < 0)
+                return fail("Failed to reinitialize filter with new input parameters", ret);
+        }
+
         int flags = AV_BUFFERSRC_FLAG_PUSH | AV_BUFFERSRC_FLAG_KEEP_REF;
-        if ((ret = av_buffersrc_add_frame_flags(filterCtx, frame, flags)) < 0)
+        if ((ret = av_buffersrc_add_frame_flags(inputs_[i], frame, flags)) < 0)
             return fail("Could not pass frame to filters", ret);
         else
             return 0;
@@ -281,6 +289,19 @@ MediaFilter::initInputFilter(AVFilterInOut* in, MediaStream msp)
     return ret;
 }
 
+int
+MediaFilter::reinitialize()
+{
+    // keep parameters needed for initialization before clearing filter
+    auto params = std::move(inputParams_);
+    auto desc = std::move(desc_);
+    clean();
+    auto ret = initialize(desc, params);
+    if (ret >= 0)
+        RING_DBG() << "Filter graph reinitialized";
+    return ret;
+}
+
 int
 MediaFilter::fail(std::string msg, int err) const
 {
@@ -292,8 +313,12 @@ MediaFilter::fail(std::string msg, int err) const
 void
 MediaFilter::clean()
 {
-    avfilter_graph_free(&graph_);
     initialized_ = false;
+    avfilter_graph_free(&graph_); // frees inputs_ and output_
+    desc_.clear();
+    inputs_.clear(); // don't point to freed memory
+    output_ = nullptr; // don't point to freed memory
+    inputParams_.clear();
 }
 
 } // namespace ring
diff --git a/src/media/media_filter.h b/src/media/media_filter.h
index 3868183362..3b05a13db2 100644
--- a/src/media/media_filter.h
+++ b/src/media/media_filter.h
@@ -88,7 +88,7 @@ class MediaFilter {
          *
          * NOTE Will fail if @inputName is not found in the graph.
          */
-        int feedInput(AVFrame* frame, std::string inputName);
+        int feedInput(AVFrame* frame, const std::string& inputName);
 
         /**
          * Pull a frame from the filter graph. Caller owns the frame reference.
@@ -112,6 +112,11 @@ class MediaFilter {
          */
         int initInputFilter(AVFilterInOut* in, MediaStream msp);
 
+        /**
+         * Reinitializes the filter graph with @inputParams_, which should be updated beforehand.
+         */
+        int reinitialize();
+
         /**
          * Convenience method that prints @msg and returns err.
          *
diff --git a/test/unitTest/media/test_media_filter.cpp b/test/unitTest/media/test_media_filter.cpp
index ad3f3e0eb1..e904545888 100644
--- a/test/unitTest/media/test_media_filter.cpp
+++ b/test/unitTest/media/test_media_filter.cpp
@@ -41,11 +41,13 @@ private:
     void testAudioFilter();
     void testVideoFilter();
     void testFilterParams();
+    void testReinit();
 
     CPPUNIT_TEST_SUITE(MediaFilterTest);
     CPPUNIT_TEST(testAudioFilter);
     CPPUNIT_TEST(testVideoFilter);
     CPPUNIT_TEST(testFilterParams);
+    CPPUNIT_TEST(testReinit);
     CPPUNIT_TEST_SUITE_END();
 
     std::unique_ptr<MediaFilter> filter_;
@@ -240,6 +242,36 @@ MediaFilterTest::testFilterParams()
     CPPUNIT_ASSERT(ms.format >= 0 && ms.width == width1 && ms.height == height1);
 }
 
+void
+MediaFilterTest::testReinit()
+{
+    std::string filterSpec = "[in1] aresample=48000";
+
+    // prepare audio frame
+    frame_ = av_frame_alloc();
+    frame_->format = AV_SAMPLE_FMT_S16;
+    frame_->channel_layout = AV_CH_LAYOUT_STEREO;
+    frame_->nb_samples = 100;
+    frame_->sample_rate = 44100;
+    frame_->channels = 2;
+
+    // construct the filter parameters with different sample rate
+    auto params = MediaStream("in1", frame_->format, rational<int>(1, 16000), 16000, frame_->channels);
+
+    // allocate and fill frame buffers
+    CPPUNIT_ASSERT(av_frame_get_buffer(frame_, 0) >= 0);
+    fill_samples(reinterpret_cast<uint16_t*>(frame_->data[0]), frame_->sample_rate, frame_->nb_samples, frame_->channels, 440.0);
+
+    // prepare filter
+    std::vector<MediaStream> vec;
+    vec.push_back(params);
+    CPPUNIT_ASSERT(filter_->initialize(filterSpec, vec) >= 0);
+
+    // filter should reinitialize on feedInput
+    CPPUNIT_ASSERT(filter_->feedInput(frame_, "in1") >= 0);
+    av_frame_free(&frame_);
+}
+
 }} // namespace ring::test
 
 RING_TEST_RUNNER(ring::test::MediaFilterTest::name());
-- 
GitLab