diff --git a/src/media/media_filter.cpp b/src/media/media_filter.cpp
index de9995d9ff03b3adff671c4ad8560cd8e3016084..cd08b275a6b62370ef04acd9abd05ce8e670612d 100644
--- a/src/media/media_filter.cpp
+++ b/src/media/media_filter.cpp
@@ -49,15 +49,6 @@ MediaFilter::getFilterDesc() const
     return desc_;
 }
 
-int
-MediaFilter::initialize(const std::string& filterDesc, MediaStream msp)
-{
-    std::vector<MediaStream> msps;
-    msps.push_back(msp);
-    desc_ = filterDesc;
-    return initialize(desc_, msps);
-}
-
 int
 MediaFilter::initialize(const std::string& filterDesc, std::vector<MediaStream> msps)
 {
@@ -94,25 +85,20 @@ MediaFilter::initialize(const std::string& filterDesc, std::vector<MediaStream>
         return fail("Size mismatch between number of inputs in filter graph and input parameter array",
                     AVERROR(EINVAL));
 
-    if (count > 1) {
-        /* Complex filter */
-        for (AVFilterInOut* current = inputs.get(); current; current = current->next) {
-            if (!current->name)
-                return fail("Complex filters' inputs require names", AVERROR(EINVAL));
-            std::string name = current->name;
-            const auto& it = std::find_if(msps.begin(), msps.end(), [name](const MediaStream& msp)
-                    { return msp.name == name; });
-            if (it != msps.end()) {
-                if ((ret = initInputFilter(current, *it, false)) < 0) {
-                    std::string msg = "Failed to find matching parameters for: " + name;
-                    return fail(msg, ret);
-                }
+    for (AVFilterInOut* current = inputs.get(); current; current = current->next) {
+        if (!current->name)
+            return fail("Filters require non empty names", AVERROR(EINVAL));
+        std::string name = current->name;
+        const auto& it = std::find_if(msps.begin(), msps.end(), [name](const MediaStream& msp)
+                { return msp.name == name; });
+        if (it != msps.end()) {
+            if ((ret = initInputFilter(current, *it)) < 0) {
+                std::string msg = "Failed to initialize input: " + name;
+                return fail(msg, ret);
             }
-        }
-    } else {
-        /* Simple filter */
-        if ((ret = initInputFilter(inputs.get(), msps[0], true)) < 0) {
-            return fail("Failed to create input for filter graph", ret);
+        } else {
+            std::string msg = "Failed to find matching parameters for: " + name;
+            return fail(msg, ret);
         }
     }
 
@@ -124,12 +110,6 @@ MediaFilter::initialize(const std::string& filterDesc, std::vector<MediaStream>
     return 0;
 }
 
-MediaStream
-MediaFilter::getInputParams() const
-{
-    return getInputParams("default");
-}
-
 MediaStream
 MediaFilter::getInputParams(const std::string& inputName) const
 {
@@ -174,12 +154,6 @@ MediaFilter::getOutputParams() const
     return output;
 }
 
-int
-MediaFilter::feedInput(AVFrame* frame)
-{
-    return feedInput(frame, "default");
-}
-
 int
 MediaFilter::feedInput(AVFrame* frame, std::string inputName)
 {
@@ -218,7 +192,7 @@ MediaFilter::readOutput()
     if (ret >= 0) {
         return frame;
     } else if (ret == AVERROR(EAGAIN)) {
-        // return nullptr
+        // no data available right now, try again
     } else if (ret == AVERROR_EOF) {
         RING_WARN() << "Filters have reached EOF, no more frames will be output";
     } else {
@@ -228,19 +202,6 @@ MediaFilter::readOutput()
     return nullptr;
 }
 
-AVFrame*
-MediaFilter::apply(AVFrame* frame)
-{
-    if (inputs_.size() != 1) {
-        RING_ERR() << "Cannot use apply(AVFrame*) shortcut with a complex filter";
-        return nullptr;
-    }
-
-    if (feedInput(frame) < 0)
-        return nullptr;
-    return readOutput();
-}
-
 int
 MediaFilter::initOutputFilter(AVFilterInOut* out)
 {
@@ -270,7 +231,7 @@ MediaFilter::initOutputFilter(AVFilterInOut* out)
 }
 
 int
-MediaFilter::initInputFilter(AVFilterInOut* in, MediaStream msp, bool simple)
+MediaFilter::initInputFilter(AVFilterInOut* in, MediaStream msp)
 {
     int ret = 0;
     AVBufferSrcParameters* params = av_buffersrc_parameters_alloc();
@@ -296,10 +257,7 @@ MediaFilter::initInputFilter(AVFilterInOut* in, MediaStream msp, bool simple)
     AVFilterContext* buffersrcCtx = nullptr;
     if (buffersrc) {
         char name[128];
-        if (simple)
-            snprintf(name, sizeof(name), "buffersrc");
-        else
-            snprintf(name, sizeof(name), "buffersrc_%s_%d", in->name, in->pad_idx);
+        snprintf(name, sizeof(name), "buffersrc_%s_%d", in->name, in->pad_idx);
         buffersrcCtx = avfilter_graph_alloc_filter(graph_, buffersrc, name);
     }
     if (!buffersrcCtx) {
@@ -318,10 +276,7 @@ MediaFilter::initInputFilter(AVFilterInOut* in, MediaStream msp, bool simple)
         return fail("Failed to link buffer source to graph", ret);
 
     inputs_.push_back(buffersrcCtx);
-    if (simple)
-        msp.name = "default";
-    else
-        msp.name = in->name;
+    msp.name = in->name;
     inputParams_.push_back(msp);
     return ret;
 }
diff --git a/src/media/media_filter.h b/src/media/media_filter.h
index 5e99dc73e8d889202a7df495d0c52b828c21f0c6..3868183362b192953408fccd30c6d7aa702281d5 100644
--- a/src/media/media_filter.h
+++ b/src/media/media_filter.h
@@ -37,21 +37,22 @@ namespace ring {
 /**
  * Provides access to libavfilter.
  *
- * Can be used for simple filters (1 input, 1 output), or complex filters (multiple inputs, 1 output).
+ * Can be used for filters with unlimited number of inputs.
  * Multiple outputs are not supported. They add complexity for little gain.
  *
  * For information on how to write a filter graph description, see:
  * https://ffmpeg.org/ffmpeg-filters.html
  * http://trac.ffmpeg.org/wiki/FilteringGuide
  *
- * For complex filters, it is required to name each filter graph input. These names are used to feed the correct input.
- * It is the same name that will be passed as second argument to feedInput(AVFrame*, std::string). This is not required
- * for simple filters, as there is only one input.
+ * It is required to name each filter graph input. These names are used to feed the correct input.
+ * It is the same name that will be passed as second argument to feedInput(AVFrame*, std::string).
  *
- * Simple filter: "scale=320:240"
- * Scales the input to 320x240. No need to specify input names.
+ * Examples:
  *
- * Complex filter: "[in1] scale=iw/4:ih/4 [mid]; [in2] [mid] overlay=main_w-overlay_w-10:main_h-overlay_h-10"
+ * - "[in1] scale=320:240"
+ * Scales the input to 320x240.
+ *
+ * - "[in1] scale=iw/4:ih/4 [mid]; [in2] [mid] overlay=main_w-overlay_w-10:main_h-overlay_h-10"
  * in1 will be scaled to 1/16th its size and placed over in2 in the bottom right corner. When feeding frames to
  * the filter, you need to specify whether the frame is destined for in1 or in2.
  */
@@ -65,26 +66,11 @@ class MediaFilter {
          */
         std::string getFilterDesc() const;
 
-        /**
-         * Initializes the filter graph with 1 input.
-         *
-         * NOTE This method will fail if @filterDesc has more than 1 input.
-         * NOTE Wraps @msp in a vector and calls initialize.
-         */
-        int initialize(const std::string& filterDesc, MediaStream msp);
-
         /**
          * Initializes the filter graph with one or more inputs and one output. Returns a negative code on error.
          */
         int initialize(const std::string& filterDesc, std::vector<MediaStream> msps);
 
-        /**
-         * Returns a MediaStream object describing the input.
-         *
-         * NOTE This is a shortcut for simple filters and will fail when called on a complex filter.
-         */
-        MediaStream getInputParams() const;
-
         /**
          * Returns a MediaStream object describing the input specified by @inputName.
          */
@@ -97,14 +83,6 @@ class MediaFilter {
          */
         MediaStream getOutputParams() const;
 
-        /**
-         * Give the filter graph an input frame. Caller is responsible for freeing the frame.
-         *
-         * NOTE This is a wrapper for feedInput(AVFrame*, std::string)
-         * NOTE This is for filters with 1 input.
-         */
-        int feedInput(AVFrame* frame);
-
         /**
          * Give the specified source filter an input frame. Caller is responsible for freeing the frame.
          *
@@ -121,16 +99,6 @@ class MediaFilter {
          */
         AVFrame* readOutput();
 
-        /**
-         * Passes a frame through a simple filter (1 input, 1 output).
-         *
-         * This is a shortcut for feedInput(AVFrame*)+readOutput().
-         *
-         * NOTE Returns nullptr if the filter graph has multiple inputs/outputs.
-         * NOTE Caller is responsible for freeing the input and output frames.
-         */
-        AVFrame* apply(AVFrame* frame);
-
     private:
         NON_COPYABLE(MediaFilter);
 
@@ -142,7 +110,7 @@ class MediaFilter {
         /**
          * Initializes an input of filter graph.
          */
-        int initInputFilter(AVFilterInOut* in, MediaStream msp, bool simple);
+        int initInputFilter(AVFilterInOut* in, MediaStream msp);
 
         /**
          * Convenience method that prints @msg and returns err.
diff --git a/src/media/media_recorder.cpp b/src/media/media_recorder.cpp
index e5a0bf0f30f2f7257ae2b26423bd868c41e981b0..943d3e3279614cb5d492ca1076894f3f58685033 100644
--- a/src/media/media_recorder.cpp
+++ b/src/media/media_recorder.cpp
@@ -339,37 +339,31 @@ MediaRecorder::setupVideoOutput()
     MediaStream encoderStream;
     const MediaStream& peer = streams_[true][true];
     const MediaStream& local = streams_[true][false];
+    int ret = -1;
 
     // vp8 supports only yuv420p
     videoFilter_.reset(new MediaFilter);
     switch (nbReceivedVideoStreams_) {
     case 1:
-        encoderStream = (peer.width > 0 && peer.height > 0 ? peer : local);
-        if (videoFilter_->initialize("format=pix_fmts=yuv420p", encoderStream) < 0) {
-            RING_ERR() << "Failed to initialize video filter";
-            encoderStream.format = -1; // invalidate stream
-        } else {
-            encoderStream = videoFilter_->getOutputParams();
-        }
+        ret = videoFilter_->initialize("[v:main] format=pix_fmts=yuv420p",
+            std::vector<MediaStream>{peer.isValid() ? peer : local});
         break;
     case 2: // overlay local video over peer video
-        if (videoFilter_->initialize(buildVideoFilter(),
-                std::vector<MediaStream>{peer, local}) < 0) {
-            RING_ERR() << "Failed to initialize video filter";
-            encoderStream.format = -1; // invalidate stream
-        } else {
-            encoderStream = videoFilter_->getOutputParams();
-        }
+        ret = videoFilter_->initialize(buildVideoFilter(),
+            std::vector<MediaStream>{peer, local});
         break;
     default:
         RING_ERR() << "Recording more than 2 video streams is not supported";
         break;
     }
 
-    if (encoderStream.format < 0)
-        return encoderStream;
+    if (ret >= 0) {
+        encoderStream = videoFilter_->getOutputParams();
+        RING_DBG() << "Recorder output: " << encoderStream;
+    } else {
+        RING_ERR() << "Failed to initialize video filter";
+    }
 
-    RING_DBG() << "Recorder output: " << encoderStream;
     return encoderStream;
 }
 
@@ -406,39 +400,32 @@ MediaRecorder::setupAudioOutput()
     MediaStream encoderStream;
     const MediaStream& peer = streams_[false][true];
     const MediaStream& local = streams_[false][false];
+    std::string filter = "aresample=osr=48000:ocl=stereo:osf=s16";
+    int ret = -1;
 
     // resample to common audio format, so any player can play the file
     audioFilter_.reset(new MediaFilter);
     switch (nbReceivedAudioStreams_) {
     case 1:
-        encoderStream = (peer.sampleRate > 0 && peer.nbChannels > 0 ? peer : local);
-        if (audioFilter_->initialize("aresample=osr=48000:ocl=stereo:osf=s16",
-                encoderStream) < 0) {
-            RING_ERR() << "Failed to initialize audio filter";
-            encoderStream.format = -1; // invalidate stream
-        } else {
-            encoderStream = audioFilter_->getOutputParams();
-        }
+        filter.insert(0, "[a:1] ");
+        ret = audioFilter_->initialize(filter, std::vector<MediaStream>{peer.isValid() ? peer : local});
         break;
     case 2: // mix both audio streams
-        if (audioFilter_->initialize("[a:1][a:2] amix,aresample=osr=48000:ocl=stereo:osf=s16",
-                std::vector<MediaStream>{peer, local}) < 0) {
-            RING_ERR() << "Failed to initialize audio filter";
-            encoderStream.format = -1; // invalidate stream
-        } else {
-            encoderStream = audioFilter_->getOutputParams();
-        }
+        filter.insert(0, "[a:1][a:2] amix,");
+        ret = audioFilter_->initialize(filter, std::vector<MediaStream>{peer, local});
         break;
     default:
         RING_ERR() << "Recording more than 2 audio streams is not supported";
-        encoderStream.format = -1; // invalidate stream
         break;
     }
 
-    if (encoderStream.format < 0)
-        return encoderStream;
+    if (ret >= 0) {
+        encoderStream = audioFilter_->getOutputParams();
+        RING_DBG() << "Recorder output: " << encoderStream;
+    } else {
+        RING_ERR() << "Failed to initialize audio filter";
+    }
 
-    RING_DBG() << "Recorder output: " << encoderStream;
     return encoderStream;
 }
 
diff --git a/test/unitTest/media/test_media_filter.cpp b/test/unitTest/media/test_media_filter.cpp
index 3953e721714154f76002e537af082496a9d48003..ad3f3e0eb1ca83cc51d4c4b1485d5b2c518cbc5f 100644
--- a/test/unitTest/media/test_media_filter.cpp
+++ b/test/unitTest/media/test_media_filter.cpp
@@ -38,18 +38,14 @@ public:
     void tearDown();
 
 private:
-    void testSimpleVideoFilter();
-    void testSimpleAudioFilter();
-    void testComplexVideoFilter();
-    void testSimpleFilterParams();
-    void testComplexFilterParams();
+    void testAudioFilter();
+    void testVideoFilter();
+    void testFilterParams();
 
     CPPUNIT_TEST_SUITE(MediaFilterTest);
-    CPPUNIT_TEST(testSimpleVideoFilter);
-    CPPUNIT_TEST(testSimpleAudioFilter);
-    CPPUNIT_TEST(testComplexVideoFilter);
-    CPPUNIT_TEST(testSimpleFilterParams);
-    CPPUNIT_TEST(testComplexFilterParams);
+    CPPUNIT_TEST(testAudioFilter);
+    CPPUNIT_TEST(testVideoFilter);
+    CPPUNIT_TEST(testFilterParams);
     CPPUNIT_TEST_SUITE_END();
 
     std::unique_ptr<MediaFilter> filter_;
@@ -113,44 +109,9 @@ fill_samples(uint16_t* samples, int sampleRate, int nbSamples, int nbChannels, f
 }
 
 void
-MediaFilterTest::testSimpleVideoFilter()
+MediaFilterTest::testAudioFilter()
 {
-    std::string filterSpec = "scale=200x100";
-
-    // constants
-    const constexpr int width = 320;
-    const constexpr int height = 240;
-    const constexpr AVPixelFormat format = AV_PIX_FMT_YUV420P;
-
-    // prepare video frame
-    frame_ = av_frame_alloc();
-    frame_->format = format;
-    frame_->width = width;
-    frame_->height = height;
-
-    // construct the filter parameters
-    rational<int> one = rational<int>(1);
-    auto params = MediaStream("vf", format, one, width, height, one, one);
-
-    // allocate and fill frame buffers
-    CPPUNIT_ASSERT(av_frame_get_buffer(frame_, 32) >= 0);
-    fill_yuv_image(frame_->data, frame_->linesize, frame_->width, frame_->height, 0);
-
-    // prepare filter
-    CPPUNIT_ASSERT(filter_->initialize(filterSpec, params) >= 0);
-
-    // apply filter
-    frame_ = filter_->apply(frame_);
-    CPPUNIT_ASSERT(frame_);
-
-    // check if the filter worked
-    CPPUNIT_ASSERT(frame_->width == 200 && frame_->height == 100);
-}
-
-void
-MediaFilterTest::testSimpleAudioFilter()
-{
-    std::string filterSpec = "aformat=sample_fmts=u8";
+    std::string filterSpec = "[in1] aformat=sample_fmts=u8";
 
     // constants
     const constexpr int nbSamples = 100;
@@ -158,6 +119,7 @@ MediaFilterTest::testSimpleAudioFilter()
     const constexpr int sampleRate = 44100;
     const constexpr enum AVSampleFormat format = AV_SAMPLE_FMT_S16;
 
+
     // prepare audio frame
     frame_ = av_frame_alloc();
     frame_->format = format;
@@ -167,17 +129,21 @@ MediaFilterTest::testSimpleAudioFilter()
     frame_->channels = av_get_channel_layout_nb_channels(channelLayout);
 
     // construct the filter parameters
-    auto params = MediaStream("af", format, rational<int>(1, 1), sampleRate, frame_->channels);
+    auto params = MediaStream("in1", format, rational<int>(1, sampleRate), sampleRate, 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]), sampleRate, nbSamples, frame_->channels, 440.0);
 
     // prepare filter
-    CPPUNIT_ASSERT(filter_->initialize(filterSpec, params) >= 0);
+    std::vector<MediaStream> vec;
+    vec.push_back(params);
+    CPPUNIT_ASSERT(filter_->initialize(filterSpec, vec) >= 0);
 
     // apply filter
-    frame_ = filter_->apply(frame_);
+    CPPUNIT_ASSERT(filter_->feedInput(frame_, "in1") >= 0);
+    av_frame_free(&frame_);
+    frame_ = filter_->readOutput();
     CPPUNIT_ASSERT(frame_);
 
     // check if the filter worked
@@ -185,7 +151,7 @@ MediaFilterTest::testSimpleAudioFilter()
 }
 
 void
-MediaFilterTest::testComplexVideoFilter()
+MediaFilterTest::testVideoFilter()
 {
     std::string filterSpec = "[main] [top] overlay=main_w-overlay_w-10:main_h-overlay_h-10";
     std::string main = "main";
@@ -238,36 +204,7 @@ MediaFilterTest::testComplexVideoFilter()
 }
 
 void
-MediaFilterTest::testSimpleFilterParams()
-{
-    std::string filterSpec = "scale=200x100";
-
-    // constants
-    const constexpr int width = 320;
-    const constexpr int height = 240;
-    const constexpr AVPixelFormat format = AV_PIX_FMT_YUV420P;
-
-    // construct the filter parameters
-    rational<int> one = rational<int>(1);
-    auto params = MediaStream("vf", format, one, width, height, one, one);
-
-    // returned params should be invalid
-    CPPUNIT_ASSERT(filter_->getOutputParams().format < 0);
-
-    // prepare filter
-    CPPUNIT_ASSERT(filter_->initialize(filterSpec, params) >= 0);
-
-    // check input params
-    auto msin = filter_->getInputParams();
-    CPPUNIT_ASSERT(msin.format == format && msin.width == width && msin.height == height);
-
-    // output params should now be valid
-    auto msout = filter_->getOutputParams();
-    CPPUNIT_ASSERT(msout.format >= 0 && msout.width > 0 && msout.height > 0);
-}
-
-void
-MediaFilterTest::testComplexFilterParams()
+MediaFilterTest::testFilterParams()
 {
     std::string filterSpec = "[main] [top] overlay=main_w-overlay_w-10:main_h-overlay_h-10";