diff --git a/src/media/audio/Makefile.am b/src/media/audio/Makefile.am
index 904df3c81dd21b810d216da81cfff1ee435193b9..12bcbf21897559a05627297fe535d6e8d148ab1f 100644
--- a/src/media/audio/Makefile.am
+++ b/src/media/audio/Makefile.am
@@ -39,6 +39,7 @@ endif
 
 libaudio_la_SOURCES = \
 		audiobuffer.cpp \
+		audio_input.cpp \
 		audioloop.cpp \
 		ringbuffer.cpp \
 		ringbufferpool.cpp \
@@ -59,13 +60,14 @@ endif
 
 noinst_HEADERS = \
 		audiobuffer.h \
+		audio_input.h \
 		audioloop.h \
 		ringbuffer.h \
 		ringbufferpool.h \
 		audiolayer.h \
+		resampler.h \
 		$(RING_SPEEXDSP_HEAD) \
 		dcblocker.h \
-		resampler.h \
 		audio_rtp_session.h \
 		tonecontrol.h
 
diff --git a/src/media/audio/audio_input.cpp b/src/media/audio/audio_input.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..295f1a95025d33e51d2f255d0b5e0bc1f7aab3a4
--- /dev/null
+++ b/src/media/audio/audio_input.cpp
@@ -0,0 +1,124 @@
+/*
+ *  Copyright (C) 2018 Savoir-faire Linux Inc.
+ *
+ *  Author: Hugo Lefeuvre <hugo.lefeuvre@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
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+ */
+
+#include "audio_input.h"
+
+#include <future>
+#include <chrono>
+#include "socket_pair.h"
+#include "audio/ringbufferpool.h"
+#include "manager.h"
+#include "smartools.h"
+
+namespace ring { namespace audio {
+
+AudioInput::AudioInput(const std::string& id) :
+    id_(id),
+    loop_([] {return true;}, // setup()
+          [this] {return process();},
+          [this] {return cleanup();})
+{
+    loop_.start();
+}
+
+AudioInput::~AudioInput()
+{
+    if (auto rec = recorder_.lock())
+        rec->stopRecording();
+    loop_.join();
+}
+
+// seq: frame number for video, sent samples audio
+// sampleFreq: fps for video, sample rate for audio
+// clock: stream time base (packetization interval times)
+// FIXME duplicate code from media encoder
+int64_t
+getNextTimestamp(int64_t seq, rational<int64_t> sampleFreq, rational<int64_t> clock)
+{
+    return (seq / (sampleFreq * clock)).real<int64_t>();
+}
+
+void
+AudioInput::process()
+{
+    auto& mainBuffer = Manager::instance().getRingBufferPool();
+    auto ringBuffer = mainBuffer.getRingBuffer(id_);
+    auto bufferFormat = ringBuffer->getFormat();
+
+    // compute number of bytes contained in a frame with duration msPerPacket_
+    auto bytesPerPacket = msPerPacket_ * bufferFormat.sample_rate;
+    const std::size_t bytesToGet = std::chrono::duration_cast<std::chrono::seconds>(bytesPerPacket).count();
+
+    if (ringBuffer->availableForGet(id_) < bytesToGet
+        && not ringBuffer->waitForDataAvailable(id_, bytesToGet)) {
+        return;
+    }
+
+    // get data
+    micData_.setFormat(bufferFormat);
+    micData_.resize(bytesToGet);
+    const auto samples = ringBuffer->get(micData_, id_);
+    if (samples != bytesToGet)
+        return;
+
+    if (muteState_) // audio is muted, set samples to 0
+        micData_.reset();
+
+    // record frame
+    AVFrame* frame = micData_.toAVFrame();
+    auto ms = MediaStream("a:local", micData_.getFormat());
+    frame->pts = getNextTimestamp(sent_samples, ms.sampleRate, static_cast<rational<int64_t>>(ms.timeBase));
+    sent_samples += frame->nb_samples;
+
+    {
+        auto rec = recorder_.lock();
+        if (rec && rec->isRecording()) {
+            rec->recordData(frame, ms);
+        }
+    }
+}
+
+void
+AudioInput::cleanup()
+{
+    micData_.clear();
+}
+
+void
+AudioInput::setMuted(bool isMuted)
+{
+    muteState_ = isMuted;
+}
+
+std::shared_future<DeviceParams>
+AudioInput::switchInput(const std::string& resource)
+{
+    // TODO not implemented yet
+    return {};
+}
+
+void
+AudioInput::initRecorder(const std::shared_ptr<MediaRecorder>& rec)
+{
+    rec->incrementExpectedStreams(1);
+    recorder_ = rec;
+}
+
+}} // namespace ring::audio
diff --git a/src/media/audio/audio_input.h b/src/media/audio/audio_input.h
new file mode 100644
index 0000000000000000000000000000000000000000..040d68e9ce5d9a072dfcd22b655fb25e88329bfe
--- /dev/null
+++ b/src/media/audio/audio_input.h
@@ -0,0 +1,60 @@
+/*
+ *  Copyright (C) 2018 Savoir-faire Linux Inc.
+ *
+ *  Author: Hugo Lefeuvre <hugo.lefeuvre@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
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+ */
+
+#pragma once
+
+#include <future>
+#include "audio/audiobuffer.h"
+#include "media/media_recorder.h"
+#include "audio/resampler.h"
+
+namespace ring {
+class MediaRecorder;
+}
+
+namespace ring { namespace audio {
+
+class AudioInput
+{
+public:
+    AudioInput(const std::string& id);
+    ~AudioInput();
+
+    std::shared_future<DeviceParams> switchInput(const std::string& resource);
+
+    void setMuted(bool isMuted);
+    void initRecorder(const std::shared_ptr<MediaRecorder>& rec);
+
+private:
+    std::weak_ptr<MediaRecorder> recorder_;
+    uint64_t sent_samples = 0;
+
+    std::string id_;
+    AudioBuffer micData_;
+    bool muteState_ = false;
+
+    const std::chrono::milliseconds msPerPacket_ {20};
+
+    ThreadLoop loop_;
+    void process();
+    void cleanup();
+};
+
+}} // namespace ring::audio
diff --git a/src/media/audio/audio_rtp_session.cpp b/src/media/audio/audio_rtp_session.cpp
index aa996f880111299205ea25dc02d24a6094a8b985..e354fd06816e07ad8f0b89859fc1a4706ad8d75b 100644
--- a/src/media/audio/audio_rtp_session.cpp
+++ b/src/media/audio/audio_rtp_session.cpp
@@ -222,6 +222,7 @@ AudioSender::process()
     if (audioEncoder_->encodeAudio(frame) < 0)
         RING_ERR("encoding failed");
 }
+
 void
 AudioSender::setMuted(bool isMuted)
 {