diff --git a/src/media/audio/audio_input.cpp b/src/media/audio/audio_input.cpp
index fcfe1874c5622b34243ab5065a389a43f14b84f7..82528e4a63b127d89d622979ceba1dee39805e98 100644
--- a/src/media/audio/audio_input.cpp
+++ b/src/media/audio/audio_input.cpp
@@ -116,15 +116,23 @@ AudioInput::readFromFile()
 {
     if (!decoder_)
         return;
-    const auto ret = decoder_->decode();
-    switch (ret) {
-    case MediaDemuxer::Status::Success:
+
+    auto frame = std::make_unique<AudioFrame>();
+    const auto ret = decoder_->decode(*frame);
+    switch(ret) {
+    case MediaDecoder::Status::ReadError:
+    case MediaDecoder::Status::DecodeError:
+        JAMI_ERR() << "Failed to decode frame";
         break;
-    case MediaDemuxer::Status::EndOfFile:
+    case MediaDecoder::Status::RestartRequired:
+    case MediaDecoder::Status::EOFError:
         createDecoder();
         break;
-    case MediaDemuxer::Status::ReadError:
-        JAMI_ERR() << "Failed to decode frame";
+    case MediaDecoder::Status::FrameFinished:
+        fileBuf_->put(std::move(frame));
+        break;
+    case MediaDecoder::Status::Success:
+    default:
         break;
     }
 }
@@ -239,14 +247,11 @@ AudioInput::createDecoder()
         return false;
     }
 
-    auto decoder = std::make_unique<MediaDecoder>([this](std::shared_ptr<MediaFrame>&& frame) {
-        fileBuf_->put(std::move(std::static_pointer_cast<AudioFrame>(frame)));
-    });
-
     // NOTE don't emulate rate, file is read as frames are needed
-
+    auto decoder = std::make_unique<MediaDecoder>();
     decoder->setInterruptCallback(
-        [](void* data) -> int { return not static_cast<AudioInput*>(data)->isCapturing(); }, this);
+        [](void* data) -> int { return not static_cast<AudioInput*>(data)->isCapturing(); },
+        this);
 
     if (decoder->openInput(devOpts_) < 0) {
         JAMI_ERR() << "Could not open input '" << devOpts_.input << "'";
@@ -254,7 +259,7 @@ AudioInput::createDecoder()
         return false;
     }
 
-    if (decoder->setupAudio() < 0) {
+    if (decoder->setupFromAudioData() < 0) {
         JAMI_ERR() << "Could not setup decoder for '" << devOpts_.input << "'";
         foundDevOpts(devOpts_);
         return false;
diff --git a/src/media/audio/audio_receive_thread.cpp b/src/media/audio/audio_receive_thread.cpp
index e2c3ca0df70f78eb4656fcdafa5385f7b71e154d..ffd765e3c21d2b24b8c368954de9aeb9a4f23f69 100644
--- a/src/media/audio/audio_receive_thread.cpp
+++ b/src/media/audio/audio_receive_thread.cpp
@@ -57,10 +57,7 @@ AudioReceiveThread::~AudioReceiveThread()
 bool
 AudioReceiveThread::setup()
 {
-    audioDecoder_.reset(new MediaDecoder([this](std::shared_ptr<MediaFrame>&& frame) mutable {
-        notify(frame);
-        ringbuffer_->put(std::move(std::static_pointer_cast<AudioFrame>(frame)));
-    }));
+    audioDecoder_.reset(new MediaDecoder());
     audioDecoder_->setInterruptCallback(interruptCb, this);
 
     // custom_io so the SDP demuxer will not open any UDP connections
@@ -81,10 +78,11 @@ AudioReceiveThread::setup()
 
     // Now replace our custom AVIOContext with one that will read packets
     audioDecoder_->setIOContext(demuxContext_.get());
-    if (audioDecoder_->setupAudio()) {
+    if (audioDecoder_->setupFromAudioData()) {
         JAMI_ERR("decoder IO startup failed");
         return false;
     }
+
     Smartools::getInstance().setRemoteAudioCodec(audioDecoder_->getDecoderName());
 
     ringbuffer_ = Manager::instance().getRingBufferPool().getRingBuffer(id_);
@@ -94,7 +92,31 @@ AudioReceiveThread::setup()
 void
 AudioReceiveThread::process()
 {
-    audioDecoder_->decode();
+    auto decodedFrame = std::make_shared<AudioFrame>();
+    switch (audioDecoder_->decode(*decodedFrame)) {
+        case MediaDecoder::Status::FrameFinished:
+            notify(std::static_pointer_cast<MediaFrame>(decodedFrame));
+            ringbuffer_->put(std::move(decodedFrame));
+            return;
+        case MediaDecoder::Status::DecodeError:
+            JAMI_WARN("decoding failure, trying to reset decoder...");
+            if (not setup()) {
+                JAMI_ERR("fatal error, rx thread re-setup failed");
+                loop_.stop();
+            } else if (not audioDecoder_->setupFromAudioData()) {
+                JAMI_ERR("fatal error, a-decoder setup failed");
+                loop_.stop();
+            }
+            break;
+        case MediaDecoder::Status::ReadError:
+            JAMI_ERR("fatal error, read failed");
+            loop_.stop();
+            break;
+        case MediaDecoder::Status::Success:
+        case MediaDecoder::Status::EOFError:
+        default:
+            break;
+    }
 }
 
 void
diff --git a/src/media/audio/audiobuffer.cpp b/src/media/audio/audiobuffer.cpp
index 836dd30c1f30ddd2c3f37323b65ada84f34605d3..acbf4b25de3a334c95655a6edf890d8fd1b5068e 100644
--- a/src/media/audio/audiobuffer.cpp
+++ b/src/media/audio/audiobuffer.cpp
@@ -317,14 +317,14 @@ AudioBuffer::append(const AudioFrame& audioFrame)
         setFormat(newFormat);
     }
 
-    auto f = frames();
-    auto newSize = f + frame->nb_samples;
-    resize(newSize);
+    AudioBuffer toAppend(frame->nb_samples,
+        {(unsigned)frame->sample_rate, (unsigned)frame->channels});
+    toAppend.deinterleave(reinterpret_cast<const AudioSample*>(frame->extended_data[0]),
+        frame->nb_samples, frame->channels);
 
-    auto in = reinterpret_cast<const AudioSample*>(frame->extended_data[0]);
-    for (unsigned c=channels(); f < newSize; f++)
-        for (unsigned j = 0; j < c; j++)
-            samples_[j][f] = *in++;
+    for (size_t c = 0; c < samples_.size(); ++c) {
+        samples_[c].insert(samples_[c].end(), toAppend.samples_[c].begin(), toAppend.samples_[c].end());
+    }
 
     return 0;
 }
diff --git a/src/media/audio/sound/audiofile.cpp b/src/media/audio/sound/audiofile.cpp
index 2fe8bc2f6620374684b7ed79fb6f8e095a75bcec..7c177f5e25cc487145a4502e6ea68176caa9ef5e 100644
--- a/src/media/audio/sound/audiofile.cpp
+++ b/src/media/audio/sound/audiofile.cpp
@@ -59,22 +59,46 @@ AudioFile::onBufferFinish()
 AudioFile::AudioFile(const std::string &fileName, unsigned int sampleRate) :
     AudioLoop(sampleRate), filepath_(fileName), updatePlaybackScale_(0)
 {
-    const auto& format = getFormat();
-    auto buf = std::make_unique<AudioBuffer>(0, format);
-    Resampler r {};
-    auto decoder = std::make_unique<MediaDecoder>([this, &r, &format, &buf](const std::shared_ptr<MediaFrame>& frame) mutable {
-        buf->append(*r.resample(std::static_pointer_cast<AudioFrame>(frame), format));
-    });
+    auto decoder = std::make_unique<MediaDecoder>();
     DeviceParams dev;
     dev.input = fileName;
 
     if (decoder->openInput(dev) < 0)
         throw AudioFileException("File could not be opened: " + fileName);
 
-    if (decoder->setupAudio() < 0)
+    if (decoder->setupFromAudioData() < 0)
         throw AudioFileException("Decoder setup failed: " + fileName);
 
-    while (decoder->decode() != MediaDemuxer::Status::EndOfFile);
+    auto resampler = std::make_unique<Resampler>();
+    const auto& format = getFormat();
+    auto buf = std::make_unique<AudioBuffer>(0, format);
+    bool done = false;
+    while (!done) {
+        AudioFrame input;
+        AudioFrame output;
+        auto resampled = output.pointer();
+        switch (decoder->decode(input)) {
+        case MediaDecoder::Status::FrameFinished:
+            resampled->sample_rate = format.sample_rate;
+            resampled->channel_layout = av_get_default_channel_layout(format.nb_channels);
+            resampled->channels = format.nb_channels;
+            resampled->format = format.sampleFormat;
+            if (resampler->resample(input.pointer(), resampled) < 0)
+                throw AudioFileException("Frame could not be resampled");
+            if (buf->append(output) < 0)
+                throw AudioFileException("Error while decoding: " + fileName);
+            break;
+        case MediaDecoder::Status::DecodeError:
+        case MediaDecoder::Status::ReadError:
+            throw AudioFileException("File cannot be decoded: " + fileName);
+        case MediaDecoder::Status::EOFError:
+            done = true;
+            break;
+        case MediaDecoder::Status::Success:
+        default:
+            break;
+        }
+    }
 
     delete buffer_;
     buffer_ = buf.release();
diff --git a/src/media/media_buffer.h b/src/media/media_buffer.h
index 3911c2eb2a1df1ed13fb9b8ce0c43af7592875e3..016160857c280e7f08c337ef2cb7e833fdc034d7 100644
--- a/src/media/media_buffer.h
+++ b/src/media/media_buffer.h
@@ -22,7 +22,6 @@
 
 #include "config.h"
 #include "videomanager_interface.h"
-#include "observer.h"
 
 #include <memory>
 #include <functional>
@@ -39,7 +38,6 @@ namespace jami {
 
 using MediaFrame = DRing::MediaFrame;
 using AudioFrame = DRing::AudioFrame;
-using MediaObserver = std::function<void(std::shared_ptr<MediaFrame>&&)>;
 
 #ifdef ENABLE_VIDEO
 
diff --git a/src/media/media_decoder.cpp b/src/media/media_decoder.cpp
index 62740967a31760ee7b032435e49cf8eb143014c1..bd8c456dac9765de68c3ee4440435722585526c0 100644
--- a/src/media/media_decoder.cpp
+++ b/src/media/media_decoder.cpp
@@ -3,7 +3,6 @@
  *
  *  Author: Guillaume Roguez <Guillaume.Roguez@savoirfairelinux.com>
  *  Author: Philippe Gorley <philippe.gorley@savoirfairelinux.com>
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License as published by
@@ -54,20 +53,23 @@ const constexpr auto jitterBufferMaxDelay_ = std::chrono::milliseconds(50);
 // maximum number of times accelerated decoding can fail in a row before falling back to software
 const constexpr unsigned MAX_ACCEL_FAILURES { 5 };
 
-MediaDemuxer::MediaDemuxer() :
+MediaDecoder::MediaDecoder() :
     inputCtx_(avformat_alloc_context()),
     startTime_(AV_NOPTS_VALUE)
-{}
+{
+}
 
-MediaDemuxer::~MediaDemuxer()
+MediaDecoder::~MediaDecoder()
 {
+    if (decoderCtx_)
+        avcodec_free_context(&decoderCtx_);
     if (inputCtx_)
         avformat_close_input(&inputCtx_);
+
     av_dict_free(&options_);
 }
 
-int
-MediaDemuxer::openInput(const DeviceParams& params)
+int MediaDecoder::openInput(const DeviceParams& params)
 {
     inputParams_ = params;
     AVInputFormat *iformat = av_find_input_format(params.format.c_str());
@@ -121,6 +123,12 @@ MediaDemuxer::openInput(const DeviceParams& params)
     JAMI_DBG("Trying to open device %s with format %s, pixel format %s, size %dx%d, rate %lf", params.input.c_str(),
                                                         params.format.c_str(), params.pixel_format.c_str(), params.width, params.height, params.framerate.real());
 
+#ifdef RING_ACCEL
+    // if there was a fallback to software decoding, do not enable accel
+    // it has been disabled already by the video_receive_thread/video_input
+    enableAccel_ &= Manager::instance().videoPreferences.getDecodingAccelerated();
+#endif
+
     int ret = avformat_open_input(
         &inputCtx_,
         params.input.c_str(),
@@ -136,26 +144,7 @@ MediaDemuxer::openInput(const DeviceParams& params)
     return ret;
 }
 
-void
-MediaDemuxer::findStreamInfo()
-{
-    if (not streamInfoFound_) {
-        inputCtx_->max_analyze_duration = 30 * AV_TIME_BASE;
-        int err;
-        if ((err = avformat_find_stream_info(inputCtx_, nullptr)) < 0) {
-            JAMI_ERR() << "Could not find stream info: " << libav_utils::getError(err);
-        }
-        streamInfoFound_ = true;
-    }
-}
-
-int
-MediaDemuxer::selectStream(AVMediaType type)
-{
-    return av_find_best_stream(inputCtx_, type, -1, -1, nullptr, 0);
-}
-
-void MediaDemuxer::setInterruptCallback(int (*cb)(void*), void *opaque)
+void MediaDecoder::setInterruptCallback(int (*cb)(void*), void *opaque)
 {
     if (cb) {
         inputCtx_->interrupt_callback.callback = cb;
@@ -165,104 +154,48 @@ void MediaDemuxer::setInterruptCallback(int (*cb)(void*), void *opaque)
     }
 }
 
-void MediaDemuxer::setIOContext(MediaIOHandle *ioctx)
+void MediaDecoder::setIOContext(MediaIOHandle *ioctx)
 { inputCtx_->pb = ioctx->getContext(); }
 
-
-MediaDemuxer::Status
-MediaDemuxer::decode()
-{
-    auto packet = std::unique_ptr<AVPacket, std::function<void(AVPacket*)>>(av_packet_alloc(), [](AVPacket* p){
-        if (p)
-            av_packet_free(&p);
-    });
-
-    int ret = av_read_frame(inputCtx_, packet.get());
-    if (ret == AVERROR(EAGAIN)) {
-        return Status::Success;
-    } else if (ret == AVERROR_EOF) {
-        return Status::EndOfFile;
-    } else if (ret < 0) {
-        JAMI_ERR("Couldn't read frame: %s\n", libav_utils::getError(ret).c_str());
-        return Status::ReadError;
-    }
-
-    auto streamIndex = packet->stream_index;
-    if (static_cast<unsigned>(streamIndex) >= streams_.size() || streamIndex < 0) {
-        return Status::Success;
-    }
-
-    auto& cb = streams_[streamIndex];
-    if (cb)
-        cb(*packet.get());
-    return Status::Success;
-}
-
-MediaDecoder::MediaDecoder(const std::shared_ptr<MediaDemuxer>& demuxer, int index)
- : demuxer_(demuxer),
-   avStream_(demuxer->getStream(index))
+int MediaDecoder::setupFromAudioData()
 {
-    demuxer->setStreamCallback(index, [this](AVPacket& packet) {
-        decode(packet);
-    });
-    setupStream();
+    return setupStream(AVMEDIA_TYPE_AUDIO);
 }
 
-MediaDecoder::MediaDecoder()
- : demuxer_(new MediaDemuxer)
- {}
-
-MediaDecoder::MediaDecoder(MediaObserver o)
- : demuxer_(new MediaDemuxer), callback_(std::move(o))
- {}
-
-MediaDecoder::~MediaDecoder()
+int
+MediaDecoder::selectStream(AVMediaType type)
 {
-#ifdef RING_ACCEL
-    if (decoderCtx_ && decoderCtx_->hw_device_ctx)
-        av_buffer_unref(&decoderCtx_->hw_device_ctx);
-#endif
-    if (decoderCtx_)
-        avcodec_free_context(&decoderCtx_);
+    return av_find_best_stream(inputCtx_, type, -1, -1, &inputDecoder_, 0);
 }
 
 int
-MediaDecoder::openInput(const DeviceParams& p)
+MediaDecoder::setupStream(AVMediaType mediaType)
 {
-    return demuxer_->openInput(p);
-}
+    int ret = 0;
+    std::string streamType = av_get_media_type_string(mediaType);
 
-void
-MediaDecoder::setInterruptCallback(int (*cb)(void*), void *opaque)
-{
-    demuxer_->setInterruptCallback(cb, opaque);
-}
+    avcodec_free_context(&decoderCtx_);
 
-void
-MediaDecoder::setIOContext(MediaIOHandle *ioctx)
-{
-    demuxer_->setIOContext(ioctx);
-}
+    // Increase analyze time to solve synchronization issues between callers.
+    static const unsigned MAX_ANALYZE_DURATION = 30;
+    inputCtx_->max_analyze_duration = MAX_ANALYZE_DURATION * AV_TIME_BASE;
+
+    // If fallback from accel, don't check for stream info, it's already done
+    if (!fallback_) {
+        JAMI_DBG() << "Finding " << streamType << " stream info";
+        if ((ret = avformat_find_stream_info(inputCtx_, nullptr)) < 0) {
+            // Always fail here
+            JAMI_ERR() << "Could not find " << streamType << " stream info: " << libav_utils::getError(ret);
+            return -1;
+        }
+    }
 
-int MediaDecoder::setup(AVMediaType type)
-{
-    demuxer_->findStreamInfo();
-    auto stream = demuxer_->selectStream(type);
-    if (stream < 0)
+    if ((streamIndex_ = selectStream(mediaType)) < 0) {
+        JAMI_ERR() << "No suitable " << streamType << " stream found";
         return -1;
-    avStream_ = demuxer_->getStream(stream);
-    demuxer_->setStreamCallback(stream, [this](AVPacket& packet) {
-        decode(packet);
-    });
-    return setupStream();
-}
-
-int
-MediaDecoder::setupStream()
-{
-    int ret = 0;
-    avcodec_free_context(&decoderCtx_);
+    }
 
+    avStream_ = inputCtx_->streams[streamIndex_];
     inputDecoder_ = findDecoder(avStream_->codecpar->codec_id);
     if (!inputDecoder_) {
         JAMI_ERR() << "Unsupported codec";
@@ -276,15 +209,23 @@ MediaDecoder::setupStream()
     }
     avcodec_parameters_to_context(decoderCtx_, avStream_->codecpar);
     decoderCtx_->framerate = avStream_->avg_frame_rate;
-    if (avStream_->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
+    if (mediaType == AVMEDIA_TYPE_VIDEO) {
         if (decoderCtx_->framerate.num == 0 || decoderCtx_->framerate.den == 0)
             decoderCtx_->framerate = inputParams_.framerate;
         if (decoderCtx_->framerate.num == 0 || decoderCtx_->framerate.den == 0)
             decoderCtx_->framerate = av_inv_q(decoderCtx_->time_base);
         if (decoderCtx_->framerate.num == 0 || decoderCtx_->framerate.den == 0)
             decoderCtx_->framerate = {30, 1};
+    }
+
+    decoderCtx_->thread_count = std::max(1u, std::min(8u, std::thread::hardware_concurrency()/2));
+
+    if (emulateRate_)
+        JAMI_DBG() << "Using framerate emulation";
+    startTime_ = av_gettime(); // used to set pts after decoding, and for rate emulation
 
 #ifdef RING_ACCEL
+    if (mediaType == AVMEDIA_TYPE_VIDEO) {
         if (enableAccel_) {
             accel_ = video::HardwareAccel::setupDecoder(decoderCtx_->codec_id,
                 decoderCtx_->width, decoderCtx_->height);
@@ -297,16 +238,10 @@ MediaDecoder::setupStream()
         } else {
             JAMI_WARN() << "Hardware decoding disabled by user preference";
         }
-#endif
     }
+#endif
 
-    decoderCtx_->thread_count = std::max(1u, std::min(8u, std::thread::hardware_concurrency()/2));
-
-    if (emulateRate_)
-        JAMI_DBG() << "Using framerate emulation";
-    startTime_ = av_gettime(); // used to set pts after decoding, and for rate emulation
-
-    JAMI_DBG() << "Decoding using " << inputDecoder_->long_name << " (" << inputDecoder_->name << ")";
+    JAMI_DBG() << "Decoding " << streamType << " using " << inputDecoder_->long_name << " (" << inputDecoder_->name << ")";
     decoderCtx_->err_recognition = (AV_EF_CRCCHECK | AV_EF_BITSTREAM | AV_EF_BUFFER | AV_EF_EXPLODE | AV_EF_CAREFUL | AV_EF_COMPLIANT | AV_EF_AGGRESSIVE);
     ret = avcodec_open2(decoderCtx_, inputDecoder_, nullptr);
     if (ret < 0) {
@@ -317,17 +252,39 @@ MediaDecoder::setupStream()
     return 0;
 }
 
+#ifdef ENABLE_VIDEO
+int MediaDecoder::setupFromVideoData()
+{
+    return setupStream(AVMEDIA_TYPE_VIDEO);
+}
+
 MediaDecoder::Status
-MediaDecoder::decode(AVPacket& packet)
+MediaDecoder::decode(VideoFrame& result)
 {
+    AVPacket inpacket;
+    av_init_packet(&inpacket);
+    int ret = av_read_frame(inputCtx_, &inpacket);
+    if (ret == AVERROR(EAGAIN)) {
+        return Status::Success;
+    } else if (ret == AVERROR_EOF) {
+        return Status::EOFError;
+    } else if (ret < 0) {
+        JAMI_ERR("Couldn't read frame: %s\n", libav_utils::getError(ret).c_str());
+        return Status::ReadError;
+    }
+
+    // is this a packet from the video stream?
+    if (inpacket.stream_index != streamIndex_) {
+        av_packet_unref(&inpacket);
+        return Status::Success;
+    }
+
+    auto frame = result.pointer();
     int frameFinished = 0;
-    auto ret = avcodec_send_packet(decoderCtx_, &packet);
+    ret = avcodec_send_packet(decoderCtx_, &inpacket);
     if (ret < 0 && ret != AVERROR(EAGAIN)) {
         return ret == AVERROR_EOF ? Status::Success : Status::DecodeError;
     }
-
-    auto f = std::make_shared<MediaFrame>();
-    auto frame = f->pointer();
     ret = avcodec_receive_frame(decoderCtx_, frame);
     if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF) {
         return Status::DecodeError;
@@ -335,6 +292,8 @@ MediaDecoder::decode(AVPacket& packet)
     if (ret >= 0)
         frameFinished = 1;
 
+    av_packet_unref(&inpacket);
+
     if (frameFinished) {
         frame->format = (AVPixelFormat) correctPixFmt(frame->format);
         auto packetTimestamp = frame->pts; // in stream time base
@@ -351,18 +310,73 @@ MediaDecoder::decode(AVPacket& packet)
                 std::this_thread::sleep_for(std::chrono::microseconds(target - now));
             }
         }
-        if (callback_)
-            callback_(std::move(f));
         return Status::FrameFinished;
     }
 
     return Status::Success;
 }
+#endif // ENABLE_VIDEO
 
-MediaDemuxer::Status
-MediaDecoder::decode()
+MediaDecoder::Status
+MediaDecoder::decode(AudioFrame& decodedFrame)
 {
-    return demuxer_->decode();
+    const auto frame = decodedFrame.pointer();
+
+    AVPacket inpacket;
+    av_init_packet(&inpacket);
+
+   int ret = av_read_frame(inputCtx_, &inpacket);
+    if (ret == AVERROR(EAGAIN)) {
+        return Status::Success;
+    } else if (ret == AVERROR_EOF) {
+        return Status::EOFError;
+    } else if (ret < 0) {
+        JAMI_ERR("Couldn't read frame: %s\n", libav_utils::getError(ret).c_str());
+        return Status::ReadError;
+    }
+
+    // is this a packet from the audio stream?
+    if (inpacket.stream_index != streamIndex_) {
+        av_packet_unref(&inpacket);
+        return Status::Success;
+    }
+
+    int frameFinished = 0;
+        ret = avcodec_send_packet(decoderCtx_, &inpacket);
+        if (ret < 0 && ret != AVERROR(EAGAIN))
+            return ret == AVERROR_EOF ? Status::Success : Status::DecodeError;
+
+    ret = avcodec_receive_frame(decoderCtx_, frame);
+    if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF)
+        return Status::DecodeError;
+    if (ret >= 0)
+        frameFinished = 1;
+
+    if (frameFinished) {
+        av_packet_unref(&inpacket);
+
+        // channel layout is needed if frame will be resampled
+        if (!frame->channel_layout)
+            frame->channel_layout = av_get_default_channel_layout(frame->channels);
+
+        auto packetTimestamp = frame->pts;
+        // NOTE don't use clock to rescale audio pts, it may create artifacts
+        frame->pts = av_rescale_q_rnd(frame->pts, avStream_->time_base, decoderCtx_->time_base,
+            static_cast<AVRounding>(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
+        lastTimestamp_ = frame->pts;
+
+        if (emulateRate_ and packetTimestamp != AV_NOPTS_VALUE) {
+            auto frame_time = getTimeBase()*(packetTimestamp - avStream_->start_time);
+            auto target = startTime_ + static_cast<std::int64_t>(frame_time.real() * 1e6);
+            auto now = av_gettime();
+            if (target > now) {
+                std::this_thread::sleep_for(std::chrono::microseconds(target - now));
+            }
+        }
+        return Status::FrameFinished;
+    }
+
+    return Status::Success;
 }
 
 #ifdef ENABLE_VIDEO
@@ -381,7 +395,7 @@ MediaDecoder::enableAccel(bool enableAccel)
 #endif
 
 MediaDecoder::Status
-MediaDecoder::flush()
+MediaDecoder::flush(VideoFrame& result)
 {
     AVPacket inpacket;
     av_init_packet(&inpacket);
@@ -392,8 +406,7 @@ MediaDecoder::flush()
     if (ret < 0 && ret != AVERROR(EAGAIN))
         return ret == AVERROR_EOF ? Status::Success : Status::DecodeError;
 
-    auto result = std::make_shared<MediaFrame>();
-    ret = avcodec_receive_frame(decoderCtx_, result->pointer());
+    ret = avcodec_receive_frame(decoderCtx_, result.pointer());
     if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF)
         return Status::DecodeError;
     if (ret >= 0)
@@ -401,9 +414,7 @@ MediaDecoder::flush()
 
     if (frameFinished) {
         av_packet_unref(&inpacket);
-        if (callback_)
-            callback_(std::move(result));
-        return Status::FrameFinished;
+        return Status::EOFError;
     }
 
     return Status::Success;
diff --git a/src/media/media_decoder.h b/src/media/media_decoder.h
index 619fdde94552ea99eedaa9e3feca7d0900f4b315..70cc790fe2ed582c7abfaddee57df105618e4b75 100644
--- a/src/media/media_decoder.h
+++ b/src/media/media_decoder.h
@@ -22,22 +22,17 @@
 #include "config.h"
 
 #include "rational.h"
-#include "observer.h"
 
 #ifdef ENABLE_VIDEO
 #include "video/video_base.h"
 #include "video/video_scaler.h"
 #endif // ENABLE_VIDEO
 
-#ifdef RING_ACCEL
-#include "video/accel.h"
-#endif
-#include "logger.h"
-
 #include "audio/audiobuffer.h"
 
 #include "media_device.h"
 #include "media_stream.h"
+#include "rational.h"
 #include "noncopyable.h"
 
 #include <map>
@@ -65,128 +60,85 @@ struct AudioFormat;
 class RingBuffer;
 class Resampler;
 class MediaIOHandle;
-class MediaDecoder;
-
-class MediaDemuxer {
-public:
-    MediaDemuxer();
-    ~MediaDemuxer();
-
-    enum class Status {
-        Success,
-        EndOfFile,
-        ReadError
-    };
-    using StreamCallback = std::function<void(AVPacket&)>;
-
-    int openInput(const DeviceParams&);
-
-    void setInterruptCallback(int (*cb)(void*), void *opaque);
-    void setIOContext(MediaIOHandle *ioctx);
-
-    void findStreamInfo();
-    int selectStream(AVMediaType type);
-
-    void setStreamCallback(unsigned stream, StreamCallback cb = {}) {
-        if (streams_.size() <= stream)
-            streams_.resize(stream + 1);
-        streams_[stream] = std::move(cb);
-    }
-
-    AVStream* getStream(unsigned stream) {
-        if (stream >= inputCtx_->nb_streams)
-            throw std::invalid_argument("Invalid stream index");
-        return inputCtx_->streams[stream];
-    }
-
-    Status decode();
-
-private:
-    bool streamInfoFound_ {false};
-    AVFormatContext *inputCtx_ = nullptr;
-    std::vector<StreamCallback> streams_;
-    int64_t startTime_;
-    DeviceParams inputParams_;
-    AVDictionary *options_ = nullptr;
-};
 
-class MediaDecoder
-{
-public:
-    enum class Status {
-        Success,
-        FrameFinished,
-        EndOfFile,
-        ReadError,
-        DecodeError,
-        RestartRequired
-    };
-
-    MediaDecoder();
-    MediaDecoder(MediaObserver observer);
-    MediaDecoder(const std::shared_ptr<MediaDemuxer>& demuxer, int index);
-    MediaDecoder(const std::shared_ptr<MediaDemuxer>& demuxer, AVMediaType type) : MediaDecoder(demuxer, demuxer->selectStream(type)) {}
-    ~MediaDecoder();
-
-    void emulateRate() { emulateRate_ = true; }
-
-    int openInput(const DeviceParams&);
-    void setInterruptCallback(int (*cb)(void*), void *opaque);
-    void setIOContext(MediaIOHandle *ioctx);
-
-    int setup(AVMediaType type);
-    int setupAudio() { return setup(AVMEDIA_TYPE_AUDIO); }
-    int setupVideo() { return setup(AVMEDIA_TYPE_VIDEO); }
-
-    MediaDemuxer::Status decode();
-    Status flush();
-
-    int getWidth() const;
-    int getHeight() const;
-    std::string getDecoderName() const;
-
-    rational<double> getFps() const;
-    AVPixelFormat getPixelFormat() const;
-
-    void setOptions(const std::map<std::string, std::string>& options);
 #ifdef RING_ACCEL
-    void enableAccel(bool enableAccel);
+namespace video {
+class HardwareAccel;
+}
 #endif
 
-    MediaStream getStream(std::string name = "") const;
+class MediaDecoder {
+    public:
+        enum class Status {
+            Success,
+            FrameFinished,
+            EOFError,
+            ReadError,
+            DecodeError,
+            RestartRequired
+        };
+
+        MediaDecoder();
+        ~MediaDecoder();
+
+        void emulateRate() { emulateRate_ = true; }
+        void setInterruptCallback(int (*cb)(void*), void *opaque);
+        int openInput(const DeviceParams&);
+
+        void setIOContext(MediaIOHandle *ioctx);
+#ifdef ENABLE_VIDEO
+        int setupFromVideoData();
+        Status decode(VideoFrame&);
+        Status flush(VideoFrame&);
+#endif // ENABLE_VIDEO
+
+        int setupFromAudioData();
+        Status decode(AudioFrame&);
+
+        int getWidth() const;
+        int getHeight() const;
+        std::string getDecoderName() const;
 
-private:
-    NON_COPYABLE(MediaDecoder);
+        rational<double> getFps() const;
+        AVPixelFormat getPixelFormat() const;
+
+        void setOptions(const std::map<std::string, std::string>& options);
+#ifdef RING_ACCEL
+        void enableAccel(bool enableAccel);
+#endif
 
-    Status decode(AVPacket&);
+        MediaStream getStream(std::string name = "") const;
 
-    rational<unsigned> getTimeBase() const;
+    private:
+        NON_COPYABLE(MediaDecoder);
 
-    std::shared_ptr<MediaDemuxer> demuxer_;
+        rational<unsigned> getTimeBase() const;
 
-    AVCodec *inputDecoder_ = nullptr;
-    AVCodecContext *decoderCtx_ = nullptr;
-    AVStream *avStream_ = nullptr;
-    bool emulateRate_ = false;
-    int64_t startTime_;
-    int64_t lastTimestamp_;
+        AVCodec *inputDecoder_ = nullptr;
+        AVCodecContext *decoderCtx_ = nullptr;
+        AVFormatContext *inputCtx_ = nullptr;
+        AVStream *avStream_ = nullptr;
+        int streamIndex_ = -1;
+        bool emulateRate_ = false;
+        int64_t startTime_;
+        int64_t lastTimestamp_{0};
 
-    DeviceParams inputParams_;
+        DeviceParams inputParams_;
 
-    int correctPixFmt(int input_pix_fmt);
-    int setupStream();
+        int correctPixFmt(int input_pix_fmt);
+        int setupStream(AVMediaType mediaType);
+        int selectStream(AVMediaType type);
 
-    bool fallback_ = false;
+        bool fallback_ = false;
 
 #ifdef RING_ACCEL
-    bool enableAccel_ = true;
-    std::unique_ptr<video::HardwareAccel> accel_;
-    unsigned short accelFailures_ = 0;
+        bool enableAccel_ = true;
+        std::unique_ptr<video::HardwareAccel> accel_;
+        unsigned short accelFailures_ = 0;
 #endif
-    MediaObserver callback_;
 
-protected:
-    AVDictionary *options_ = nullptr;
+    protected:
+        AVDictionary *options_ = nullptr;
 };
 
 } // namespace jami
diff --git a/src/media/video/video_base.cpp b/src/media/video/video_base.cpp
index 9404aa745021732428312de209191323f1f3eed1..94c306fa1d076ac384c12ac6ca1d466e1f7340f0 100644
--- a/src/media/video/video_base.cpp
+++ b/src/media/video/video_base.cpp
@@ -47,14 +47,6 @@ VideoGenerator::publishFrame()
     notify(std::static_pointer_cast<MediaFrame>(lastFrame_));
 }
 
-void
-VideoGenerator::publishFrame(std::shared_ptr<VideoFrame> frame)
-{
-    std::lock_guard<std::mutex> lk(mutex_);
-    lastFrame_ = std::move(frame);
-    notify(std::static_pointer_cast<MediaFrame>(lastFrame_));
-}
-
 void
 VideoGenerator::flushFrames()
 {
diff --git a/src/media/video/video_base.h b/src/media/video/video_base.h
index d4e2e19e71dcf7da6936dcecf82fae0a3ce77b4a..63935a354759168a20a7611d056eded10a1d848a 100644
--- a/src/media/video/video_base.h
+++ b/src/media/video/video_base.h
@@ -80,7 +80,6 @@ public:
     // getNewFrame and publishFrame must be called by the same thread only
     VideoFrame& getNewFrame();
     void publishFrame();
-    void publishFrame(std::shared_ptr<VideoFrame>);
     void flushFrames();
 
 private:
diff --git a/src/media/video/video_input.cpp b/src/media/video/video_input.cpp
index 14ac429f7d58d28d35cb83edd999d60d6b348fbc..83e99b8a5554bb6f8f5365aa318144e7bdd975df 100644
--- a/src/media/video/video_input.cpp
+++ b/src/media/video/video_input.cpp
@@ -228,18 +228,40 @@ VideoInput::isCapturing() const noexcept
 bool VideoInput::captureFrame()
 {
     // Return true if capture could continue, false if must be stop
+
     if (not decoder_)
         return false;
 
-    switch (decoder_->decode()) {
-    case MediaDemuxer::Status::EndOfFile:
-        createDecoder();
-        return static_cast<bool>(decoder_);
-    case MediaDemuxer::Status::ReadError:
-        JAMI_ERR() << "Failed to decode frame";
-        return false;
-    default:
-        return true;
+    auto& frame = getNewFrame();
+    const auto ret = decoder_->decode(frame);
+    switch (ret) {
+        case MediaDecoder::Status::ReadError:
+            return false;
+
+        // try to keep decoding
+        case MediaDecoder::Status::DecodeError:
+            return true;
+
+        case MediaDecoder::Status::RestartRequired:
+            createDecoder();
+#ifdef RING_ACCEL
+            JAMI_WARN("Disabling hardware decoding due to previous failure");
+            decoder_->enableAccel(false);
+#endif
+            return static_cast<bool>(decoder_);
+
+        // End of streamed file
+        case MediaDecoder::Status::EOFError:
+            createDecoder();
+            return static_cast<bool>(decoder_);
+
+        case MediaDecoder::Status::FrameFinished:
+            publishFrame();
+            return true;
+        // continue decoding
+        case MediaDecoder::Status::Success:
+        default:
+            return true;
     }
 }
 
@@ -341,9 +363,7 @@ VideoInput::createDecoder()
         return;
     }
 
-    auto decoder = std::make_unique<MediaDecoder>([this](const std::shared_ptr<MediaFrame>& frame) mutable {
-        publishFrame(std::static_pointer_cast<VideoFrame>(frame));
-    });
+    auto decoder = std::unique_ptr<MediaDecoder>(new MediaDecoder());
 
     if (emulateRate_)
         decoder->emulateRate();
@@ -359,7 +379,7 @@ VideoInput::createDecoder()
     }
 
     /* Data available, finish the decoding */
-    if (decoder->setupVideo() < 0) {
+    if (decoder->setupFromVideoData() < 0) {
         JAMI_ERR("decoder IO startup failed");
         foundDecOpts(decOpts_);
         return;
@@ -501,7 +521,7 @@ VideoInput::initFile(std::string path)
     DeviceParams p;
     p.input = path;
     auto dec = std::make_unique<MediaDecoder>();
-    if (dec->openInput(p) < 0 || dec->setupVideo() < 0) {
+    if (dec->openInput(p) < 0 || dec->setupFromVideoData() < 0) {
         return initCamera(jami::getVideoDeviceMonitor().getDefaultDevice());
     }
 
diff --git a/src/media/video/video_receive_thread.cpp b/src/media/video/video_receive_thread.cpp
index 476fe843ca802c2ce2cf65e63944b2aa719dd3d1..6ab47e4ad29f50b7f5a1b6e23b0570824e76b1ee 100644
--- a/src/media/video/video_receive_thread.cpp
+++ b/src/media/video/video_receive_thread.cpp
@@ -55,9 +55,9 @@ VideoReceiveThread::VideoReceiveThread(const std::string& id,
     , sink_ {Manager::instance().createSinkClient(id)}
     , mtu_(mtu)
     , rotation_(0)
-    , requestKeyFrameCallback_()
+    , requestKeyFrameCallback_(0)
     , loop_(std::bind(&VideoReceiveThread::setup, this),
-            std::bind(&VideoReceiveThread::decodeFrame, this),
+            std::bind(&VideoReceiveThread::process, this),
             std::bind(&VideoReceiveThread::cleanup, this))
 {}
 
@@ -76,11 +76,7 @@ VideoReceiveThread::startLoop()
 // main thread to block while this executes, so it happens in the video thread.
 bool VideoReceiveThread::setup()
 {
-    videoDecoder_.reset(new MediaDecoder([this](const std::shared_ptr<MediaFrame>& frame) mutable {
-        if (auto displayMatrix = displayMatrix_)
-            av_frame_new_side_data_from_buf(frame->pointer(), AV_FRAME_DATA_DISPLAYMATRIX, av_buffer_ref(displayMatrix.get()));
-        publishFrame(std::static_pointer_cast<VideoFrame>(frame));
-    }));
+    videoDecoder_.reset(new MediaDecoder());
 
     dstWidth_ = args_.width;
     dstHeight_ = args_.height;
@@ -124,7 +120,7 @@ bool VideoReceiveThread::setup()
     if (requestKeyFrameCallback_)
         requestKeyFrameCallback_();
 
-    if (videoDecoder_->setupVideo()) {
+    if (videoDecoder_->setupFromVideoData()) {
         JAMI_ERR("decoder IO startup failed");
         return false;
     }
@@ -153,6 +149,9 @@ bool VideoReceiveThread::setup()
     return true;
 }
 
+void VideoReceiveThread::process()
+{ decodeFrame(); }
+
 void VideoReceiveThread::cleanup()
 {
     detach(sink_.get());
@@ -186,15 +185,53 @@ void VideoReceiveThread::addIOContext(SocketPair& socketPair)
     demuxContext_.reset(socketPair.createIOContext(mtu_));
 }
 
-void VideoReceiveThread::decodeFrame()
+bool VideoReceiveThread::decodeFrame()
 {
-    auto status = videoDecoder_->decode();
-    if (status == MediaDemuxer::Status::EndOfFile ||
-        status == MediaDemuxer::Status::ReadError) {
-        loop_.stop();
+    auto& frame = getNewFrame();
+    const auto ret = videoDecoder_->decode(frame);
+
+    if (auto displayMatrix = displayMatrix_)
+        av_frame_new_side_data_from_buf(frame.pointer(), AV_FRAME_DATA_DISPLAYMATRIX, av_buffer_ref(displayMatrix.get()));
+
+    switch (ret) {
+        case MediaDecoder::Status::FrameFinished:
+            publishFrame();
+            return true;
+
+        case MediaDecoder::Status::DecodeError:
+            JAMI_WARN("video decoding failure");
+            if (requestKeyFrameCallback_)
+            {
+                auto keyFrameCheckTimer = std::chrono::steady_clock::now() - lastKeyFrameTime_;
+                if (keyFrameCheckTimer >= MS_BETWEEN_2_KEYFRAME_REQUEST)
+                {
+                    lastKeyFrameTime_ = std::chrono::steady_clock::now();
+                    requestKeyFrameCallback_();
+                }
+            }
+            break;
+
+        case MediaDecoder::Status::ReadError:
+            JAMI_ERR("fatal error, read failed");
+            loop_.stop();
+            break;
+
+        case MediaDecoder::Status::RestartRequired:
+            // disable accel, reset decoder's AVCodecContext
+#ifdef RING_ACCEL
+            videoDecoder_->enableAccel(false);
+#endif
+            videoDecoder_->setupFromVideoData();
+            break;
+        case MediaDecoder::Status::Success:
+        case MediaDecoder::Status::EOFError:
+            break;
     }
+
+    return false;
 }
 
+
 void VideoReceiveThread::enterConference()
 {
     if (!loop_.isRunning())
diff --git a/src/media/video/video_receive_thread.h b/src/media/video/video_receive_thread.h
index 8e85e50817dde64b0aaa006227113066474c5388..413c529857bc45ed9d61a4ee22ea95d4c1cb5221 100644
--- a/src/media/video/video_receive_thread.h
+++ b/src/media/video/video_receive_thread.h
@@ -94,7 +94,7 @@ private:
 
     std::function<void(void)> requestKeyFrameCallback_;
     void openDecoder();
-    void decodeFrame();
+    bool decodeFrame();
     static int interruptCb(void *ctx);
     static int readFunction(void *opaque, uint8_t *buf, int buf_size);
 
diff --git a/src/observer.h b/src/observer.h
index 7c7e5f8f821df1c06c89e617cab97f490cce2a37..a4b84a44f7e12f87c8c6f61b9fe72f790cde988f 100644
--- a/src/observer.h
+++ b/src/observer.h
@@ -28,7 +28,6 @@
 #include <memory>
 #include <set>
 #include <mutex>
-#include <functional>
 #include <ciso646> // fix windows compiler bug
 
 namespace jami {
@@ -43,7 +42,6 @@ class Observable
 {
 public:
     Observable() : observers_(), mutex_() {}
-
     virtual ~Observable() {
         std::lock_guard<std::mutex> lk(mutex_);
         for (auto& o : observers_)
@@ -98,17 +96,4 @@ public:
     virtual void detached(Observable<T>*) {};
 };
 
-
-template <typename T>
-class FuncObserver : public Observer<T>
-{
-public:
-    using F = std::function<void(const T&)>;
-    FuncObserver(F f) : f_(f) {};
-    virtual ~FuncObserver() {};
-    void update(Observable<T>*, const T& t) override { f_(t); }
-private:
-    F f_;
-};
-
 }; // namespace jami
diff --git a/test/unitTest/media/test_media_decoder.cpp b/test/unitTest/media/test_media_decoder.cpp
index c2181c0684c49d30ad9f6e9f37054e9c674ed8b7..e44755bd415929736df9ec3627e81b9b4a1e149b 100644
--- a/test/unitTest/media/test_media_decoder.cpp
+++ b/test/unitTest/media/test_media_decoder.cpp
@@ -61,6 +61,7 @@ MediaDecoderTest::setUp()
 {
     DRing::init(DRing::InitFlag(DRing::DRING_FLAG_DEBUG | DRing::DRING_FLAG_CONSOLE_LOG));
     libav_utils::ring_avcodec_init();
+    decoder_.reset(new MediaDecoder);
 }
 
 void
@@ -79,26 +80,28 @@ MediaDecoderTest::testAudioFile()
 
     writeWav();
 
-    decoder_.reset(new MediaDecoder([this](const std::shared_ptr<MediaFrame>&& f) mutable {
-        CPPUNIT_ASSERT(f->pointer()->sample_rate == decoder_->getStream().sampleRate);
-        CPPUNIT_ASSERT(f->pointer()->channels == decoder_->getStream().nbChannels);
-    }));
     DeviceParams dev;
     dev.input = filename_;
     CPPUNIT_ASSERT(decoder_->openInput(dev) >= 0);
-    CPPUNIT_ASSERT(decoder_->setupAudio() >= 0);
+    CPPUNIT_ASSERT(decoder_->setupFromAudioData() >= 0);
 
     bool done = false;
     while (!done) {
-        switch (decoder_->decode()) {
-        case MediaDemuxer::Status::ReadError:
+        AudioFrame frame;
+        switch (decoder_->decode(frame)) {
+        case MediaDecoder::Status::FrameFinished:
+            CPPUNIT_ASSERT(frame.pointer()->sample_rate == decoder_->getStream().sampleRate);
+            CPPUNIT_ASSERT(frame.pointer()->channels == decoder_->getStream().nbChannels);
+            break;
+        case MediaDecoder::Status::DecodeError:
+        case MediaDecoder::Status::ReadError:
             CPPUNIT_ASSERT_MESSAGE("Decode error", false);
             done = true;
             break;
-        case MediaDemuxer::Status::EndOfFile:
+        case MediaDecoder::Status::EOFError:
             done = true;
             break;
-        case MediaDemuxer::Status::Success:
+        case MediaDecoder::Status::Success:
         default:
             break;
         }