diff --git a/src/media/media_io_handle.cpp b/src/media/media_io_handle.cpp
index d276380266a64fa4b7f64f033aba5c858a502fe4..9e56114a760b2b1b10dac02ca2fb2356e6cfee90 100644
--- a/src/media/media_io_handle.cpp
+++ b/src/media/media_io_handle.cpp
@@ -31,12 +31,20 @@ MediaIOHandle::MediaIOHandle(std::size_t buffer_size,
                              void *opaque) : ctx_(0)
 
 {
-    buf_.reserve(buffer_size);
-    ctx_ = avio_alloc_context(buf_.data(), buffer_size, writeable, opaque, read_cb,
+    /* FFmpeg doesn't alloc the buffer for the first time, but it does free and
+     * alloc it afterwards.
+     * Don't directly use malloc because av_malloc is optimized for memory alignment.
+     */
+    auto buf = static_cast<uint8_t*>(av_malloc(buffer_size));
+    ctx_ = avio_alloc_context(buf, buffer_size, writeable, opaque, read_cb,
                               write_cb, seek_cb);
     ctx_->max_packet_size = buffer_size;
 }
 
-MediaIOHandle::~MediaIOHandle() { av_free(ctx_); }
+MediaIOHandle::~MediaIOHandle()
+{
+    av_free(ctx_->buffer);
+    av_free(ctx_);
+}
 
 } // namespace ring
diff --git a/src/media/media_io_handle.h b/src/media/media_io_handle.h
index 82f06c806a6af8a408902bd38e6e3fd420492906..ce68cb1ea730240ff336e46874e2cf8bebb5e174 100644
--- a/src/media/media_io_handle.h
+++ b/src/media/media_io_handle.h
@@ -52,7 +52,6 @@ public:
 private:
     NON_COPYABLE(MediaIOHandle);
     AVIOContext *ctx_;
-    std::vector<uint8_t> buf_ {};
 };
 
 } // namespace ring