diff --git a/src/client/ring_signal.cpp b/src/client/ring_signal.cpp
index 1a9c875a4b7afb6d03753bf3d3f5041a97d7811f..ba74f93068021582b365dd208acde1aaa03160b3 100644
--- a/src/client/ring_signal.cpp
+++ b/src/client/ring_signal.cpp
@@ -88,11 +88,10 @@ getSignalHandlers()
         exported_callback<DRing::VideoSignal::DecodingStarted>(),
         exported_callback<DRing::VideoSignal::DecodingStopped>(),
 #ifdef __ANDROID__
-        exported_callback<DRing::VideoSignal::AcquireCamera>(),
-        exported_callback<DRing::VideoSignal::ReleaseCamera>(),
-        exported_callback<DRing::VideoSignal::GetCameraFormats>(),
-        exported_callback<DRing::VideoSignal::GetCameraSizes>(),
-        exported_callback<DRing::VideoSignal::GetCameraRates>(),
+        exported_callback<DRing::VideoSignal::GetCameraInfo>(),
+        exported_callback<DRing::VideoSignal::SetParameters>(),
+        exported_callback<DRing::VideoSignal::StartCapture>(),
+        exported_callback<DRing::VideoSignal::StopCapture>(),
 #endif
 #endif
     };
diff --git a/src/client/videomanager.cpp b/src/client/videomanager.cpp
index 009e8130d51e3bfc1e23b545f613e24ce01a3010..10f4b9b48e886807a967a76ff2154b483117482b 100644
--- a/src/client/videomanager.cpp
+++ b/src/client/videomanager.cpp
@@ -159,6 +159,22 @@ removeVideoDevice(const std::string &node)
 {
     videoManager.videoDeviceMonitor.removeDevice(node);
 }
+
+void*
+obtainFrame(int length)
+{
+    if (auto input = videoManager.videoInput.lock())
+        return (*input).obtainFrame(length);
+
+    return nullptr;
+}
+
+void
+releaseFrame(void* frame)
+{
+    if (auto input = videoManager.videoInput.lock())
+        (*input).releaseFrame(frame);
+}
 #endif
 
 } // namespace DRing
diff --git a/src/dring/videomanager_interface.h b/src/dring/videomanager_interface.h
index 13eef20cfd5e58b2025eab47cd2e34d91a7cd68a..1097b38f7605e83811fb1f3a9e4036791d22da24 100644
--- a/src/dring/videomanager_interface.h
+++ b/src/dring/videomanager_interface.h
@@ -73,6 +73,8 @@ void registerSinkTarget(const std::string& sinkId, const SinkTarget& target);
 #ifdef __ANDROID__
 void addVideoDevice(const std::string &node);
 void removeVideoDevice(const std::string &node);
+void* obtainFrame(int length);
+void releaseFrame(void* frame);
 #endif
 
 // Video signal type definitions
@@ -90,25 +92,21 @@ struct VideoSignal {
                 using cb_type = void(const std::string& /*id*/, const std::string& /*shm_path*/, bool /*is_mixer*/);
         };
 #ifdef __ANDROID__
-        struct AcquireCamera {
-            constexpr static const char* name = "AcquireCamera";
-            using cb_type = void(const std::string& device);
-        };
-        struct ReleaseCamera {
-            constexpr static const char* name = "ReleaseCamera";
-            using cb_type = void(const std::string& device);
+        struct GetCameraInfo {
+            constexpr static const char* name = "GetCameraInfo";
+            using cb_type = void(const std::string& device, std::vector<int> *formats, std::vector<unsigned> *sizes, std::vector<unsigned> *rates);
         };
-        struct GetCameraFormats {
-            constexpr static const char* name = "GetCameraFormats";
-            using cb_type = void(const std::string& device, std::vector<int> *formats_);
+        struct SetParameters {
+            constexpr static const char* name = "SetParameters";
+            using cb_type = void(const std::string& device, const int format, const int width, const int height, const int rate);
         };
-        struct GetCameraSizes {
-            constexpr static const char* name = "GetCameraSizes";
-            using cb_type = void(const std::string& device, int format, std::vector<std::string> *sizes);
+        struct StartCapture {
+            constexpr static const char* name = "StartCapture";
+            using cb_type = void(const std::string& device);
         };
-        struct GetCameraRates{
-            constexpr static const char* name = "GetCameraRates";
-            using cb_type = void(const std::string& device, const int format, const std::string& size, std::vector<float> *rates_);
+        struct StopCapture {
+            constexpr static const char* name = "StopCapture";
+            using cb_type = void(void);
         };
 #endif
 };
diff --git a/src/media/libav_utils.cpp b/src/media/libav_utils.cpp
index 2dfd4efcec0b06fa7a8e93296b32ddb1bd53c252..da4f44a360274ca363905171c2b778ab95f6e829 100644
--- a/src/media/libav_utils.cpp
+++ b/src/media/libav_utils.cpp
@@ -116,6 +116,8 @@ int libav_pixel_format(int fmt)
         case video::VIDEO_PIXFMT_BGRA: return PIXEL_FORMAT(BGRA);
         case video::VIDEO_PIXFMT_RGBA: return PIXEL_FORMAT(RGBA);
         case video::VIDEO_PIXFMT_YUYV422: return PIXEL_FORMAT(YUYV422);
+        case video::VIDEO_PIXFMT_YUV420P: return PIXEL_FORMAT(YUV420P);
+        case video::VIDEO_PIXFMT_NV21: return PIXEL_FORMAT(NV21);
     }
     return fmt;
 }
diff --git a/src/media/media_buffer.cpp b/src/media/media_buffer.cpp
index c8a6c8cbc032936543b1f6d07ae621f8b8f7718b..0620fe3d8823fb8742230be8bf552d981aa45751 100644
--- a/src/media/media_buffer.cpp
+++ b/src/media/media_buffer.cpp
@@ -44,6 +44,12 @@ MediaFrame::reset() noexcept
 
 #ifdef RING_VIDEO
 
+VideoFrame::~VideoFrame()
+{
+    if (releaseBufferCb_)
+        releaseBufferCb_(ptr_);
+}
+
 void
 VideoFrame::reset() noexcept
 {
@@ -51,6 +57,7 @@ VideoFrame::reset() noexcept
 #if !USE_OLD_AVU
     allocated_ = false;
 #endif
+    releaseBufferCb_ = {};
 }
 
 size_t
@@ -107,10 +114,11 @@ VideoFrame::reserve(int format, int width, int height)
     if (av_frame_get_buffer(libav_frame, 32))
         throw std::bad_alloc();
     allocated_ = true;
+    releaseBufferCb_ = {};
 }
 
 void
-VideoFrame::setFromMemory(uint8_t* ptr, int format, int width, int height)
+VideoFrame::setFromMemory(uint8_t* ptr, int format, int width, int height) noexcept
 {
     reset();
     setGeometry(format, width, height);
@@ -120,6 +128,17 @@ VideoFrame::setFromMemory(uint8_t* ptr, int format, int width, int height)
                    (AVPixelFormat)frame_->format, width, height);
 }
 
+void
+VideoFrame::setFromMemory(uint8_t* ptr, int format, int width, int height,
+                          std::function<void(void*)> cb) noexcept
+{
+    setFromMemory(ptr, format, width, height);
+    if (cb) {
+        releaseBufferCb_ = cb;
+        ptr_ = ptr;
+    }
+}
+
 void
 VideoFrame::noise()
 {
diff --git a/src/media/media_buffer.h b/src/media/media_buffer.h
index 106597cc00aadccf87c5d1e8942f1daf22bb9057..a59fb616bad33e2207e4bed59c9c876fb958be10 100644
--- a/src/media/media_buffer.h
+++ b/src/media/media_buffer.h
@@ -57,6 +57,7 @@ class VideoFrame: public MediaFrame {
     public:
         // Construct an empty VideoFrame
         VideoFrame() = default;
+        ~VideoFrame();
 
         // Reset internal buffers (return to an empty VideoFrame)
         void reset() noexcept override;
@@ -78,7 +79,8 @@ class VideoFrame: public MediaFrame {
 
         // Set internal pixel buffers on given memory buffer
         // This buffer must follow given specifications.
-        void setFromMemory(uint8_t* data, int format, int width, int height);
+        void setFromMemory(uint8_t* data, int format, int width, int height) noexcept;
+        void setFromMemory(uint8_t* data, int format, int width, int height, std::function<void(void*)> cb) noexcept;
 
         void noise();
 
@@ -86,6 +88,8 @@ class VideoFrame: public MediaFrame {
         VideoFrame& operator =(const VideoFrame& src);
 
     private:
+        std::function<void(void*)> releaseBufferCb_ {};
+        void *ptr_ {nullptr};
         bool allocated_ {false};
         void setGeometry(int format, int width, int height) noexcept;
 };
diff --git a/src/media/video/androidvideo/video_device_impl.cpp b/src/media/video/androidvideo/video_device_impl.cpp
index b41f822385a4531ad20f59b552c3afb0c6bd06a7..789526dcf6a957131f3d5b00000d177408b44514 100644
--- a/src/media/video/androidvideo/video_device_impl.cpp
+++ b/src/media/video/androidvideo/video_device_impl.cpp
@@ -33,19 +33,19 @@
 
 namespace ring { namespace video {
 
-class VideoAndroidSize {
-    public:
-        VideoAndroidSize(std::string size, std::string camera_id, int format);
-
-        std::string str() const;
-        float getRate(std::string) const;
-        float getRate(unsigned) const;
-        std::vector<std::string> getRateList() const;
+/*
+ * Array to match Android formats. List formats in ascending
+ * preferrence: the format with the lower index will be picked.
+ */
+struct android_fmt {
+    int             code;
+    std::string     name;
+    enum VideoPixelFormat ring_format;
+};
 
-        unsigned width;
-        unsigned height;
-    private:
-        std::vector<float> rates_;
+static const struct android_fmt and_formats[] {
+    { 17,           "NV21",     VIDEO_PIXFMT_NV21 },
+    { 842094169,    "YUV420",   VIDEO_PIXFMT_YUV420P },
 };
 
 class VideoDeviceImpl {
@@ -55,193 +55,175 @@ class VideoDeviceImpl {
          */
         VideoDeviceImpl(const std::string& path);
 
-        std::string camera_id;
         std::string name;
 
-        const VideoAndroidSize& getSize(const std::string size) const;
-
-        VideoSettings getSettings() const;
-        void applySettings(VideoSettings settings);
-
         DeviceParams getDeviceParams() const;
-        std::vector<std::string> getSizeList() const;
-    private:
-        int format_;
-        std::string size_;
-        unsigned rate_;
-        std::vector<VideoAndroidSize> sizes_;
-};
+        void setDeviceParams(const DeviceParams&);
 
-/* VideoAndroidSize */
-VideoAndroidSize::VideoAndroidSize(std::string size, std::string camera_id, int format)
-{
-    sscanf(size.c_str(), "%ux%u", &width, &height);
+        std::vector<VideoSize> getSizeList() const;
+        std::vector<FrameRate> getRateList() const;
+    private:
+        void selectFormat();
+        VideoSize getSize(VideoSize size) const;
+        FrameRate getRate(FrameRate rate) const;
 
-    emitSignal<DRing::VideoSignal::GetCameraRates>(camera_id, format, size, &rates_);
-}
+        std::vector<int> formats_ {};
+        std::vector<VideoSize> sizes_ {};
+        std::vector<FrameRate> rates_ {};
 
-std::string
-VideoAndroidSize::str() const
-{
-    std::stringstream ss;
-    ss << width << "x" << height;
-    return ss.str();
-}
+        const struct android_fmt *fmt_ {nullptr};
+        VideoSize size_ {};
+        FrameRate rate_ {};
+};
 
-float
-VideoAndroidSize::getRate(unsigned rate) const
-{
-    for(const auto &r : rates_) {
-        if (r == rate)
-            return r;
+void
+VideoDeviceImpl::selectFormat()
+{
+    /*
+     * formats_ contains camera parameters as returned by the GetCameraInfo
+     * signal, find the matching V4L2 formats
+     */
+    unsigned int current, best = UINT_MAX;
+    for(const auto &fmt : formats_) {
+        const struct android_fmt *and_fmt;
+        for(and_fmt = std::begin(and_formats);
+            and_formats < std::end(and_formats);
+            and_fmt++) {
+            if (fmt == and_fmt->code) {
+                current = and_fmt - std::begin(and_formats);
+                break;
+            }
+        }
+
+        /* No match found, we should add it */
+        if (and_fmt == std::end(and_formats)) {
+            RING_WARN("AndroidVideo: No format matching %d", fmt);
+            continue;
+        }
+
+        if (current < best)
+            best = current;
     }
 
-    assert(not rates_.empty());
-    return rates_.back();
-}
-
-float
-VideoAndroidSize::getRate(std::string rate) const
-{
-    unsigned r;
-    std::stringstream ss;
-    ss << rate;
-    ss >> r;
-
-    return getRate(r);
-}
-
-std::vector<std::string>
-VideoAndroidSize::getRateList() const
-{
-    std::vector<std::string> v;
-
-    for(const auto &rate : rates_) {
-        std::stringstream ss;
-        ss << rate;
-        v.push_back(ss.str());
+    if (best != UINT_MAX) {
+        fmt_ = &and_formats[best];
+        RING_DBG("AndroidVideo: picked format %s", fmt_->name.c_str());
+    }
+    else {
+        fmt_ = &and_formats[0];
+        RING_ERR("AndroidVideo: Could not find a known format to use");
     }
-
-    return v;
 }
 
-/* VideoDeviceImpl */
-VideoDeviceImpl::VideoDeviceImpl(const std::string& path) :
-    camera_id(path), name(path), format_(), rate_()
+VideoDeviceImpl::VideoDeviceImpl(const std::string& path) : name(path)
 {
-    emitSignal<DRing::VideoSignal::AcquireCamera>(camera_id);
-    RING_DBG("### Acquired Camera %s", camera_id.c_str());
-
-    std::vector<int> formats;
-    emitSignal<DRing::VideoSignal::GetCameraFormats>(camera_id, &formats);
-
-    assert(not formats.empty());
-    format_ = formats[0]; /* FIXME: select a real value */
+    std::vector<unsigned> sizes;
+    std::vector<unsigned> rates;
+    emitSignal<DRing::VideoSignal::GetCameraInfo>(name, &formats_, &sizes, &rates);
+    for (size_t i=0, n=sizes.size(); i<n; i+=2)
+        sizes_.emplace_back(sizes[i], sizes[i+1]);
+    for (const auto& r : rates)
+        rates_.emplace_back(r, 1000);
 
-    std::vector<std::string> sizes;
-    emitSignal<DRing::VideoSignal::GetCameraSizes>(camera_id, format_, &sizes);
-    for(const auto &iter : sizes) {
-        sizes_.push_back(VideoAndroidSize(iter, camera_id, format_));
-    }
-
-    emitSignal<DRing::VideoSignal::ReleaseCamera>(camera_id);
-
-    // Set default settings
-    applySettings(VideoSettings());
+    selectFormat();
 }
 
-const VideoAndroidSize&
-VideoDeviceImpl::getSize(const std::string size) const
+VideoSize
+VideoDeviceImpl::getSize(VideoSize size) const
 {
     for (const auto &iter : sizes_) {
-        if (iter.str() == size)
+        if (iter == size)
             return iter;
     }
 
-    assert(not sizes_.empty());
-    return sizes_.back();
+    return sizes_.empty() ? VideoSize{0, 0} : sizes_.back();
 }
 
-std::vector<std::string>
-VideoDeviceImpl::getSizeList() const
+FrameRate
+VideoDeviceImpl::getRate(FrameRate rate) const
 {
-    std::vector<std::string> v;
-
-    for(const auto &iter : sizes_)
-        v.push_back(iter.str());
+    for (const auto &iter : rates_) {
+        if (iter == rate)
+            return iter;
+    }
 
-    return v;
+    return rates_.empty() ? FrameRate{0, 0} : rates_.back();
 }
 
-void
-VideoDeviceImpl::applySettings(VideoSettings settings)
+std::vector<VideoSize>
+VideoDeviceImpl::getSizeList() const
 {
-    const VideoAndroidSize &s = getSize(settings.video_size);
-    size_ = s.str();
-    rate_ = s.getRate(settings.framerate);
+    return sizes_;
 }
 
-VideoSettings
-VideoDeviceImpl::getSettings() const
+std::vector<FrameRate>
+VideoDeviceImpl::getRateList() const
 {
-    VideoSettings settings;
-    settings.name = name;
-    settings.channel = "default";
-    settings.video_size = size_;
-    settings.framerate = rate_;
-    return settings;
+    return rates_;
 }
 
 DeviceParams
 VideoDeviceImpl::getDeviceParams() const
 {
-    const VideoAndroidSize &s = getSize(size_);
-
     DeviceParams params;
-    params.input = camera_id;
-    params.format = "android";
+    std::stringstream ss1, ss2;
+    char sep;
+
+    ss1 << fmt_->ring_format;
+    ss1 >> params.format;
+
+    params.name = name;
+    params.input = name;
     params.channel =  0;
-    params.width = s.width;
-    params.height = s.height;
+    params.width = size_.first;
+    params.height = size_.second;
     params.framerate = rate_;
+
     return params;
 }
 
+void
+VideoDeviceImpl::setDeviceParams(const DeviceParams& params)
+{
+    size_ = getSize({params.width, params.height});
+    rate_ = getRate(params.framerate);
+    emitSignal<DRing::VideoSignal::SetParameters>(name, fmt_->code, size_.first, size_.second, rate_.real());
+}
+
 VideoDevice::VideoDevice(const std::string& path) :
     deviceImpl_(new VideoDeviceImpl(path))
 {
     name = deviceImpl_->name;
 }
 
-void
-VideoDevice::applySettings(VideoSettings settings)
+DeviceParams
+VideoDevice::getDeviceParams() const
 {
-    deviceImpl_->applySettings(settings);
+    return deviceImpl_->getDeviceParams();
 }
 
-VideoSettings
-VideoDevice::getSettings() const
+void
+VideoDevice::setDeviceParams(const DeviceParams& params)
 {
-    return deviceImpl_->getSettings();
+    return deviceImpl_->setDeviceParams(params);
 }
 
-DeviceParams
-VideoDevice::getDeviceParams() const
+std::vector<std::string>
+VideoDevice::getChannelList() const
 {
-    return deviceImpl_->getDeviceParams();
+    return {"default"};
 }
 
-DRing::VideoCapabilities
-VideoDevice::getCapabilities() const
+std::vector<VideoSize>
+VideoDevice::getSizeList(const std::string& channel) const
 {
-    DRing::VideoCapabilities cap;
-
-    for (const auto &iter : deviceImpl_->getSizeList()) {
-        const auto &s = deviceImpl_->getSize(iter);
-        cap["default"][s.str()] = s.getRateList();
-    }
+    return deviceImpl_->getSizeList();
+}
 
-    return cap;
+std::vector<FrameRate>
+VideoDevice::getRateList(const std::string& channel, VideoSize size) const
+{
+    return deviceImpl_->getRateList();
 }
 
 VideoDevice::~VideoDevice()
diff --git a/src/media/video/video_base.h b/src/media/video/video_base.h
index ff1a776e5a02914af596858b112e15633f654284..6b39000e543aac318976002bffc4164ec10240d3 100644
--- a/src/media/video/video_base.h
+++ b/src/media/video/video_base.h
@@ -50,8 +50,10 @@ namespace ring { namespace video {
 
 enum VideoPixelFormat {
     VIDEO_PIXFMT_BGRA = -1,
-    VIDEO_PIXFMT_YUYV422 = -2,
-    VIDEO_PIXFMT_RGBA = -3,
+    VIDEO_PIXFMT_YUV420P = -2,
+    VIDEO_PIXFMT_YUYV422 = -3,
+    VIDEO_PIXFMT_RGBA = -4,
+    VIDEO_PIXFMT_NV21 = -5,
 };
 
 template <typename T> class Observer;
diff --git a/src/media/video/video_device.h b/src/media/video/video_device.h
index 2216112133015c17f897c1d1f19e996d9afef491..125538adfd42f5046218b14097f13e51e17afe0f 100644
--- a/src/media/video/video_device.h
+++ b/src/media/video/video_device.h
@@ -190,7 +190,7 @@ private:
         FrameRate closest {0};
         double rate_val = 0;
         try {
-            rate_val = rate.empty() ? 0 : std::stod(rate);
+            rate_val = rate.empty() ? 0 : ring::stod(rate);
         } catch (...) {
             RING_WARN("Can't read framerate \"%s\"", rate.c_str());
         }
diff --git a/src/media/video/video_input.cpp b/src/media/video/video_input.cpp
index 69f3629a54463224fe632862d9426d238ee11423..ed702846448d88bf43093ec8b5ff5d9bdf957ca7 100644
--- a/src/media/video/video_input.cpp
+++ b/src/media/video/video_input.cpp
@@ -29,8 +29,12 @@
 #include "media_const.h"
 #include "manager.h"
 #include "client/videomanager.h"
+#include "client/ring_signal.h"
 #include "sinkclient.h"
 #include "logger.h"
+#include "media/media_buffer.h"
+
+#include <libavformat/avio.h>
 
 #include <string>
 #include <sstream>
@@ -45,16 +49,94 @@ static constexpr unsigned default_grab_height = 480;
 VideoInput::VideoInput()
     : VideoGenerator::VideoGenerator()
     , sink_ {Manager::instance().createSinkClient("local")}
+#ifndef __ANDROID__
     , loop_(std::bind(&VideoInput::setup, this),
             std::bind(&VideoInput::process, this),
             std::bind(&VideoInput::cleanup, this))
+#else
+    , loop_(std::bind(&VideoInput::setup, this),
+            std::bind(&VideoInput::processAndroid, this),
+            std::bind(&VideoInput::cleanupAndroid, this))
+    , mutex_(), frame_cv_(), buffers_(8)
+#endif
 {}
 
 VideoInput::~VideoInput()
 {
+#ifdef __ANDROID__
+    /* we need to stop the loop and notify the condition variable
+     * to unblock the process loop */
+    loop_.stop();
+    frame_cv_.notify_one();
+#endif
     loop_.join();
 }
 
+#ifdef __ANDROID__
+bool VideoInput::waitForBufferFull()
+{
+    for(auto& buffer : buffers_) {
+        if (buffer.status == BUFFER_FULL)
+            return true;
+    }
+
+    /* If the loop is stopped, returned true so we can quit the process loop */
+    return !isCapturing();
+}
+
+void VideoInput::processAndroid()
+{
+    foundDecOpts(decOpts_);
+
+    if (switchPending_.exchange(false)) {
+        RING_DBG("Switching input to '%s'", decOpts_.input.c_str());
+        if (decOpts_.input.empty()) {
+            loop_.stop();
+            return;
+        }
+
+        emitSignal<DRing::VideoSignal::StopCapture>();
+        emitSignal<DRing::VideoSignal::StartCapture>(decOpts_.input);
+    }
+
+    std::unique_lock<std::mutex> lck(mutex_);
+
+    frame_cv_.wait(lck, [this] { return waitForBufferFull(); });
+    for (auto& buffer : buffers_) {
+        if (buffer.status == BUFFER_FULL && buffer.index == publish_index_) {
+            auto& frame = getNewFrame();
+            int format = getPixelFormat();
+
+            buffer.status = BUFFER_PUBLISHED;
+            frame.setFromMemory((uint8_t*)buffer.data, format, decOpts_.width, decOpts_.height,
+                                std::bind(&VideoInput::releaseBufferCb, this));
+            publish_index_++;
+            lck.unlock();
+            publishFrame();
+            break;
+        }
+    }
+}
+
+void VideoInput::cleanupAndroid()
+{
+    emitSignal<DRing::VideoSignal::StopCapture>();
+
+    if (detach(sink_.get()))
+        sink_->stop();
+
+    std::lock_guard<std::mutex> lck(mutex_);
+    for (auto& buffer : buffers_) {
+        if (buffer.status == BUFFER_AVAILABLE ||
+            buffer.status == BUFFER_FULL) {
+            freeOneBuffer(buffer);
+        } else if (buffer.status != BUFFER_NOT_ALLOCATED) {
+            RING_ERR("Failed to free buffer [%p]", buffer.data);
+        }
+    }
+}
+#endif
+
 bool VideoInput::setup()
 {
     if (not attach(sink_.get())) {
@@ -134,6 +216,92 @@ bool VideoInput::captureFrame()
     }
 }
 
+#ifdef __ANDROID__
+int VideoInput::allocateOneBuffer(struct VideoFrameBuffer& b, int length)
+{
+    b.data = std::malloc(length);
+    if (b.data) {
+        b.status = BUFFER_AVAILABLE;
+        b.length = length;
+        RING_DBG("Allocated buffer [%p]", b.data);
+        return 0;
+    }
+
+    RING_DBG("Failed to allocate memory for one buffer");
+    return -ENOMEM;
+}
+
+void VideoInput::freeOneBuffer(struct VideoFrameBuffer& b)
+{
+    RING_DBG("Free buffer [%p]", b.data);
+    std::free(b.data);
+    b.data = nullptr;
+    b.length = 0;
+    b.status = BUFFER_NOT_ALLOCATED;
+}
+
+void VideoInput::releaseBufferCb(void *ptr)
+{
+    std::lock_guard<std::mutex> lck(mutex_);
+
+    for(auto &buffer : buffers_) {
+        if (buffer.data == ptr) {
+            buffer.status = BUFFER_AVAILABLE;
+            if (!isCapturing())
+                freeOneBuffer(buffer);
+            break;
+        }
+    }
+}
+
+void*
+VideoInput::obtainFrame(int length)
+{
+    std::lock_guard<std::mutex> lck(mutex_);
+
+    /* allocate buffers. This is done there because it's only when the Android
+     * application requests a buffer that we know its size
+     */
+    for(auto& buffer : buffers_) {
+        if (buffer.status == BUFFER_NOT_ALLOCATED) {
+            allocateOneBuffer(buffer, length);
+        }
+    }
+
+    /* search for an available frame */
+    for(auto& buffer : buffers_) {
+        if (buffer.length == length && buffer.status == BUFFER_AVAILABLE) {
+            buffer.status = BUFFER_CAPTURING;
+            return buffer.data;
+        }
+    }
+
+    RING_WARN("No buffer found");
+    return nullptr;
+}
+
+void
+VideoInput::releaseFrame(void *ptr)
+{
+    std::lock_guard<std::mutex> lck(mutex_);
+    for(auto& buffer : buffers_) {
+        if (buffer.data  == ptr) {
+            if (buffer.status != BUFFER_CAPTURING)
+                RING_ERR("Released a buffer with status %d, expected %d",
+                         buffer.status, BUFFER_CAPTURING);
+            if (isCapturing()) {
+                buffer.status = BUFFER_FULL;
+                buffer.index = capture_index_++;
+                frame_cv_.notify_one();
+            } else {
+                freeOneBuffer(buffer);
+            }
+            break;
+        }
+    }
+}
+#endif
+
 void
 VideoInput::createDecoder()
 {
@@ -360,6 +528,23 @@ VideoInput::switchInput(const std::string& resource)
     return futureDecOpts_;
 }
 
+#ifdef __ANDROID__
+int VideoInput::getWidth() const
+{ return decOpts_.width; }
+
+int VideoInput::getHeight() const
+{ return decOpts_.height; }
+
+int VideoInput::getPixelFormat() const
+{
+    int format;
+    std::stringstream ss;
+    ss << decOpts_.format;
+    ss >> format;
+
+    return format;
+}
+#else
 int VideoInput::getWidth() const
 { return decoder_->getWidth(); }
 
@@ -368,6 +553,7 @@ int VideoInput::getHeight() const
 
 int VideoInput::getPixelFormat() const
 { return decoder_->getPixelFormat(); }
+#endif
 
 DeviceParams VideoInput::getParams() const
 { return decOpts_; }
diff --git a/src/media/video/video_input.h b/src/media/video/video_input.h
index 740b5c7f8864f04ddd4d67f4b3340fbfc322afd9..84692b9fe90c09e7690346ed48e1dd46a6810997 100644
--- a/src/media/video/video_input.h
+++ b/src/media/video/video_input.h
@@ -32,6 +32,8 @@
 #include <atomic>
 #include <future>
 #include <string>
+#include <mutex>
+#include <condition_variable>
 
 namespace ring {
 class MediaDecoder;
@@ -41,6 +43,24 @@ namespace ring { namespace video {
 
 class SinkClient;
 
+enum VideoFrameStatus {
+    BUFFER_NOT_ALLOCATED,
+    BUFFER_AVAILABLE,       /* owned by us */
+    BUFFER_CAPTURING,       /* owned by Android Java Application */
+    BUFFER_FULL,            /* owned by us again */
+    BUFFER_PUBLISHED,       /* owned by libav */
+};
+
+struct VideoFrameBuffer {
+    void                    *data;
+    size_t                  length;
+    enum VideoFrameStatus   status;
+    int                     index;
+
+    VideoFrameBuffer() : data(nullptr), length(0),
+                         status(BUFFER_NOT_ALLOCATED), index(0) {}
+};
+
 class VideoInput : public VideoGenerator
 {
 public:
@@ -54,6 +74,14 @@ public:
     DeviceParams getParams() const;
 
     std::shared_future<DeviceParams> switchInput(const std::string& resource);
+#ifdef __ANDROID__
+    /*
+     * these fonctions are used to pass buffer from/to the daemon
+     * to the Java application
+     */
+    void* obtainFrame(int length);
+    void releaseFrame(void *frame);
+#endif
 
 private:
     NON_COPYABLE(VideoInput);
@@ -91,6 +119,23 @@ private:
 
     bool captureFrame();
     bool isCapturing() const noexcept;
+
+#ifdef __ANDROID__
+    void processAndroid();
+    void cleanupAndroid();
+    int allocateOneBuffer(struct VideoFrameBuffer& b, int length);
+    void freeOneBuffer(struct VideoFrameBuffer& b);
+    bool waitForBufferFull();
+
+    std::mutex mutex_;
+    std::condition_variable frame_cv_;
+    int capture_index_ = 0;
+    int publish_index_ = 0;
+
+    /* Get notified when libav is done with this buffer */
+    static void releaseBufferCb(void *opaque, void *ptr);
+    std::vector<struct VideoFrameBuffer> buffers_;
+#endif
 };
 
 }} // namespace ring::video
diff --git a/src/string_utils.h b/src/string_utils.h
index 4072bb10fdf306ee850f2baf0efd83786f7398de..fbd677816bd80783ee226a694e950b17d156ccba 100644
--- a/src/string_utils.h
+++ b/src/string_utils.h
@@ -67,6 +67,15 @@ stoi(const std::string& str)
     return v;
 }
 
+static inline double
+stod(const std::string& str)
+{
+    double v;
+    std::istringstream os(str);
+    os >> v;
+    return v;
+}
+
 #else
 
 template <typename T>
@@ -82,6 +91,12 @@ stoi(const std::string& str)
     return std::stoi(str);
 }
 
+static inline double
+stod(const std::string& str)
+{
+    return std::stod(str);
+}
+
 #endif
 
 std::string trim(const std::string &s);