diff --git a/daemon/src/Makefile.am b/daemon/src/Makefile.am
index 8c03a90cd29911806769d3207862f8edf10d6d69..cc610fdf19de8326d7250dafe1bfd4ce729f6ba1 100644
--- a/daemon/src/Makefile.am
+++ b/daemon/src/Makefile.am
@@ -85,9 +85,9 @@ libsflphone_la_SOURCES = conference.cpp \
 		logger.cpp \
 		numbercleaner.cpp \
 		fileutils.cpp \
-		sflthread.cpp \
+		threadloop.cpp \
 		ip_utils.cpp \
-		sflthread.h \
+		threadloop.h \
 		conference.h \
 		voiplink.h \
 		preferences.h \
diff --git a/daemon/src/sflthread.cpp b/daemon/src/threadloop.cpp
similarity index 63%
rename from daemon/src/sflthread.cpp
rename to daemon/src/threadloop.cpp
index 669714bf60548f6d8c35e7cec5306aef29ce71e5..f175aae3229a0538e176d0340658461e780779b9 100644
--- a/daemon/src/sflthread.cpp
+++ b/daemon/src/threadloop.cpp
@@ -29,63 +29,67 @@
  *  as that of the covered work.
  */
 
-#include "sflthread.h"
+#include "threadloop.h"
 #include "logger.h"
 
-void* SFLThread::run_(void* data)
+void ThreadLoop::mainloop()
 {
-    SFLThread *obj = static_cast<SFLThread*>(data);
-    obj->mainloop_();
-    return nullptr;
-}
-
-void SFLThread::mainloop_()
-{
-    if (setup()) {
-        while (running_)
-            process();
-        cleanup();
-    } else
-        ERROR("setup failed");
+    try {
+        if (setup_()) {
+            while (running_)
+                process_();
+            cleanup_();
+        } else {
+            ERROR("setup failed");
+        }
+    } catch (const ThreadLoopException &e) {
+        ERROR("%s", e.what());
+    }
 }
 
-SFLThread::SFLThread() : thread_(), running_(false)
+ThreadLoop::ThreadLoop(const std::function<bool()> &setup,
+                       const std::function<void()> &process,
+                       const std::function<void()> &cleanup)
+    : setup_(setup), process_(process), cleanup_(cleanup)
 {}
 
-SFLThread::~SFLThread()
+ThreadLoop::~ThreadLoop()
 {
     if (isRunning()) {
-        stop();
+        ERROR("Thread not stopped by owner");
         join();
     }
 }
 
-void SFLThread::start()
+void ThreadLoop::start()
 {
     if (!running_) {
         running_ = true;
-        pthread_create(&thread_, NULL, &run_, this);
+        thread_ = std::thread(&ThreadLoop::mainloop, this);
+    } else {
+        ERROR("Thread already started");
     }
 }
 
-void SFLThread::stop()
+void ThreadLoop::stop()
 {
     running_ = false;
 }
 
-void SFLThread::join()
+void ThreadLoop::join()
 {
-    if (thread_)
-        pthread_join(thread_, NULL);
+    stop();
+    if (thread_.joinable())
+        thread_.join();
 }
 
-void SFLThread::exit()
+void ThreadLoop::exit()
 {
     stop();
-    pthread_exit(NULL);
+    throw ThreadLoopException();
 }
 
-bool SFLThread::isRunning()
+bool ThreadLoop::isRunning() const
 {
     return running_;
 }
diff --git a/daemon/src/sflthread.h b/daemon/src/threadloop.h
similarity index 67%
rename from daemon/src/sflthread.h
rename to daemon/src/threadloop.h
index 08c1c09011d3e3ac09f79be1636d9ab72400b173..a41eb227862a75b781929916b77f007b2e797988 100644
--- a/daemon/src/sflthread.h
+++ b/daemon/src/threadloop.h
@@ -29,35 +29,42 @@
  *  as that of the covered work.
  */
 
-#ifndef __SFLTHREAD_H__
-#define __SFLTHREAD_H__
+#ifndef __THREADLOOP_H__
+#define __THREADLOOP_H__
 
-#include <pthread.h>
 #include <atomic>
+#include <thread>
+#include <functional>
+#include <stdexcept>
 
-class SFLThread {
+struct ThreadLoopException : public std::runtime_error {
+    ThreadLoopException() : std::runtime_error("ThreadLoopException") {}
+};
+
+class ThreadLoop {
 public:
-    SFLThread();
-    virtual ~SFLThread();
+    ThreadLoop(const std::function<bool()> &setup,
+               const std::function<void()> &process,
+               const std::function<void()> &cleanup);
+    ~ThreadLoop();
 
     void start();
 
-protected:
     void exit();
     void stop();
     void join();
-    bool isRunning();
+    bool isRunning() const;
 
 private:
-    virtual bool setup() { return true; };
-    virtual void process() {};
-    virtual void cleanup() {};
+    // These must be provided by users of ThreadLoop
+    std::function<bool()> setup_;
+    std::function<void()> process_;
+    std::function<void()> cleanup_;
 
-    static void* run_(void*);
-    void mainloop_();
-    pthread_t thread_;
+    void mainloop();
 
-    std::atomic<bool> running_;
+    std::atomic<bool> running_ = {false};
+    std::thread thread_ = {};
 };
 
-#endif // __SFLTHREAD_H__
+#endif // __THREADLOOP_H__
diff --git a/daemon/src/video/check.h b/daemon/src/video/check.h
index 5fb7eb92b9bacdb32078ca5fbe01a05da70a150a..388268a555a7765582c8e39f5cef0c3354aa9547 100644
--- a/daemon/src/video/check.h
+++ b/daemon/src/video/check.h
@@ -35,6 +35,6 @@
 
 // If condition A is false, print the error message in M and exit thread
 #define EXIT_IF_FAIL(A, M, ...) if (!(A)) { \
-        ERROR(M, ##__VA_ARGS__); this->exit(); }
+        ERROR(M, ##__VA_ARGS__); loop_.exit(); }
 
 #endif // CHECK_H_
diff --git a/daemon/src/video/video_input.cpp b/daemon/src/video/video_input.cpp
index 3593007d52fb9a0198fecdc142d5eb2bacb5ad5c..61e78ad707f552ec62fcb9b9a64835634843ddfa 100644
--- a/daemon/src/video/video_input.cpp
+++ b/daemon/src/video/video_input.cpp
@@ -44,14 +44,16 @@ namespace sfl_video {
 VideoInput::VideoInput() :
     VideoGenerator::VideoGenerator()
     , sink_()
+    , loop_(std::bind(&VideoInput::setup, this),
+            std::bind(&VideoInput::process, this),
+            std::bind(&VideoInput::cleanup, this))
 {
-    start();
+    loop_.start();
 }
 
 VideoInput::~VideoInput()
 {
-    stop();
-    join();
+    loop_.join();
 }
 
 bool VideoInput::setup()
@@ -86,7 +88,7 @@ void VideoInput::cleanup()
 int VideoInput::interruptCb(void *data)
 {
     VideoInput *context = static_cast<VideoInput*>(data);
-    return not context->isRunning();
+    return not context->loop_.isRunning();
 }
 
 bool VideoInput::captureFrame()
@@ -100,7 +102,7 @@ bool VideoInput::captureFrame()
 
         case VideoDecoder::Status::ReadError:
         case VideoDecoder::Status::DecodeError:
-            stop();
+            loop_.stop();
             // fallthrough
         case VideoDecoder::Status::Success:
             return false;
diff --git a/daemon/src/video/video_input.h b/daemon/src/video/video_input.h
index 45569962d896d33c126b45040c7635bcc9e6950a..e3e0671ad4cbc5706cef4c12c3702d2288f6ab9e 100644
--- a/daemon/src/video/video_input.h
+++ b/daemon/src/video/video_input.h
@@ -37,7 +37,7 @@
 #include "noncopyable.h"
 #include "shm_sink.h"
 #include "video_decoder.h"
-#include "sflthread.h"
+#include "threadloop.h"
 
 #include <map>
 #include <atomic>
@@ -45,9 +45,7 @@
 
 namespace sfl_video {
 
-class VideoInput :
-    public VideoGenerator,
-    public SFLThread
+class VideoInput : public VideoGenerator
 {
 public:
     VideoInput();
@@ -75,6 +73,7 @@ private:
     std::string input_      = "";
     std::string format_     = "";
     bool emulateRate_       = false;
+    ThreadLoop loop_;
 
     void createDecoder();
     void deleteDecoder();
@@ -83,7 +82,7 @@ private:
     bool initX11(std::string display);
     bool initFile(std::string path);
 
-    // as SFLThread
+    // for ThreadLoop
     bool setup();
     void process();
     void cleanup();
diff --git a/daemon/src/video/video_mixer.h b/daemon/src/video/video_mixer.h
index ce107bdfa585c22c5eb4febf0873b0242f9bc51e..5baeb6e149b5be18ce32dc297f5bb3d89629d97d 100644
--- a/daemon/src/video/video_mixer.h
+++ b/daemon/src/video/video_mixer.h
@@ -36,7 +36,6 @@
 #include "video_base.h"
 #include "video_scaler.h"
 #include "shm_sink.h"
-#include "sflthread.h"
 
 #include <mutex>
 #include <list>
diff --git a/daemon/src/video/video_receive_thread.cpp b/daemon/src/video/video_receive_thread.cpp
index e34e742ee5ab30f7ed9ec6318362f43a768ae58e..09ee7bd4c0f59f5a27870558c4f5ab084cbce3d4 100644
--- a/daemon/src/video/video_receive_thread.cpp
+++ b/daemon/src/video/video_receive_thread.cpp
@@ -59,12 +59,20 @@ VideoReceiveThread::VideoReceiveThread(const std::string& id,
     , demuxContext_()
     , sink_(id)
     , requestKeyFrameCallback_(0)
+    , loop_(std::bind(&VideoReceiveThread::setup, this),
+            std::bind(&VideoReceiveThread::process, this),
+            std::bind(&VideoReceiveThread::cleanup, this))
 {}
 
 VideoReceiveThread::~VideoReceiveThread()
 {
-    stop();
-    join();
+    loop_.join();
+}
+
+void
+VideoReceiveThread::startLoop()
+{
+    loop_.start();
 }
 
 // We do this setup here instead of the constructor because we don't want the
@@ -162,7 +170,7 @@ void VideoReceiveThread::cleanup()
 int VideoReceiveThread::interruptCb(void *data)
 {
     VideoReceiveThread *context = static_cast<VideoReceiveThread*>(data);
-    return not context->isRunning();
+    return not context->loop_.isRunning();
 }
 
 int VideoReceiveThread::readFunction(void *opaque, uint8_t *buf, int buf_size)
@@ -199,7 +207,7 @@ bool VideoReceiveThread::decodeFrame()
             // fallthrough if we can't request keyframe
         case VideoDecoder::Status::ReadError:
             ERROR("VideoDecoder fatal error, stopping it...");
-            stop();
+            loop_.stop();
 
         default:
             break;
@@ -211,7 +219,7 @@ bool VideoReceiveThread::decodeFrame()
 
 void VideoReceiveThread::enterConference()
 {
-    if (!isRunning())
+    if (!loop_.isRunning())
         return;
 
     if (detach(&sink_)) {
@@ -222,7 +230,7 @@ void VideoReceiveThread::enterConference()
 
 void VideoReceiveThread::exitConference()
 {
-    if (!isRunning())
+    if (!loop_.isRunning())
         return;
 
     if (dstWidth_ > 0 && dstHeight_ > 0) {
diff --git a/daemon/src/video/video_receive_thread.h b/daemon/src/video/video_receive_thread.h
index 37b01b6a893302286763274e990120eda8438252..cfab33494c9feca804ebb5acf9f4b407e9cdec61 100644
--- a/daemon/src/video/video_receive_thread.h
+++ b/daemon/src/video/video_receive_thread.h
@@ -33,7 +33,7 @@
 
 #include "video_decoder.h"
 #include "shm_sink.h"
-#include "sflthread.h"
+#include "threadloop.h"
 #include "noncopyable.h"
 
 #include <map>
@@ -46,11 +46,12 @@ namespace sfl_video {
 
 class SocketPair;
 
-class VideoReceiveThread : public VideoGenerator, public SFLThread  {
+class VideoReceiveThread : public VideoGenerator {
 public:
     VideoReceiveThread(const std::string &id,
                        const std::map<std::string, std::string> &args);
     ~VideoReceiveThread();
+    void startLoop();
 
     void addIOContext(SocketPair &socketPair);
     void setRequestKeyFrameCallback(void (*)(const std::string &));
@@ -86,7 +87,9 @@ private:
     static int readFunction(void *opaque, uint8_t *buf, int buf_size);
 
 
-    // as SFLThread
+    ThreadLoop loop_;
+
+    // used by ThreadLoop
     bool setup();
     void process();
     void cleanup();
diff --git a/daemon/src/video/video_rtp_session.cpp b/daemon/src/video/video_rtp_session.cpp
index d3c8274814293ade8dff4bdd8730467660337da8..cbc99beb33523d766ad9d5408d7257af005a061c 100644
--- a/daemon/src/video/video_rtp_session.cpp
+++ b/daemon/src/video/video_rtp_session.cpp
@@ -172,7 +172,7 @@ void VideoRtpSession::startReceiver()
         receiveThread_.reset(new VideoReceiveThread(callID_, rxArgs_));
         receiveThread_->setRequestKeyFrameCallback(&SIPVoIPLink::enqueueKeyframeRequest);
         receiveThread_->addIOContext(*socketPair_);
-        receiveThread_->start();
+        receiveThread_->startLoop();
     } else {
         DEBUG("Video receiving disabled");
         if (receiveThread_)