diff --git a/.idea/.gitignore b/.idea/.gitignore
index e94e0e3f5cd58cd2fa7ce47e1c1213048899ff9d..6700efe6022a04a6dc6c560a437730065837fd95 100644
--- a/.idea/.gitignore
+++ b/.idea/.gitignore
@@ -8,3 +8,4 @@
 /dataSources.local.xml
 # Editor-based HTTP Client requests
 /httpRequests/
+/discord.xml
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000000000000000000000000000000000000..ba0f7e5ea16a2606db664148aefdeaa4437feca7
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,8 @@
+<component name="InspectionProjectProfileManager">
+  <profile version="1.0">
+    <option name="myName" value="Project Default" />
+    <inspection_tool class="Clazy" enabled="true" level="WARNING" enabled_by_default="true">
+      <option name="clazyChecks" value="level0,no-qt-macros" />
+    </inspection_tool>
+  </profile>
+</component>
\ No newline at end of file
diff --git a/AVQt/CMakeLists.txt b/AVQt/CMakeLists.txt
index 700bb739d2fbebf4f791fb1c913d9a9fd1626a4d..436bf3967c1ccb18c9ba68c3293332a5cb754bd2 100644
--- a/AVQt/CMakeLists.txt
+++ b/AVQt/CMakeLists.txt
@@ -12,10 +12,6 @@ set(CMAKE_INCLUDE_CURRENT_DIR on)
 #find_package(Qt6 COMPONENTS Core Gui Concurrent Widgets OpenGL OpenGLWidgets)
 find_package(Qt5 COMPONENTS Core Gui Concurrent Widgets OpenGL)
 
-if (WIN32)
-    set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS on)
-endif ()
-
 #if (${CMAKE_BUILD_TYPE} EQUAL "Release")
 #    -O3)
 #endif ()
@@ -84,95 +80,105 @@ add_library(AVQt SHARED ${SOURCES})
 add_library(AVQtStatic STATIC ${SOURCES})
 set_target_properties(AVQtStatic PROPERTIES OUTPUT_NAME AVQt_static)
 
-target_compile_options(AVQt PRIVATE
-        -std=c++20
-        -Werror=all
-        -Werror=extra
-        -Werror=pedantic
-        -Wno-float-equal
-        -ansi
-        -Werror=init-self
-        -Werror=old-style-cast
-        -Werror=overloaded-virtual
-        -Werror=uninitialized
-        -Werror=missing-declarations
-        -Werror=init-self
-        #    -Wall -Wextra
-        -Wno-error=non-virtual-dtor
-        #        -Wno-unused-result
-        #-Wpedantic
-        -Wshadow
-        -Wfatal-errors
-        -Wundef
-        -Wuninitialized
-        -Winit-self
-        -Wconversion
-        -Wfloat-equal
-        -Wstrict-aliasing
-        -Wtrigraphs
-        -Wodr # one definition rule
-        -Wzero-as-null-pointer-constant
-        -Wsizeof-array-argument
-        -Winline
-        -Wcast-qual
-        -Weffc++
-
-
-        #    -fno-common # each global variable is only declared once.
-        #    -fmerge-all-constants
-        #    -ffunction-sections
-        #    -fdata-sections
-        #    -fno-use-cxa-atexit
-        #    -fverbose-asm
-        #    -fstrict-enums
-        -Wstack-protector
-        -Wdouble-promotion
-        )
-target_compile_options(AVQtStatic PRIVATE
-        -std=c++20
-        -Werror=all
-        -Werror=extra
-        -Werror=pedantic
-        -ansi
-        -Werror=init-self
-        -Werror=old-style-cast
-        -Werror=overloaded-virtual
-        -Werror=uninitialized
-        -Werror=missing-declarations
-        -Werror=init-self
-        #    -Wall -Wextra
-        -Wno-error=non-virtual-dtor
-        #        -Wno-unused-result
-        #-Wpedantic
-        -Wshadow
-        -Wfatal-errors
-        -Wundef
-        -Wuninitialized
-        -Winit-self
-        -Wconversion
-        -Wfloat-equal
-        -Wstrict-aliasing
-        -Wtrigraphs
-        -Wodr # one definition rule
-        -Wzero-as-null-pointer-constant
-        -Wsizeof-array-argument
-        -Winline
-        -Wcast-qual
-        -Weffc++
-
-
-        #    -fno-common # each global variable is only declared once.
-        #    -fmerge-all-constants
-        #    -ffunction-sections
-        #    -fdata-sections
-        #    -fno-use-cxa-atexit
-        #    -fverbose-asm
-        #    -fstrict-enums
-        -Wstack-protector
-        -Wdouble-promotion
-        )
+if (!WIN32)
+    target_compile_options(AVQt PRIVATE
+            -std=c++20
+            -Werror=all
+            -Werror=extra
+            -Werror=pedantic
+            -Wno-float-equal
+            -ansi
+            -Werror=init-self
+            -Werror=old-style-cast
+            -Werror=overloaded-virtual
+            -Werror=uninitialized
+            -Werror=missing-declarations
+            -Werror=init-self
+            #    -Wall -Wextra
+            -Wno-error=non-virtual-dtor
+            #        -Wno-unused-result
+            #-Wpedantic
+            -Wshadow
+            -Wfatal-errors
+            -Wundef
+            -Wuninitialized
+            -Winit-self
+            -Wconversion
+            -Wfloat-equal
+            -Wstrict-aliasing
+            -Wtrigraphs
+            -Wodr # one definition rule
+            -Wzero-as-null-pointer-constant
+            -Wsizeof-array-argument
+            -Winline
+            -Wcast-qual
+            -Weffc++
+
+
+            #    -fno-common # each global variable is only declared once.
+            #    -fmerge-all-constants
+            #    -ffunction-sections
+            #    -fdata-sections
+            #    -fno-use-cxa-atexit
+            #    -fverbose-asm
+            #    -fstrict-enums
+            -Wstack-protector
+            -Wdouble-promotion
+            )
+    target_compile_options(AVQtStatic PRIVATE
+            -std=c++20
+            -Werror=all
+            -Werror=extra
+            -Werror=pedantic
+            -ansi
+            -Werror=init-self
+            -Werror=old-style-cast
+            -Werror=overloaded-virtual
+            -Werror=uninitialized
+            -Werror=missing-declarations
+            -Werror=init-self
+            #    -Wall -Wextra
+            -Wno-error=non-virtual-dtor
+            #        -Wno-unused-result
+            #-Wpedantic
+            -Wshadow
+            -Wfatal-errors
+            -Wundef
+            -Wuninitialized
+            -Winit-self
+            -Wconversion
+            -Wfloat-equal
+            -Wstrict-aliasing
+            -Wtrigraphs
+            -Wodr # one definition rule
+            -Wzero-as-null-pointer-constant
+            -Wsizeof-array-argument
+            -Winline
+            -Wcast-qual
+            -Weffc++
+
+
+            #    -fno-common # each global variable is only declared once.
+            #    -fmerge-all-constants
+            #    -ffunction-sections
+            #    -fdata-sections
+            #    -fno-use-cxa-atexit
+            #    -fverbose-asm
+            #    -fstrict-enums
+            -Wstack-protector
+            -Wdouble-promotion
+            )
+endif ()
 
 #target_link_libraries(AVQt Qt6::Core Qt6::Gui Qt6::Concurrent Qt6::Widgets Qt6::OpenGL Qt6::OpenGLWidgets avformat avfilter avutil avcodec avdevice swscale swresample GL openal)
 #target_link_libraries(AVQtStatic Qt6::Core Qt6::Gui Qt6::Concurrent Qt6::Widgets Qt6::OpenGL Qt6::OpenGLWidgets avformat avfilter avutil avcodec avdevice swscale swresample GL openal)
-target_link_libraries(AVQt Qt5::Core Qt5::Gui Qt5::Concurrent Qt5::Widgets Qt5::OpenGL avformat avfilter avutil avcodec avdevice swscale swresample GL openal)
-target_link_libraries(AVQtStatic Qt5::Core Qt5::Gui Qt5::Concurrent Qt5::Widgets Qt5::OpenGL avformat avfilter avutil avcodec avdevice swscale swresample GL openal)
\ No newline at end of file
+target_link_libraries(AVQt Qt5::Core Qt5::Gui Qt5::Concurrent Qt5::Widgets Qt5::OpenGL avformat avfilter avutil avcodec avdevice swscale swresample)
+target_link_libraries(AVQtStatic Qt5::Core Qt5::Gui Qt5::Concurrent Qt5::Widgets Qt5::OpenGL avformat avfilter avutil avcodec avdevice swscale swresample)
+
+if (WIN32)
+    target_link_libraries(AVQt opengl32 OpenAL32)
+    target_link_libraries(AVQtStatic opengl32 OpenAL32)
+else ()
+    target_link_libraries(AVQt GL openal)
+    target_link_libraries(AVQtStatic GL openal)
+endif ()
\ No newline at end of file
diff --git a/AVQt/filter/DecoderVAAPI.cpp b/AVQt/filter/DecoderVAAPI.cpp
index a68f3edf229d6faf9bf99d846ac56483e12743b3..d61dcecebc86002e501cdd314dbc8c3e104ba7d5 100644
--- a/AVQt/filter/DecoderVAAPI.cpp
+++ b/AVQt/filter/DecoderVAAPI.cpp
@@ -72,6 +72,13 @@ namespace AVQt {
 
         stop();
 
+        {
+            QMutexLocker lock(&d->m_cbListMutex);
+            for (const auto &cb: d->m_cbList) {
+                cb->deinit(this);
+            }
+        }
+
         if (d->m_pCodecParams) {
             avcodec_parameters_free(&d->m_pCodecParams);
             d->m_pCodecParams = nullptr;
@@ -228,6 +235,7 @@ namespace AVQt {
 
                     avcodec_parameters_to_context(d->m_pCodecCtx, d->m_pCodecParams);
                     d->m_pCodecCtx->hw_device_ctx = av_buffer_ref(d->m_pDeviceCtx);
+                    d->m_pCodecCtx->time_base = d->m_timebase;
                     ret = avcodec_open2(d->m_pCodecCtx, d->m_pCodec, nullptr);
                     if (ret != 0) {
                         qFatal("%d: Could not open VAAPI decoder: %s", ret, av_make_error_string(strBuf, strBufSize, ret));
diff --git a/AVQt/filter/EncoderVAAPI.cpp b/AVQt/filter/EncoderVAAPI.cpp
index 05f1271c21db92be295fceb1a77aaf6549254dc5..e94235e68346c30b72239c85aa070701531039a6 100644
--- a/AVQt/filter/EncoderVAAPI.cpp
+++ b/AVQt/filter/EncoderVAAPI.cpp
@@ -45,6 +45,8 @@ namespace AVQt {
     int EncoderVAAPI::deinit() {
         Q_D(AVQt::EncoderVAAPI);
 
+        stop();
+
         {
             QMutexLocker lock(&d->m_cbListMutex);
             for (const auto &cb: d->m_cbList) {
@@ -216,6 +218,10 @@ namespace AVQt {
         {
             QMutexLocker lock{&d->m_inputQueueMutex};
             d->m_inputQueue.enqueue(queueFrame);
+//            std::sort(d->m_inputQueue.begin(), d->m_inputQueue.end(),
+//                      [&](const QPair<AVFrame *, int64_t> &f1, const QPair<AVFrame *, int64_t> &f2) {
+//                          return f1.first->pts < f2.first->pts;
+//                      });
         }
     }
 
@@ -227,8 +233,9 @@ namespace AVQt {
         char strBuf[strBufSize];
 
         while (d->m_running.load()) {
-            if (!d->m_paused.load() && !d->m_inputQueue.isEmpty()) {
+            if (!d->m_paused.load() && d->m_inputQueue.size() > 5) {
                 bool shouldBe = true;
+                // Encoder init is only possible with frame parameters
                 if (d->m_firstFrame.compare_exchange_strong(shouldBe, false)) {
                     auto frame = d->m_inputQueue.first().first;
 
@@ -238,27 +245,6 @@ namespace AVQt {
                     }
                     d->m_pCodecCtx->width = frame->width;
                     d->m_pCodecCtx->height = frame->height;
-//                    if (frame->hw_frames_ctx) {
-//                        qDebug("Creating derived HW context...");
-//                        d->m_pDeviceCtx = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_VAAPI);
-//                        ret = av_hwdevice_ctx_create(&d->m_pDeviceCtx, AV_HWDEVICE_TYPE_VAAPI, "/dev/dri/renderD128", nullptr, 0);
-//                        if (ret < 0) {
-//                            qFatal("%i: Unable to create derived AVHWDeviceContext: %s", ret, av_make_error_string(strBuf, strBufSize, ret));
-//                        } else if (!d->m_pDeviceCtx) {
-//                            qFatal("Unable to create derived AVHWDeviceContext");
-//                        }
-//
-//                        d->m_pCodecCtx->sw_pix_fmt = reinterpret_cast<AVHWFramesContext*>(frame->hw_frames_ctx->data)->sw_format;
-//                        ret = av_hwframe_ctx_create_derived(&d->m_pFramesCtx, d->m_pCodecCtx->sw_pix_fmt, d->m_pDeviceCtx, frame->hw_frames_ctx, 0);
-//                        if (ret < 0) {
-//                            qFatal("%i: Unable to create derived AVHWFramesContext: %s", ret, av_make_error_string(strBuf, strBufSize, ret));
-//                        } else if (!d->m_pDeviceCtx) {
-//                            qFatal("Unable to create derived AVHWFramesContext");
-//                        }
-//                        d->m_pCodecCtx->pix_fmt = reinterpret_cast<AVHWFramesContext*>(d->m_pFramesCtx->data)->format;
-//                        d->m_pCodecCtx->hw_device_ctx = av_buffer_ref(d->m_pDeviceCtx);
-//                        d->m_pCodecCtx->hw_frames_ctx = av_buffer_ref(d->m_pFramesCtx);
-//                    } else {
                     qDebug("Creating new HW context...");
                     ret = av_hwdevice_ctx_create(&d->m_pDeviceCtx, AV_HWDEVICE_TYPE_VAAPI, "/dev/dri/renderD128", nullptr, 0);
                     if (ret < 0) {
@@ -290,15 +276,25 @@ namespace AVQt {
 
                     d->m_pCodecCtx->hw_device_ctx = av_buffer_ref(d->m_pDeviceCtx);
                     d->m_pCodecCtx->hw_frames_ctx = av_buffer_ref(d->m_pFramesCtx);
-//                    }
-
-//                    d->m_pCodecCtx->pix_fmt = AV_PIX_FMT_VAAPI;
+//                    d->m_pCodecCtx->bit_rate = 5000000;
+//                    d->m_pCodecCtx->rc_min_rate = 4500000;
+//                    d->m_pCodecCtx->rc_max_rate = 6000000;
+//                    d->m_pCodecCtx->rc_buffer_size = 10000000;
+                    d->m_pCodecCtx->gop_size = 20;
+                    d->m_pCodecCtx->max_b_frames = 0;
+                    d->m_pCodecCtx->color_primaries = AVCOL_PRI_BT2020;
+                    d->m_pCodecCtx->color_trc = AVCOL_TRC_SMPTE2084;
+                    d->m_pCodecCtx->colorspace = AVCOL_SPC_BT2020_NCL;
+
+                    // Timestamps from frame sources are always microseconds, trying to use this timebase for the encoder too
                     d->m_pCodecCtx->time_base = av_make_q(1, 1000000);
                     ret = avcodec_open2(d->m_pCodecCtx, d->m_pCodec, nullptr);
                     if (ret < 0) {
                         qFatal("%i: Unable to open VAAPI encoder: %s", ret, av_make_error_string(strBuf, strBufSize, ret));
                     }
                     qDebug("[AVQt::EncoderVAAPI] Encoder timebase: %d/%d", d->m_pCodecCtx->time_base.num, d->m_pCodecCtx->time_base.den);
+
+                    // Preallocating frame on gpu
                     d->m_pHWFrame = av_frame_alloc();
                     ret = av_hwframe_get_buffer(d->m_pFramesCtx, d->m_pHWFrame, 0);
                     if (ret != 0) {
@@ -325,24 +321,44 @@ namespace AVQt {
                     } else {
                         av_hwframe_transfer_data(d->m_pHWFrame, frame.first, 0);
                     }
-                    d->m_pHWFrame->pts = av_rescale_q(frame.first->pts, av_make_q(1, 1000000), d->m_pCodecCtx->time_base);
+                    d->m_pHWFrame->pts = av_rescale_q(frame.first->pts, av_make_q(1, 1000000),
+                                                      d->m_pCodecCtx->time_base); // Incoming timestamps are always microseconds
+//                    d->m_pHWFrame->pts = frame.first->pts;
                     av_frame_free(&frame.first);
                     ret = avcodec_send_frame(d->m_pCodecCtx, d->m_pHWFrame);
-                    if (ret != 0) {
+                    qDebug("[AVQt::EncoderVAAPI] Sent frame with PTS %lld to encoder", static_cast<long long>(d->m_pHWFrame->pts));
+                    /*if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
+                        break;
+                    } else */if (ret != 0) {
                         qFatal("%i: Could not send frame to VAAPI encoder: %s", ret, av_make_error_string(strBuf, strBufSize, ret));
                     }
+
+//                    avcodec_send_frame(d->m_pCodecCtx, nullptr);
+//
+//                    if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
+//                        break;
+//                    } else if (ret != 0) {
+//                        qFatal("%i: Could not flush VAAPI encoder: %s", ret, av_make_error_string(strBuf, strBufSize, ret));
+//                    }
                 }
                 AVPacket *packet = av_packet_alloc();
                 while (true) {
                     ret = avcodec_receive_packet(d->m_pCodecCtx, packet);
-                    qDebug("[AVQt::EncoderVAAPI] Got packet from encoder with PTS: %lld, timebase: %d/%d",
-                           static_cast<long long>(packet->pts),
-                           d->m_pCodecCtx->time_base.num, d->m_pCodecCtx->time_base.den);
                     if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN)) {
                         break;
                     } else if (ret != 0) {
                         qFatal("%i: Could not receive packet from encoder: %s", ret, av_make_error_string(strBuf, strBufSize, ret));
                     }
+//                    packet->pts = d->m_pHWFrame->pts;
+//                    packet->pts = d->m_pCodecCtx->coded_frame->pts;
+//                    packet->pts = packet->dts;
+                    packet->pos = -1;
+                    av_packet_rescale_ts(packet, d->m_pCodecCtx->time_base, av_make_q(1, 1000000));
+                    qDebug("[AVQt::EncoderVAAPI] Got packet from encoder with PTS: %lld, DTS: %lld, duration: %lld, timebase: %d/%d",
+                           static_cast<long long>(packet->pts),
+                           static_cast<long long>(packet->dts),
+                           static_cast<long long>(packet->duration),
+                           d->m_pCodecCtx->time_base.num, d->m_pCodecCtx->time_base.den);
                     if (packet->buf) {
                         QMutexLocker lock(&d->m_cbListMutex);
                         for (const auto &cb: d->m_cbList) {
diff --git a/AVQt/input/Demuxer.cpp b/AVQt/input/Demuxer.cpp
index d99e134901427e16e18385b13a8bdb749967497e..fe24e6fe396b7a8038b3aa6b01b432fc4bacb8dc 100644
--- a/AVQt/input/Demuxer.cpp
+++ b/AVQt/input/Demuxer.cpp
@@ -102,7 +102,7 @@ namespace AVQt {
         bool initialized = true;
         d->m_initialized.compare_exchange_strong(initialized, true);
         if (!initialized) {
-            d->m_pBuffer = new uint8_t[DemuxerPrivate::BUFFER_SIZE];
+            d->m_pBuffer = static_cast<uint8_t *>(av_malloc(DemuxerPrivate::BUFFER_SIZE));
             d->m_pIOCtx = avio_alloc_context(d->m_pBuffer, DemuxerPrivate::BUFFER_SIZE, 0, d->m_inputDevice,
                                              &DemuxerPrivate::readFromIO, nullptr, &DemuxerPrivate::seekIO);
             d->m_pFormatCtx = avformat_alloc_context();
diff --git a/AVQt/input/private/Demuxer_p.h b/AVQt/input/private/Demuxer_p.h
index aa6cc518b355539e34baecb91b801520db1bc449..1ba35b36645ceaa09a54a9085624ff7a58bb161b 100644
--- a/AVQt/input/private/Demuxer_p.h
+++ b/AVQt/input/private/Demuxer_p.h
@@ -37,7 +37,7 @@ namespace AVQt {
         QList<int64_t> m_videoStreams{}, m_audioStreams{}, m_subtitleStreams{};
         int64_t m_videoStream{-1}, m_audioStream{-1}, m_subtitleStream{-1};
 
-        static constexpr size_t BUFFER_SIZE{1024};
+        static constexpr size_t BUFFER_SIZE{32 * 1024};
         uint8_t *m_pBuffer{nullptr};
         AVFormatContext *m_pFormatCtx{nullptr};
         AVIOContext *m_pIOCtx{nullptr};
diff --git a/AVQt/output/Muxer.cpp b/AVQt/output/Muxer.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..6f6788730b8b9ed4a43c5dff6f7a5ce48ec9fa53
--- /dev/null
+++ b/AVQt/output/Muxer.cpp
@@ -0,0 +1,280 @@
+//
+// Created by silas on 5/24/21.
+//
+#include <input/IPacketSource.h>
+#include "private/Muxer_p.h"
+#include "Muxer.h"
+
+namespace AVQt {
+    Muxer::Muxer(QIODevice *outputDevice, QObject *parent) : QThread(parent), d_ptr(new MuxerPrivate(this)) {
+        Q_D(AVQt::Muxer);
+        d->m_outputDevice = outputDevice;
+    }
+
+    Muxer::Muxer(AVQt::MuxerPrivate &p) : d_ptr(&p) {
+
+    }
+
+    AVQt::Muxer::Muxer(Muxer &&other) noexcept: d_ptr(other.d_ptr) {
+        other.d_ptr = nullptr;
+    }
+
+    bool Muxer::isPaused() {
+        Q_D(AVQt::Muxer);
+        return d->m_paused.load();
+    }
+
+    void Muxer::init(IPacketSource *source, AVRational framerate, AVRational timebase, int64_t duration, AVCodecParameters *vParams,
+                     AVCodecParameters *aParams, AVCodecParameters *sParams) {
+        Q_UNUSED(framerate)
+        Q_UNUSED(duration)
+        Q_D(AVQt::Muxer);
+
+        if ((vParams && aParams) || (vParams && sParams) || (aParams && sParams)) {
+            qWarning("[AVQt::Muxer] init() called for multiple stream types at once. Ignoring call");
+            return;
+        }
+
+        if (d->m_sourceStreamMap.contains(source)) {
+            bool alreadyCalled = false;
+            for (const auto &stream: d->m_sourceStreamMap[source].keys()) {
+                if ((vParams && stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) ||
+                    (aParams && stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) ||
+                    (sParams && stream->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE)) {
+                    alreadyCalled = true;
+                    break;
+                }
+            }
+            if (alreadyCalled) {
+                qWarning("[AVQt::Muxer] init() called multiple times for the same stream type by the same source. Ignoring call");
+                return;
+            }
+        }
+        QMutexLocker lock(&d->m_initMutex);
+        if (!d->m_pFormatContext) {
+            if (!d->m_outputDevice->isOpen()) {
+                if (!d->m_outputDevice->open((d->m_outputDevice->isSequential() ? QIODevice::WriteOnly : QIODevice::ReadWrite))) {
+                    qFatal("[AVQt::Muxer] Could not open output device");
+                }
+            } else if (!d->m_outputDevice->isWritable()) {
+                qFatal("[AVQt::Muxer] Output device is not writable");
+            }
+            d->m_pFormatContext = avformat_alloc_context();
+            d->m_pFormatContext->oformat = av_guess_format("mp4", "", nullptr);
+            d->m_pIOBuffer = static_cast<uint8_t *>(av_malloc(MuxerPrivate::IOBUF_SIZE));
+            d->m_pIOContext = avio_alloc_context(d->m_pIOBuffer, MuxerPrivate::IOBUF_SIZE, 1, d->m_outputDevice, nullptr,
+                                                 &MuxerPrivate::writeToIO, &MuxerPrivate::seekIO);
+            d->m_pIOContext->seekable = !d->m_outputDevice->isSequential();
+            d->m_pFormatContext->pb = d->m_pIOContext;
+            d->m_pFormatContext->flags |= AVFMT_FLAG_CUSTOM_IO;
+        }
+
+        if (!d->m_sourceStreamMap.contains(source)) {
+            d->m_sourceStreamMap[source] = QMap<AVStream *, AVRational>();
+        }
+        if (vParams) {
+            AVStream *videoStream = avformat_new_stream(d->m_pFormatContext, avcodec_find_encoder(vParams->codec_id));
+            videoStream->codecpar = avcodec_parameters_alloc();
+            avcodec_parameters_copy(videoStream->codecpar, vParams);
+            videoStream->time_base = timebase;
+            d->m_sourceStreamMap[source].insert(videoStream, timebase);
+        } else if (aParams) {
+            AVStream *audioStream = avformat_new_stream(d->m_pFormatContext, avcodec_find_encoder(aParams->codec_id));
+            audioStream->codecpar = avcodec_parameters_alloc();
+            avcodec_parameters_copy(audioStream->codecpar, aParams);
+            audioStream->time_base = timebase;
+            d->m_sourceStreamMap[source].insert(audioStream, timebase);
+        } else if (sParams) {
+            AVStream *subtitleStream = avformat_new_stream(d->m_pFormatContext, avcodec_find_encoder(sParams->codec_id));
+            subtitleStream->codecpar = avcodec_parameters_alloc();
+            avcodec_parameters_copy(subtitleStream->codecpar, sParams);
+            subtitleStream->time_base = timebase;
+            d->m_sourceStreamMap[source].insert(subtitleStream, timebase);
+        }
+        int ret = avformat_write_header(d->m_pFormatContext, nullptr);
+        if (ret != 0) {
+            constexpr auto strBufSize = 32;
+            char strBuf[strBufSize];
+            qWarning("[AVQt::Muxer] %d: Couldn't init AVFormatContext: %s", ret, av_make_error_string(strBuf, strBufSize, ret));
+        }
+        qDebug("[AVQt::Muxer] Initialized");
+    }
+
+    void Muxer::deinit(IPacketSource *source) {
+        Q_D(AVQt::Muxer);
+
+        stop(source);
+        qDebug("[AVQt::Muxer] deinit() called");
+
+        if (d->m_sourceStreamMap.contains(source)) {
+            d->m_sourceStreamMap.remove(source);
+        } else {
+            qWarning("[AVQt::Muxer] deinit() called without preceding init() from source. Ignoring call");
+            return;
+        }
+
+        if (d->m_sourceStreamMap.isEmpty()) {
+            QMutexLocker lock(&d->m_initMutex);
+            if (d->m_pFormatContext) {
+                int ret = av_write_frame(d->m_pFormatContext, nullptr);
+                if (ret != 0) {
+                    constexpr auto strBufSize = 32;
+                    char strBuf[strBufSize];
+                    qWarning("%d: Couldn't flush AVFormatContext packet queue: %s", ret, av_make_error_string(strBuf, strBufSize, ret));
+                }
+//                if (d->m_headerWritten.load()) {
+                av_write_trailer(d->m_pFormatContext);
+                qDebug("[AVQt::Muxer] Wrote trailer");
+//                }
+                avio_flush(d->m_pFormatContext->pb);
+                avformat_free_context(d->m_pFormatContext);
+                d->m_outputDevice->close();
+            }
+        }
+
+    }
+
+    void Muxer::start(IPacketSource *source) {
+        Q_UNUSED(source)
+        Q_D(AVQt::Muxer);
+
+        bool shouldBe = false;
+        if (d->m_running.compare_exchange_strong(shouldBe, true)) {
+            shouldBe = false;
+//            if (d->m_headerWritten.compare_exchange_strong(shouldBe, true)) {
+//
+//            }
+            d->m_paused.store(false);
+            QThread::start();
+            started();
+        }
+    }
+
+    void Muxer::stop(IPacketSource *source) {
+        Q_UNUSED(source)
+        Q_D(AVQt::Muxer);
+
+        bool shouldBe = true;
+        if (d->m_running.compare_exchange_strong(shouldBe, false)) {
+            d->m_paused.store(false);
+            wait();
+            {
+                QMutexLocker lock{&d->m_inputQueueMutex};
+                while (!d->m_inputQueue.isEmpty()) {
+                    auto packet = d->m_inputQueue.dequeue();
+                    av_packet_free(&packet.first);
+                }
+            }
+            stopped();
+        }
+    }
+
+    void Muxer::pause(bool p) {
+        Q_D(AVQt::Muxer);
+
+        bool shouldBe = !p;
+        if (d->m_paused.compare_exchange_strong(shouldBe, p)) {
+            paused(p);
+        }
+    }
+
+    void Muxer::onPacket(IPacketSource *source, AVPacket *packet, int8_t packetType) {
+        Q_D(AVQt::Muxer);
+
+        if (packet->pts != AV_NOPTS_VALUE) {
+
+            bool unknownSource = !d->m_sourceStreamMap.contains(source);
+            bool initStream = false;
+            AVStream *addStream;
+            if (!unknownSource) {
+                for (const auto &stream : d->m_sourceStreamMap[source].keys()) {
+                    if ((packetType == IPacketSource::CB_VIDEO && stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) ||
+                        (packetType == IPacketSource::CB_AUDIO && stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) ||
+                        (packetType == IPacketSource::CB_SUBTITLE && stream->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE)) {
+                        addStream = stream;
+                        initStream = true;
+                        break;
+                    }
+                }
+            }
+            if (unknownSource || !initStream) {
+                qWarning("[AVQt::Muxer] onPacket() called without preceding call to init() for stream type. Ignoring packet");
+                return;
+            }
+
+            QPair<AVPacket *, AVStream *> queuePacket{av_packet_clone(packet), addStream};
+            queuePacket.first->stream_index = addStream->index;
+            qDebug("[AVQt::Muxer] Getting packet with PTS: %lld", static_cast<long long>(packet->pts));
+            av_packet_rescale_ts(queuePacket.first, av_make_q(1, 1000000), addStream->time_base);
+
+            while (d->m_inputQueue.size() >= 100) {
+                QThread::msleep(2);
+            }
+
+            QMutexLocker lock(&d->m_inputQueueMutex);
+            d->m_inputQueue.enqueue(queuePacket);
+//            std::sort(d->m_inputQueue.begin(), d->m_inputQueue.end(), &MuxerPrivate::packetQueueCompare);
+        }
+    }
+
+    void Muxer::run() {
+        Q_D(AVQt::Muxer);
+
+        while (d->m_running.load()) {
+            if (!d->m_paused.load() && d->m_inputQueue.size() > 5) {
+                QPair<AVPacket *, AVStream *> packet;
+                {
+                    QMutexLocker lock(&d->m_inputQueueMutex);
+                    packet = d->m_inputQueue.dequeue();
+                }
+                int ret = av_interleaved_write_frame(d->m_pFormatContext, packet.first);
+                qDebug("Written packet");
+                if (ret != 0) {
+                    constexpr auto strBufSize = 32;
+                    char strBuf[strBufSize];
+                    qWarning("%d: Couldn't write packet to AVFormatContext: %s", ret, av_make_error_string(strBuf, strBufSize, ret));
+                }
+            } else {
+                msleep(4);
+            }
+        }
+    }
+
+    int MuxerPrivate::writeToIO(void *opaque, uint8_t *buf, int buf_size) {
+        auto *outputDevice = reinterpret_cast<QIODevice *>(opaque);
+
+        auto bytesWritten = outputDevice->write(reinterpret_cast<const char *>(buf), buf_size);
+        return bytesWritten == 0 ? AVERROR_UNKNOWN : static_cast<int>(bytesWritten);
+    }
+
+    int64_t MuxerPrivate::seekIO(void *opaque, int64_t offset, int whence) {
+        auto *outputDevice = reinterpret_cast<QIODevice *>(opaque);
+
+        if (outputDevice->isSequential()) {
+            return AVERROR_UNKNOWN;
+        }
+
+        bool result;
+        switch (whence) {
+            case SEEK_SET:
+                result = outputDevice->seek(offset);
+                break;
+            case SEEK_CUR:
+                result = outputDevice->seek(outputDevice->pos() + offset);
+                break;
+            case SEEK_END:
+                result = outputDevice->seek(outputDevice->size() - offset);
+                break;
+            case AVSEEK_SIZE:
+                return outputDevice->size();
+            default:
+                return AVERROR_UNKNOWN;
+        }
+
+        return result ? outputDevice->pos() : AVERROR_UNKNOWN;
+    }
+
+    bool MuxerPrivate::packetQueueCompare(const QPair<AVPacket *, AVStream *> &packet1, const QPair<AVPacket *, AVStream *> &packet2) {
+        return packet1.first->dts < packet2.first->dts;
+    }
+}
\ No newline at end of file
diff --git a/AVQt/output/Muxer.h b/AVQt/output/Muxer.h
new file mode 100644
index 0000000000000000000000000000000000000000..09d76da55fde2dd1b5408aeea2b2dc15d3349f67
--- /dev/null
+++ b/AVQt/output/Muxer.h
@@ -0,0 +1,64 @@
+//
+// Created by silas on 5/24/21.
+//
+
+#include "IPacketSink.h"
+
+#include <QThread>
+#include <QIODevice>
+
+#ifndef LIBAVQT_MUXER_H
+#define LIBAVQT_MUXER_H
+
+namespace AVQt {
+    class MuxerPrivate;
+
+    class Muxer : public QThread, public IPacketSink {
+    Q_OBJECT
+        Q_INTERFACES(AVQt::IPacketSink)
+
+        Q_DECLARE_PRIVATE(AVQt::Muxer)
+
+    protected:
+        void run() Q_DECL_OVERRIDE;
+
+    public:
+        explicit Muxer(QIODevice *outputDevice, QObject *parent = nullptr);
+
+        Muxer(Muxer &) = delete;
+
+        Muxer(Muxer &&other) noexcept;
+
+        bool isPaused() Q_DECL_OVERRIDE;
+
+        void init(IPacketSource *source, AVRational framerate, AVRational timebase, int64_t duration, AVCodecParameters *vParams,
+                  AVCodecParameters *aParams, AVCodecParameters *sParams) Q_DECL_OVERRIDE;
+
+        void deinit(IPacketSource *source) Q_DECL_OVERRIDE;
+
+        void start(IPacketSource *source) Q_DECL_OVERRIDE;
+
+        void stop(IPacketSource *source) Q_DECL_OVERRIDE;
+
+        void pause(bool p) Q_DECL_OVERRIDE;
+
+        void onPacket(IPacketSource *source, AVPacket *packet, int8_t packetType) Q_DECL_OVERRIDE;
+
+        void operator=(const Muxer &) = delete;
+
+    signals:
+
+        void started() Q_DECL_OVERRIDE;
+
+        void stopped() Q_DECL_OVERRIDE;
+
+        void paused(bool pause) Q_DECL_OVERRIDE;
+
+    protected:
+        [[maybe_unused]] explicit Muxer(MuxerPrivate &p);
+
+        MuxerPrivate *d_ptr;
+    };
+}
+
+#endif //LIBAVQT_MUXER_H
\ No newline at end of file
diff --git a/AVQt/output/OpenGLRenderer.cpp b/AVQt/output/OpenGLRenderer.cpp
index 8ce839be0e36325e412df022e94d4ed8f2a58982..d78699bc6edecea2909780f3839ffd8c735ef741 100644
--- a/AVQt/output/OpenGLRenderer.cpp
+++ b/AVQt/output/OpenGLRenderer.cpp
@@ -64,10 +64,10 @@ namespace AVQt {
             d->m_paused.store(false);
             qDebug("Started renderer");
 
-            showNormal();
-            requestActivate();
+            QMetaObject::invokeMethod(this, "showNormal", Qt::QueuedConnection);
+            QMetaObject::invokeMethod(this, "requestActivate", Qt::QueuedConnection);
+            QMetaObject::invokeMethod(this, "update", Qt::QueuedConnection);
 
-            update();
             started();
         }
         return 0;
diff --git a/AVQt/output/private/Muxer_p.h b/AVQt/output/private/Muxer_p.h
new file mode 100644
index 0000000000000000000000000000000000000000..ca43c3509aa082d74f34d217019e48d850edd489
--- /dev/null
+++ b/AVQt/output/private/Muxer_p.h
@@ -0,0 +1,55 @@
+//
+// Created by silas on 5/24/21.
+//
+
+#include "../Muxer.h"
+
+extern "C" {
+#include <libavformat/avformat.h>
+#include <libavcodec/avcodec.h>
+}
+
+#include <QtCore>
+
+#ifndef LIBAVQT_MUXER_P_H
+#define LIBAVQT_MUXER_P_H
+
+namespace AVQt {
+    class MuxerPrivate {
+    public:
+        MuxerPrivate(const MuxerPrivate &) = delete;
+
+        void operator=(const MuxerPrivate &) = delete;
+
+    private:
+        explicit MuxerPrivate(Muxer *q) : q_ptr(q) {};
+
+        Muxer *q_ptr;
+
+        QIODevice *m_outputDevice{nullptr};
+
+        QMutex m_initMutex{};
+        static constexpr size_t IOBUF_SIZE{4 * 1024};  // 4 KB
+        uint8_t *m_pIOBuffer{nullptr};
+        AVIOContext *m_pIOContext{nullptr};
+        AVFormatContext *m_pFormatContext{nullptr};
+        QMap<IPacketSource *, QMap<AVStream *, AVRational>> m_sourceStreamMap{};
+
+        QMutex m_inputQueueMutex{};
+        QQueue<QPair<AVPacket *, AVStream *>> m_inputQueue{};
+
+        std::atomic_bool m_running{false};
+        std::atomic_bool m_paused{false};
+        std::atomic_bool m_headerWritten{false};
+
+        friend class Muxer;
+
+        static int64_t seekIO(void *opaque, int64_t offset, int whence);
+
+        static int writeToIO(void *opaque, uint8_t *buf, int buf_size);
+
+        static bool packetQueueCompare(const QPair<AVPacket *, AVStream *> &packet1, const QPair<AVPacket *, AVStream *> &packet2);
+    };
+}
+
+#endif //LIBAVQT_MUXER_P_H
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9a4414e8199c6c64be30ee0a20db1450da0d4bd2..ad04765f27265ee05af97c7128d92a21e2d5ead6 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -3,10 +3,21 @@ project(LibAVQt)
 
 set(CMAKE_CXX_STANDARD 20)
 
+if (WIN32)
+    include_directories(${OPENAL_INCLUDE_DIR} ${FFMPEG_INCLUDE_DIR})
+    link_directories(${OPENAL_LIBRARY} ${FFMPEG_LIBRARY})
+    set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS on)
+endif ()
+
+if (WIN32)
+    set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
+    set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
+endif ()
+
 set(CMAKE_AUTOMOC on)
 set(CMAKE_AUTORCC on)
 set(CMAKE_AUTOUIC on)
 
 add_subdirectory(AVQt)
 include_directories(AVQt)
-add_subdirectory(Player)
+add_subdirectory(Player)
\ No newline at end of file
diff --git a/Player/main.cpp b/Player/main.cpp
index 7a4cef9d521e602b0ce52c88599296c90e85133a..83656d8347d3ce3f95d26da4e4d02202162c9c3c 100644
--- a/Player/main.cpp
+++ b/Player/main.cpp
@@ -43,6 +43,9 @@ int main(int argc, char *argv[]) {
     app = new QApplication(argc, argv);
     signal(SIGINT, &signalHandler);
     signal(SIGTERM, &signalHandler);
+
+    av_log_set_level(AV_LOG_DEBUG);
+    av_log_set_flags(AV_LOG_SKIP_REPEATED);
 //    signal(SIGQUIT, &signalHandler);
 
     start = std::chrono::system_clock::now();
@@ -57,7 +60,7 @@ int main(int argc, char *argv[]) {
         return 0;
     }
 
-    inputFile->open(QIODevice::ReadOnly);
+    inputFile->open(QIODevice::ReadWrite);
 
     AVQt::Demuxer demuxer(inputFile);
 //    AVQt::AudioDecoder decoder;
@@ -67,8 +70,10 @@ int main(int argc, char *argv[]) {
 //    decoder.registerCallback(&output);
 
     AVQt::IDecoder *videoDecoder;
+    AVQt::IEncoder *videoEncoder;
 #ifdef Q_OS_LINUX
     videoDecoder = new AVQt::DecoderVAAPI;
+    videoEncoder = new AVQt::EncoderVAAPI("hevc_vaapi");
 #elif defined(Q_OS_WINDOWS)
     videoDecoder = new AVQt::DecoderDXVA2();
 #else
@@ -76,16 +81,15 @@ int main(int argc, char *argv[]) {
 #endif
     AVQt::OpenGLRenderer renderer;
 
-//    AVQt::IEncoder *encoder = new AVQt::EncoderVAAPI("hevc_vaapi");
-
     demuxer.registerCallback(videoDecoder, AVQt::IPacketSource::CB_VIDEO);
-//    videoDecoder->registerCallback(encoder);
+//    videoDecoder->registerCallback(videoEncoder);
 
-//    QFile outputFile("output.ts");
+//    QFile outputFile("output.mp4");
 //    outputFile.open(QIODevice::ReadWrite | QIODevice::Truncate);
+//    outputFile.seek(0);
 //    AVQt::Muxer muxer(&outputFile);
 
-//    encoder->registerCallback(&muxer, AVQt::IPacketSource::CB_VIDEO);
+//    videoEncoder->registerCallback(&muxer, AVQt::IPacketSource::CB_VIDEO);
     videoDecoder->registerCallback(&renderer);
 
     renderer.setMinimumSize(QSize(360, 240));
@@ -105,7 +109,7 @@ int main(int argc, char *argv[]) {
 
     QObject::connect(app, &QApplication::aboutToQuit, [&] {
         demuxer.deinit();
-//        delete encoder;
+//        delete videoEncoder;
         delete videoDecoder;
     });