From aa8972f9424f82fa1fd27ea03d5839cb0561e8aa Mon Sep 17 00:00:00 2001 From: Oleksandr Nemesh Date: Mon, 30 Dec 2024 02:37:11 +0200 Subject: [PATCH] pimpl everything, speed up writeFrame method --- include/events.hpp | 37 ++++----- include/recorder.hpp | 40 ++++++---- include/render_settings.hpp | 5 +- src/compat.cpp | 31 +++++++- src/event-api/events.cpp | 7 +- src/recorder.cpp | 155 ++++++++++++++++++++---------------- 6 files changed, 164 insertions(+), 111 deletions(-) diff --git a/include/events.hpp b/include/events.hpp index 7fb807e..5858523 100644 --- a/include/events.hpp +++ b/include/events.hpp @@ -53,24 +53,17 @@ namespace impl { void* m_ptr; }; - class WriteFrameRecorderEvent : public geode::Event { - public: - WriteFrameRecorderEvent(void* ptr, const std::vector& frameData) { - m_ptr = ptr; - m_frameData = &frameData; - } - - void setResult(geode::Result<>&& result) {m_result = std::move(result);} - geode::Result<> getResult() {return m_result;} + struct Dummy {}; - void* getPtr() const {return m_ptr;} - - const std::vector& getFrameData() const {return *m_frameData;} + class GetWriteFrameFunctionEvent : public geode::Event { + public: + using writeFrame_t = geode::Result<>(Dummy::*)(std::vector const&); + GetWriteFrameFunctionEvent() = default; + void setFunction(writeFrame_t function) {m_function = function;} + writeFrame_t getFunction() const {return m_function;} private: - const std::vector* m_frameData; - void* m_ptr; - geode::Result<> m_result = DEFAULT_RESULT_ERROR; + writeFrame_t m_function; }; class CodecRecorderEvent : public geode::Event { @@ -134,7 +127,7 @@ class Recorder { Recorder() { impl::CreateRecorderEvent createEvent; createEvent.post(); - m_ptr = createEvent.getPtr(); + m_ptr = static_cast(createEvent.getPtr()); } ~Recorder() { @@ -184,9 +177,13 @@ class Recorder { * @warning Ensure that the frameData size matches the expected dimensions of the frame. */ geode::Result<> writeFrame(const std::vector& frameData) { - impl::WriteFrameRecorderEvent writeFrameEvent(m_ptr, frameData); - writeFrameEvent.post(); - return writeFrameEvent.getResult(); + static auto writeFrame = []{ + impl::GetWriteFrameFunctionEvent event; + event.post(); + return event.getFunction(); + }(); + if (!writeFrame) return geode::Err("Failed to call writeFrame function."); + return std::invoke(writeFrame, m_ptr, frameData); } /** @@ -203,7 +200,7 @@ class Recorder { return codecEvent.getCodecs(); } private: - void* m_ptr = nullptr; + impl::Dummy* m_ptr = nullptr; }; class AudioMixer { diff --git a/include/recorder.hpp b/include/recorder.hpp index 139de8d..341b869 100644 --- a/include/recorder.hpp +++ b/include/recorder.hpp @@ -38,6 +38,7 @@ class FFMPEG_API_DLL Recorder { * @return true if initialization is successful, false otherwise. */ geode::Result<> init(const RenderSettings& settings); + /** * @brief Stops the recording process and finalizes the output file. * @@ -75,24 +76,29 @@ class FFMPEG_API_DLL Recorder { geode::Result<> filterFrame(AVFrame* inputFrame, AVFrame* outputFrame); private: - AVFormatContext* m_formatContext = nullptr; - const AVCodec* m_codec = nullptr; - AVStream* m_videoStream = nullptr; - AVCodecContext* m_codecContext = nullptr; - AVBufferRef* m_hwDevice = nullptr; - AVFrame* m_frame = nullptr; - AVFrame* m_convertedFrame = nullptr; - AVFrame* m_filteredFrame = nullptr; - AVPacket* m_packet = nullptr; - SwsContext* m_swsCtx = nullptr; - AVFilterGraph* m_filterGraph = nullptr; - AVFilterContext* m_buffersrcCtx = nullptr; - AVFilterContext* m_buffersinkCtx = nullptr; - AVFilterContext* m_colorspaceCtx = nullptr; - AVFilterContext* m_vflipCtx = nullptr; + class Impl { + public: + AVFormatContext* m_formatContext = nullptr; + const AVCodec* m_codec = nullptr; + AVStream* m_videoStream = nullptr; + AVCodecContext* m_codecContext = nullptr; + AVBufferRef* m_hwDevice = nullptr; + AVFrame* m_frame = nullptr; + AVFrame* m_convertedFrame = nullptr; + AVFrame* m_filteredFrame = nullptr; + AVPacket* m_packet = nullptr; + SwsContext* m_swsCtx = nullptr; + AVFilterGraph* m_filterGraph = nullptr; + AVFilterContext* m_buffersrcCtx = nullptr; + AVFilterContext* m_buffersinkCtx = nullptr; + AVFilterContext* m_colorspaceCtx = nullptr; + AVFilterContext* m_vflipCtx = nullptr; + + size_t m_frameCount = 0; + bool m_init = false; + }; - size_t m_frameCount = 0; - bool m_init = false; + std::unique_ptr m_impl = nullptr; }; END_FFMPEG_NAMESPACE_V \ No newline at end of file diff --git a/include/render_settings.hpp b/include/render_settings.hpp index 42e0324..ae050a4 100644 --- a/include/render_settings.hpp +++ b/include/render_settings.hpp @@ -2,8 +2,9 @@ #include #include +#include "export.hpp" -namespace ffmpeg { +BEGIN_FFMPEG_NAMESPACE_V enum class PixelFormat : int { NONE = -1, @@ -321,4 +322,4 @@ struct RenderSettings { std::filesystem::path m_outputFile; }; -} \ No newline at end of file +END_FFMPEG_NAMESPACE_V \ No newline at end of file diff --git a/src/compat.cpp b/src/compat.cpp index 46f9709..35ab3f6 100644 --- a/src/compat.cpp +++ b/src/compat.cpp @@ -19,11 +19,38 @@ namespace ffmpeg { } }; + typedef struct RenderSettings { + HardwareAccelerationType m_hardwareAccelerationType; + PixelFormat m_pixelFormat; + std::string m_codec; + std::string m_colorspaceFilters; + int64_t m_bitrate; + uint32_t m_width; + uint32_t m_height; + uint16_t m_fps; + std::filesystem::path m_outputFile; + + v2::RenderSettings toV2() const { + return v2::RenderSettings { + .m_hardwareAccelerationType = m_hardwareAccelerationType, + .m_pixelFormat = m_pixelFormat, + .m_codec = m_codec, + .m_colorspaceFilters = m_colorspaceFilters, + .m_doVerticalFlip = false, + .m_bitrate = m_bitrate, + .m_width = m_width, + .m_height = m_height, + .m_fps = m_fps, + .m_outputFile = m_outputFile + }; + } + } RenderSettingsV1; + class FFMPEG_API_DLL Recorder { #define self reinterpret_cast(this) public: - bool init(const RenderSettings& settings) { - auto res = self->init(settings); + bool init(const RenderSettingsV1& settings) { + auto res = self->init(settings.toV2()); return res.isOk(); } diff --git a/src/event-api/events.cpp b/src/event-api/events.cpp index 9b470df..9e70b6d 100644 --- a/src/event-api/events.cpp +++ b/src/event-api/events.cpp @@ -29,9 +29,10 @@ using namespace geode::prelude; return ListenerResult::Stop; }); - new EventListener>(+[](WriteFrameRecorderEvent* e) { - ffmpeg::Recorder* ptr = (ffmpeg::Recorder*)e->getPtr(); - e->setResult(ptr->writeFrame(e->getFrameData())); + new EventListener>(+[](GetWriteFrameFunctionEvent* e) { + // this function is getting called a lot, so it would be better to cache the pointer + auto ptr = &ffmpeg::Recorder::writeFrame; + e->setFunction(reinterpret_cast(ptr)); return ListenerResult::Stop; }); diff --git a/src/recorder.cpp b/src/recorder.cpp index 43f3e6c..00b0973 100644 --- a/src/recorder.cpp +++ b/src/recorder.cpp @@ -40,25 +40,32 @@ const AVCodec* getCodecByName(const std::string& name) { } geode::Result<> Recorder::init(const RenderSettings& settings) { - int ret = avformat_alloc_output_context2(&m_formatContext, NULL, NULL, settings.m_outputFile.string().c_str()); + m_impl = std::make_unique(); + + int ret = avformat_alloc_output_context2(&m_impl->m_formatContext, NULL, NULL, settings.m_outputFile.string().c_str()); + const auto m_formatContext = m_impl->m_formatContext; if (!m_formatContext) return geode::Err("Could not create output context: " + utils::getErrorString(ret)); - m_codec = getCodecByName(settings.m_codec); + m_impl->m_codec = getCodecByName(settings.m_codec); + const auto m_codec = m_impl->m_codec; if (!m_codec) return geode::Err("Could not find encoder."); - m_videoStream = avformat_new_stream(m_formatContext, m_codec); + m_impl->m_videoStream = avformat_new_stream(m_formatContext, m_codec); + const auto m_videoStream = m_impl->m_videoStream; if (!m_videoStream) return geode::Err("Could not create video stream."); - m_codecContext = avcodec_alloc_context3(m_codec); + m_impl->m_codecContext = avcodec_alloc_context3(m_codec); + const auto m_codecContext = m_impl->m_codecContext; if (!m_codecContext) return geode::Err("Could not allocate video codec context."); - if(settings.m_hardwareAccelerationType != HardwareAccelerationType::NONE && (ret = av_hwdevice_ctx_create(&m_hwDevice, (AVHWDeviceType)settings.m_hardwareAccelerationType, NULL, NULL, 0)); ret < 0) + if(settings.m_hardwareAccelerationType != HardwareAccelerationType::NONE && (ret = av_hwdevice_ctx_create(&m_impl->m_hwDevice, (AVHWDeviceType)settings.m_hardwareAccelerationType, NULL, NULL, 0)); ret < 0) return geode::Err("Could not create hardware device context: " + utils::getErrorString(ret)); + const auto m_hwDevice = m_impl->m_hwDevice; m_codecContext->hw_device_ctx = m_hwDevice ? av_buffer_ref(m_hwDevice) : nullptr; m_codecContext->codec_id = m_codec->id; m_codecContext->bit_rate = settings.m_bitrate; @@ -102,7 +109,8 @@ geode::Result<> Recorder::init(const RenderSettings& settings) { if (ret = avformat_write_header(m_formatContext, nullptr); ret < 0) return geode::Err("Could not write header: " + utils::getErrorString(ret)); - m_frame = av_frame_alloc(); + m_impl->m_frame = av_frame_alloc(); + const auto m_frame = m_impl->m_frame; m_frame->format = m_codecContext->pix_fmt; m_frame->width = m_codecContext->width; m_frame->height = m_codecContext->height; @@ -110,25 +118,26 @@ geode::Result<> Recorder::init(const RenderSettings& settings) { if (ret = av_image_alloc(m_frame->data, m_frame->linesize, m_codecContext->width, m_codecContext->height, (AVPixelFormat)settings.m_pixelFormat, 32); ret < 0) return geode::Err("Could not allocate raw picture buffer: " + utils::getErrorString(ret)); - m_convertedFrame = av_frame_alloc(); + m_impl->m_convertedFrame = av_frame_alloc(); + const auto m_convertedFrame = m_impl->m_convertedFrame; m_convertedFrame->format = m_codecContext->pix_fmt; m_convertedFrame->width = m_codecContext->width; m_convertedFrame->height = m_codecContext->height; if(ret = av_image_alloc(m_convertedFrame->data, m_convertedFrame->linesize, m_convertedFrame->width, m_convertedFrame->height, m_codecContext->pix_fmt, 32); ret < 0) return geode::Err("Could not allocate raw picture buffer: " + utils::getErrorString(ret)); - m_filteredFrame = av_frame_alloc(); + m_impl->m_filteredFrame = av_frame_alloc(); - m_packet = av_packet_alloc(); + m_impl->m_packet = av_packet_alloc(); - m_packet->data = nullptr; - m_packet->size = 0; + m_impl->m_packet->data = nullptr; + m_impl->m_packet->size = 0; int inputPixelFormat = (int)settings.m_pixelFormat; if(!settings.m_colorspaceFilters.empty() || settings.m_doVerticalFlip) { - m_filterGraph = avfilter_graph_alloc(); - if (!m_filterGraph) + m_impl->m_filterGraph = avfilter_graph_alloc(); + if (!m_impl->m_filterGraph) return geode::Err("Could not allocate filter graph."); const AVFilter* buffersrc = avfilter_get_by_name("buffer"); @@ -142,100 +151,112 @@ geode::Result<> Recorder::init(const RenderSettings& settings) { m_codecContext->width, m_codecContext->height, m_codecContext->pix_fmt, m_codecContext->time_base.num, m_codecContext->time_base.den, m_codecContext->sample_aspect_ratio.num, m_codecContext->sample_aspect_ratio.den); + const auto m_filterGraph = m_impl->m_filterGraph; - if(ret = avfilter_graph_create_filter(&m_buffersrcCtx, buffersrc, "in", args, nullptr, m_filterGraph); ret < 0) { - avfilter_graph_free(&m_filterGraph); + if(ret = avfilter_graph_create_filter(&m_impl->m_buffersrcCtx, buffersrc, "in", args, nullptr, m_filterGraph); ret < 0) { + avfilter_graph_free(&m_impl->m_filterGraph); return geode::Err("Could not create input for filter graph: " + utils::getErrorString(ret)); } - if(ret = avfilter_graph_create_filter(&m_buffersinkCtx, buffersink, "out", nullptr, nullptr, m_filterGraph); ret < 0) { - avfilter_graph_free(&m_filterGraph); + if(ret = avfilter_graph_create_filter(&m_impl->m_buffersinkCtx, buffersink, "out", nullptr, nullptr, m_filterGraph); ret < 0) { + avfilter_graph_free(&m_impl->m_filterGraph); return geode::Err("Could not create output for filter graph: " + utils::getErrorString(ret)); } if(!settings.m_colorspaceFilters.empty()) { - if(ret = avfilter_graph_create_filter(&m_colorspaceCtx, colorspace, "colorspace", settings.m_colorspaceFilters.c_str(), nullptr, m_filterGraph); ret < 0) { - avfilter_graph_free(&m_filterGraph); + if(ret = avfilter_graph_create_filter(&m_impl->m_colorspaceCtx, colorspace, "colorspace", settings.m_colorspaceFilters.c_str(), nullptr, m_filterGraph); ret < 0) { + avfilter_graph_free(&m_impl->m_filterGraph); return geode::Err("Could not create colorspace for filter graph: " + utils::getErrorString(ret)); } - if(ret = avfilter_link(m_buffersrcCtx, 0, m_colorspaceCtx, 0); ret < 0) { - avfilter_graph_free(&m_filterGraph); + if(ret = avfilter_link(m_impl->m_buffersrcCtx, 0, m_impl->m_colorspaceCtx, 0); ret < 0) { + avfilter_graph_free(&m_impl->m_filterGraph); return geode::Err("Could not link filters: " + utils::getErrorString(ret)); } - if(ret = avfilter_link(m_colorspaceCtx, 0, m_buffersinkCtx, 0); ret < 0) { - avfilter_graph_free(&m_filterGraph); + if(ret = avfilter_link(m_impl->m_colorspaceCtx, 0, m_impl->m_buffersinkCtx, 0); ret < 0) { + avfilter_graph_free(&m_impl->m_filterGraph); return geode::Err("Could not link filters: " + utils::getErrorString(ret)); } } if(settings.m_doVerticalFlip) { - if(ret = avfilter_graph_create_filter(&m_vflipCtx, vflip, "vflip", nullptr, nullptr, m_filterGraph); ret < 0) { - avfilter_graph_free(&m_filterGraph); + if(ret = avfilter_graph_create_filter(&m_impl->m_vflipCtx, vflip, "vflip", nullptr, nullptr, m_filterGraph); ret < 0) { + avfilter_graph_free(&m_impl->m_filterGraph); return geode::Err("Could not create vflip for filter graph: " + utils::getErrorString(ret)); } - if(ret = avfilter_link(m_buffersrcCtx, 0, m_vflipCtx, 0); ret < 0) { - avfilter_graph_free(&m_filterGraph); + if(ret = avfilter_link(m_impl->m_buffersrcCtx, 0, m_impl->m_vflipCtx, 0); ret < 0) { + avfilter_graph_free(&m_impl->m_filterGraph); return geode::Err("Could not link filters: " + utils::getErrorString(ret)); } - if(ret = avfilter_link(m_vflipCtx, 0, m_buffersinkCtx, 0); ret < 0) { - avfilter_graph_free(&m_filterGraph); + if(ret = avfilter_link(m_impl->m_vflipCtx, 0, m_impl->m_buffersinkCtx, 0); ret < 0) { + avfilter_graph_free(&m_impl->m_filterGraph); return geode::Err("Could not link filters: " + utils::getErrorString(ret)); } } if (ret = avfilter_graph_config(m_filterGraph, nullptr); ret < 0) { - avfilter_graph_free(&m_filterGraph); + avfilter_graph_free(&m_impl->m_filterGraph); return geode::Err("Could not configure filter graph: " + utils::getErrorString(ret)); } - inputPixelFormat = av_buffersink_get_format(m_buffersinkCtx); + inputPixelFormat = av_buffersink_get_format(m_impl->m_buffersinkCtx); } - m_swsCtx = sws_getContext(m_codecContext->width, m_codecContext->height, (AVPixelFormat)inputPixelFormat, m_codecContext->width, + m_impl->m_swsCtx = sws_getContext(m_codecContext->width, m_codecContext->height, (AVPixelFormat)inputPixelFormat, m_codecContext->width, m_codecContext->height, m_codecContext->pix_fmt, SWS_FAST_BILINEAR, nullptr, nullptr, nullptr); - if (!m_swsCtx) + if (!m_impl->m_swsCtx) return geode::Err("Could not create sws context."); - m_frameCount = 0; + m_impl->m_frameCount = 0; - m_init = true; + m_impl->m_init = true; return geode::Ok(); } geode::Result<> Recorder::writeFrame(const std::vector& frameData) { - if (!m_init || !m_frame) + if (!m_impl->m_init || !m_impl->m_frame) return geode::Err("Recorder is not initialized."); + const auto m_frame = m_impl->m_frame; + if(frameData.size() != m_frame->linesize[0] * m_frame->height) return geode::Err("Frame data size does not match expected dimensions."); - if(m_buffersrcCtx) { + if(m_impl->m_buffersrcCtx) { + const auto m_filteredFrame = m_impl->m_filteredFrame; + std::memcpy(m_frame->data[0], frameData.data(), frameData.size()); geode::Result<> res = filterFrame(m_frame, m_filteredFrame); if(res.isErr()) return res; + const auto m_convertedFrame = m_impl->m_convertedFrame; + sws_scale( - m_swsCtx, m_filteredFrame->data, m_filteredFrame->linesize, 0, m_filteredFrame->height, + m_impl->m_swsCtx, m_filteredFrame->data, m_filteredFrame->linesize, 0, m_filteredFrame->height, m_convertedFrame->data, m_convertedFrame->linesize); } else { const uint8_t* srcData[1] = { frameData.data() }; - int srcLinesize[1] = { m_frame->linesize[0] }; + const auto m_convertedFrame = m_impl->m_convertedFrame; sws_scale( - m_swsCtx, srcData, m_frame->linesize, 0, m_frame->height, + m_impl->m_swsCtx, srcData, m_frame->linesize, 0, m_frame->height, m_convertedFrame->data, m_convertedFrame->linesize); } - m_convertedFrame->pts = m_frameCount++; + const auto m_convertedFrame = m_impl->m_convertedFrame; + const auto m_packet = m_impl->m_packet; + const auto m_codecContext = m_impl->m_codecContext; + const auto m_videoStream = m_impl->m_videoStream; + + m_convertedFrame->pts = m_impl->m_frameCount++; int ret = avcodec_send_frame(m_codecContext, m_convertedFrame); if (ret < 0) @@ -251,23 +272,23 @@ geode::Result<> Recorder::writeFrame(const std::vector& frameData) { av_packet_rescale_ts(m_packet, m_codecContext->time_base, m_videoStream->time_base); m_packet->stream_index = m_videoStream->index; - av_interleaved_write_frame(m_formatContext, m_packet); + av_interleaved_write_frame(m_impl->m_formatContext, m_packet); av_packet_unref(m_packet); } - av_frame_unref(m_filteredFrame); + av_frame_unref(m_impl->m_filteredFrame); return geode::Ok(); } geode::Result<> Recorder::filterFrame(AVFrame* inputFrame, AVFrame* outputFrame) { int ret = 0; - if (ret = av_buffersrc_add_frame(m_buffersrcCtx, inputFrame); ret < 0) { - avfilter_graph_free(&m_filterGraph); + if (ret = av_buffersrc_add_frame(m_impl->m_buffersrcCtx, inputFrame); ret < 0) { + avfilter_graph_free(&m_impl->m_filterGraph); return geode::Err("Could not feed frame to filter graph: " + utils::getErrorString(ret)); } - if (ret = av_buffersink_get_frame(m_buffersinkCtx, outputFrame); ret < 0) { + if (ret = av_buffersink_get_frame(m_impl->m_buffersinkCtx, outputFrame); ret < 0) { av_frame_unref(outputFrame); return geode::Err("Could not retrieve frame from filter graph: " + utils::getErrorString(ret)); } @@ -276,39 +297,39 @@ geode::Result<> Recorder::filterFrame(AVFrame* inputFrame, AVFrame* outputFrame) } void Recorder::stop() { - if(!m_init) + if(!m_impl->m_init) return; - m_init = false; + m_impl->m_init = false; - avcodec_send_frame(m_codecContext, nullptr); - while (avcodec_receive_packet(m_codecContext, m_packet) == 0) { - av_packet_rescale_ts(m_packet, m_codecContext->time_base, m_videoStream->time_base); - m_packet->stream_index = m_videoStream->index; - av_interleaved_write_frame(m_formatContext, m_packet); - av_packet_unref(m_packet); + avcodec_send_frame(m_impl->m_codecContext, nullptr); + while (avcodec_receive_packet(m_impl->m_codecContext, m_impl->m_packet) == 0) { + av_packet_rescale_ts(m_impl->m_packet, m_impl->m_codecContext->time_base, m_impl->m_videoStream->time_base); + m_impl->m_packet->stream_index = m_impl->m_videoStream->index; + av_interleaved_write_frame(m_impl->m_formatContext, m_impl->m_packet); + av_packet_unref(m_impl->m_packet); } - av_write_trailer(m_formatContext); + av_write_trailer(m_impl->m_formatContext); - avcodec_free_context(&m_codecContext); - av_frame_free(&m_frame); - av_frame_free(&m_convertedFrame); - if (!(m_formatContext->oformat->flags & AVFMT_NOFILE)) { - avio_close(m_formatContext->pb); + avcodec_free_context(&m_impl->m_codecContext); + av_frame_free(&m_impl->m_frame); + av_frame_free(&m_impl->m_convertedFrame); + if (!(m_impl->m_formatContext->oformat->flags & AVFMT_NOFILE)) { + avio_close(m_impl->m_formatContext->pb); } - avformat_free_context(m_formatContext); + avformat_free_context(m_impl->m_formatContext); - if(m_filterGraph) { - avfilter_graph_free(&m_filterGraph); - av_frame_free(&m_filteredFrame); + if(m_impl->m_filterGraph) { + avfilter_graph_free(&m_impl->m_filterGraph); + av_frame_free(&m_impl->m_filteredFrame); } - if (m_hwDevice) { - av_buffer_unref(&m_hwDevice); + if (m_impl->m_hwDevice) { + av_buffer_unref(&m_impl->m_hwDevice); } - av_packet_free(&m_packet); + av_packet_free(&m_impl->m_packet); } END_FFMPEG_NAMESPACE_V \ No newline at end of file