diff --git a/src/media/media_filter.cpp b/src/media/media_filter.cpp
index 64e7b5e8620e1f9ed59d5828f1c52993790af18e..de9995d9ff03b3adff671c4ad8560cd8e3016084 100644
--- a/src/media/media_filter.cpp
+++ b/src/media/media_filter.cpp
@@ -125,7 +125,22 @@ MediaFilter::initialize(const std::string& filterDesc, std::vector<MediaStream>
 }
 
 MediaStream
-MediaFilter::getOutputParams()
+MediaFilter::getInputParams() const
+{
+    return getInputParams("default");
+}
+
+MediaStream
+MediaFilter::getInputParams(const std::string& inputName) const
+{
+    for (auto ms : inputParams_)
+        if (ms.name == inputName)
+            return ms;
+    return {};
+}
+
+MediaStream
+MediaFilter::getOutputParams() const
 {
     MediaStream output;
     if (!output_ || !initialized_) {
@@ -174,7 +189,7 @@ MediaFilter::feedInput(AVFrame* frame, std::string inputName)
 
     for (size_t i = 0; i < inputs_.size(); ++i) {
         auto filterCtx = inputs_[i];
-        if (inputNames_[i] != inputName)
+        if (inputParams_[i].name != inputName)
             continue;
 
         int flags = AV_BUFFERSRC_FLAG_PUSH | AV_BUFFERSRC_FLAG_KEEP_REF;
@@ -213,6 +228,19 @@ 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)
 {
@@ -291,14 +319,15 @@ MediaFilter::initInputFilter(AVFilterInOut* in, MediaStream msp, bool simple)
 
     inputs_.push_back(buffersrcCtx);
     if (simple)
-        inputNames_.push_back("default");
+        msp.name = "default";
     else
-        inputNames_.push_back(in->name);
+        msp.name = in->name;
+    inputParams_.push_back(msp);
     return ret;
 }
 
 int
-MediaFilter::fail(std::string msg, int err)
+MediaFilter::fail(std::string msg, int err) const
 {
     if (!msg.empty())
         RING_ERR() << msg << ": " << libav_utils::getError(err);
diff --git a/src/media/media_filter.h b/src/media/media_filter.h
index b3278ef9b1e0ae9094b811bc1a8ca8ba5f8eff2f..5e99dc73e8d889202a7df495d0c52b828c21f0c6 100644
--- a/src/media/media_filter.h
+++ b/src/media/media_filter.h
@@ -78,12 +78,24 @@ class MediaFilter {
          */
         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.
+         */
+        MediaStream getInputParams(const std::string& inputName) const;
+
         /**
          * Returns a MediaStream struct describing the frames that will be output.
          *
          * When called in an invalid state, the returned format will be invalid (less than 0).
          */
-        MediaStream getOutputParams();
+        MediaStream getOutputParams() const;
 
         /**
          * Give the filter graph an input frame. Caller is responsible for freeing the frame.
@@ -109,6 +121,16 @@ 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);
 
@@ -127,7 +149,7 @@ class MediaFilter {
          *
          * NOTE @msg should not be null.
          */
-        int fail(std::string msg, int err);
+        int fail(std::string msg, int err) const;
 
         /**
          * Frees resources used by MediaFilter.
@@ -150,9 +172,9 @@ class MediaFilter {
         std::vector<AVFilterContext*> inputs_;
 
         /**
-         * List of filter graph input names. Same order as @inputs_.
+         * List of filter graph input parameters. Same order as @inputs_.
          */
-        std::vector<std::string> inputNames_;
+        std::vector<MediaStream> inputParams_;
 
         /**
          * Filter graph string.
diff --git a/test/unitTest/media/test_media_filter.cpp b/test/unitTest/media/test_media_filter.cpp
index a7a42592bebde9a07059706b448b9bfceb0cbfc3..3953e721714154f76002e537af082496a9d48003 100644
--- a/test/unitTest/media/test_media_filter.cpp
+++ b/test/unitTest/media/test_media_filter.cpp
@@ -140,8 +140,7 @@ MediaFilterTest::testSimpleVideoFilter()
     CPPUNIT_ASSERT(filter_->initialize(filterSpec, params) >= 0);
 
     // apply filter
-    CPPUNIT_ASSERT(filter_->feedInput(frame_) >= 0);
-    frame_ = filter_->readOutput();
+    frame_ = filter_->apply(frame_);
     CPPUNIT_ASSERT(frame_);
 
     // check if the filter worked
@@ -178,8 +177,7 @@ MediaFilterTest::testSimpleAudioFilter()
     CPPUNIT_ASSERT(filter_->initialize(filterSpec, params) >= 0);
 
     // apply filter
-    CPPUNIT_ASSERT(filter_->feedInput(frame_) >= 0);
-    frame_ = filter_->readOutput();
+    frame_ = filter_->apply(frame_);
     CPPUNIT_ASSERT(frame_);
 
     // check if the filter worked
@@ -259,17 +257,19 @@ MediaFilterTest::testSimpleFilterParams()
     // 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 ms = filter_->getOutputParams();
-    CPPUNIT_ASSERT(ms.format >= 0 && ms.width > 0 && ms.height > 0);
+    auto msout = filter_->getOutputParams();
+    CPPUNIT_ASSERT(msout.format >= 0 && msout.width > 0 && msout.height > 0);
 }
 
 void
 MediaFilterTest::testComplexFilterParams()
 {
     std::string filterSpec = "[main] [top] overlay=main_w-overlay_w-10:main_h-overlay_h-10";
-    std::string main = "main";
-    std::string top = "top";
 
     // constants
     const constexpr int width1 = 320;
@@ -292,6 +292,12 @@ MediaFilterTest::testComplexFilterParams()
     vec.push_back(params1);
     CPPUNIT_ASSERT(filter_->initialize(filterSpec, vec) >= 0);
 
+    // check input params
+    auto main = filter_->getInputParams("main");
+    CPPUNIT_ASSERT(main.format == format && main.width == width1 && main.height == height1);
+    auto top = filter_->getInputParams("top");
+    CPPUNIT_ASSERT(top.format == format && top.width == width2 && top.height == height2);
+
     // output params should now be valid
     auto ms = filter_->getOutputParams();
     CPPUNIT_ASSERT(ms.format >= 0 && ms.width == width1 && ms.height == height1);