From 4212ce6abbefff8677d328a516849c290a694aae Mon Sep 17 00:00:00 2001 From: NDrLEUxcwQsgKUWlrFELocR1z7QP <54683950+maxnut@users.noreply.github.com> Date: Fri, 29 Nov 2024 00:51:39 +0100 Subject: [PATCH] better error handling --- CMakeLists.txt | 1 + include/audio_mixer.hpp | 6 +- include/recorder.hpp | 8 +- mod.json | 2 +- src/audio_mixer.cpp | 213 +++++++++++++++------------------------- src/recorder.cpp | 154 ++++++++++++++--------------- src/utils.cpp | 40 ++++++++ src/utils.hpp | 11 +++ 8 files changed, 215 insertions(+), 220 deletions(-) create mode 100644 src/utils.cpp create mode 100644 src/utils.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 742e375..7dea605 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,6 +14,7 @@ project(ffmpeg-api VERSION 1.0.0) add_library(${PROJECT_NAME} SHARED src/recorder.cpp src/audio_mixer.cpp + src/utils.cpp # Add any extra C++ source files here ) diff --git a/include/audio_mixer.hpp b/include/audio_mixer.hpp index b17eea6..406fe16 100644 --- a/include/audio_mixer.hpp +++ b/include/audio_mixer.hpp @@ -2,6 +2,8 @@ #include "export.hpp" +#include + #include namespace ffmpeg { @@ -21,7 +23,7 @@ class FFMPEG_API_DLL AudioMixer { * @warning The audio file is expected to contain stereo (dual-channel) audio. Using other formats might lead to unexpected results. * @warning The video file is expected to contain a single video stream. Only the first video stream will be copied. */ - void mixVideoAudio(std::filesystem::path videoFile, std::filesystem::path audioFile, std::filesystem::path outputMp4File); + geode::Result mixVideoAudio(std::filesystem::path videoFile, std::filesystem::path audioFile, std::filesystem::path outputMp4File); /** * @deprecated sampleRate parameter is no longer used. Use the other overload of this function instead. @@ -54,7 +56,7 @@ class FFMPEG_API_DLL AudioMixer { * @warning The raw audio data is expected to be stereo (dual-channel). Using mono or multi-channel audio might lead to issues. * @warning The video file is expected to contain a single video stream. Only the first video stream will be copied. */ - void mixVideoRaw(const std::filesystem::path& videoFile, const std::vector& raw, const std::filesystem::path &outputMp4File); + geode::Result mixVideoRaw(const std::filesystem::path& videoFile, const std::vector& raw, const std::filesystem::path &outputMp4File); }; } \ No newline at end of file diff --git a/include/recorder.hpp b/include/recorder.hpp index 4e31a2c..847dc7a 100644 --- a/include/recorder.hpp +++ b/include/recorder.hpp @@ -3,6 +3,8 @@ #include "render_settings.hpp" #include "export.hpp" +#include + #include #include #include @@ -35,7 +37,7 @@ class FFMPEG_API_DLL Recorder { * * @return true if initialization is successful, false otherwise. */ - bool init(const RenderSettings& settings); + geode::Result init(const RenderSettings& settings); /** * @brief Stops the recording process and finalizes the output file. * @@ -57,7 +59,7 @@ class FFMPEG_API_DLL Recorder { * * @warning Ensure that the frameData size matches the expected dimensions of the frame. */ - bool writeFrame(const std::vector& frameData); + geode::Result writeFrame(const std::vector& frameData); /** * @brief Retrieves a list of available codecs for video encoding. @@ -70,7 +72,7 @@ class FFMPEG_API_DLL Recorder { std::vector getAvailableCodecs(); private: - void filterFrame(AVFrame* inputFrame, AVFrame* outputFrame); + geode::Result filterFrame(AVFrame* inputFrame, AVFrame* outputFrame); private: AVFormatContext* m_formatContext = nullptr; diff --git a/mod.json b/mod.json index 9868f1a..794b0e5 100644 --- a/mod.json +++ b/mod.json @@ -13,7 +13,7 @@ }, "id": "eclipse.ffmpeg-api", "name": "FFmpeg API", - "version": "v1.1.2", + "version": "v1.1.3", "developers": ["Eclipse Team", "maxnu"], "description": "Interaction with FFmpeg made easy", "tags": ["utility", "offline", "developer"], diff --git a/src/audio_mixer.cpp b/src/audio_mixer.cpp index 0242f2e..996f6bc 100644 --- a/src/audio_mixer.cpp +++ b/src/audio_mixer.cpp @@ -1,7 +1,5 @@ #include "audio_mixer.hpp" - -#include -#include +#include "utils.hpp" extern "C" { #include @@ -10,18 +8,15 @@ extern "C" { } //https://gist.github.com/royshil/fff30890c7c19a4889f0a148101c9dff -std::vector readAudioFile(const char *filename, int targetSampleRate, AVSampleFormat targetSampleFormat, AVCodecParameters* outCodecParams) +geode::Result> readAudioFile(const char *filename, int targetSampleRate, AVSampleFormat targetSampleFormat, AVCodecParameters* outCodecParams) { + int ret = 0; AVFormatContext *formatContext = nullptr; - if (avformat_open_input(&formatContext, filename, nullptr, nullptr) != 0) { - geode::log::error("Error opening file"); - return {}; - } + if (ret = avformat_open_input(&formatContext, filename, nullptr, nullptr); ret != 0) + return geode::Err("Error opening file: " + ffmpeg::utils::getErrorString(ret)); - if (avformat_find_stream_info(formatContext, nullptr) < 0) { - geode::log::error("Error finding stream information"); - return {}; - } + if (ret = avformat_find_stream_info(formatContext, nullptr); ret < 0) + return geode::Err("Error finding stream information: " + ffmpeg::utils::getErrorString(ret)); int audioStreamIndex = -1; for (unsigned int i = 0; i < formatContext->nb_streams; i++) { @@ -31,33 +26,23 @@ std::vector readAudioFile(const char *filename, int targetSampleRate, AVS } } - if (audioStreamIndex == -1) { - geode::log::error("No audio stream found"); - return {}; - } + if (audioStreamIndex == -1) + return geode::Err("No audio stream found"); AVCodecParameters *codecParams = formatContext->streams[audioStreamIndex]->codecpar; const AVCodec *codec = avcodec_find_decoder(codecParams->codec_id); - if (!codec) { - geode::log::error("Decoder not found"); - return {}; - } + if (!codec) + return geode::Err("Decoder not found"); AVCodecContext *codecContext = avcodec_alloc_context3(codec); - if (!codecContext) { - geode::log::error("Failed to allocate codec context"); - return {}; - } + if (!codecContext) + return geode::Err("Failed to allocate codec context"); - if (avcodec_parameters_to_context(codecContext, codecParams) < 0) { - geode::log::error("Failed to copy codec parameters to codec context"); - return {}; - } + if (ret = avcodec_parameters_to_context(codecContext, codecParams); ret < 0) + return geode::Err("Failed to copy codec parameters to codec context: " + ffmpeg::utils::getErrorString(ret)); - if (avcodec_open2(codecContext, codec, nullptr) < 0) { - geode::log::error("Failed to open codec"); - return {}; - } + if (ret = avcodec_open2(codecContext, codec, nullptr); ret < 0) + return geode::Err("Failed to open codec: " + ffmpeg::utils::getErrorString(ret)); *outCodecParams = *codecParams; @@ -67,23 +52,15 @@ std::vector readAudioFile(const char *filename, int targetSampleRate, AVS AVChannelLayout ch_layout; av_channel_layout_from_string(&ch_layout, "2 channels"); SwrContext *swr = nullptr; - int ret; ret = swr_alloc_set_opts2(&swr, &ch_layout, targetSampleFormat, targetSampleRate, &(codecContext->ch_layout), codecContext->sample_fmt, codecContext->sample_rate, 0, nullptr); - if (ret < 0) { - char errbuf[AV_ERROR_MAX_STRING_SIZE]; - av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE); - geode::log::error("Failed to set up swr context: {}", errbuf); - return {}; - } + if (ret < 0) + return geode::Err("Failed to set up swr context: " + ffmpeg::utils::getErrorString(ret)); + ret = swr_init(swr); - if (ret < 0) { - char errbuf[AV_ERROR_MAX_STRING_SIZE]; - av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE); - geode::log::error("Failed to initialize swr context: {}", errbuf); - return {}; - } + if (ret < 0) + return geode::Err("Failed to initialize swr context: " + ffmpeg::utils::getErrorString(ret)); std::vector audioFrames; @@ -96,13 +73,9 @@ std::vector readAudioFile(const char *filename, int targetSampleRate, AVS while (avcodec_receive_frame(codecContext, frame) == 0) { int ret = swr_convert(swr, reinterpret_cast(convertBuffer), 4096, frame->data, frame->nb_samples); - if (ret < 0) { - char errbuf[AV_ERROR_MAX_STRING_SIZE]; - av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE); - geode::log::error("Failed to convert audio frame: {}", errbuf); - return {}; - } - + if (ret < 0) + return geode::Err("Failed to convert audio frame: " + ffmpeg::utils::getErrorString(ret)); + for (int i = 0; i < ret; ++i) { audioFrames.push_back(convertBuffer[0][i]); audioFrames.push_back(convertBuffer[1][i]); @@ -120,10 +93,10 @@ std::vector readAudioFile(const char *filename, int targetSampleRate, AVS avcodec_free_context(&codecContext); avformat_close_input(&formatContext); - return audioFrames; + return geode::Ok(audioFrames); } -std::vector resampleAudio(const std::vector& inputAudio, int inputSampleRate, int targetSampleRate) { +geode::Result> resampleAudio(const std::vector& inputAudio, int inputSampleRate, int targetSampleRate) { SwrContext *swrCtx = nullptr; int ret; AVChannelLayout ch_layout; @@ -133,19 +106,12 @@ std::vector resampleAudio(const std::vector& inputAudio, int input targetSampleRate, &ch_layout, AV_SAMPLE_FMT_FLT, inputSampleRate, 0, nullptr); - if (ret < 0) { - char errbuf[AV_ERROR_MAX_STRING_SIZE]; - av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE); - geode::log::error("Failed to set up swr context: {}", errbuf); - return {}; - } + if (ret < 0) + return geode::Err("Failed to set up swr context: " + ffmpeg::utils::getErrorString(ret)); + ret = swr_init(swrCtx); - if (ret < 0) { - char errbuf[AV_ERROR_MAX_STRING_SIZE]; - av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE); - geode::log::error("Failed to initialize swr context: {}", errbuf); - return {}; - } + if (ret < 0) + return geode::Err("Failed to initialize swr context: " + ffmpeg::utils::getErrorString(ret)); constexpr int chunkSize = 4096; constexpr int numChannels = 2; @@ -164,67 +130,64 @@ std::vector resampleAudio(const std::vector& inputAudio, int input int inputSamples = currentChunkSize / numChannels; int resampledSamples = swr_convert(swrCtx, outData, maxOutputSamples, inData, inputSamples); - if (ret < 0) { - char errbuf[AV_ERROR_MAX_STRING_SIZE]; - av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE); - geode::log::error("Failed to convert audio frame: {}", errbuf); - return {}; - } + if (ret < 0) + return geode::Err("Failed to convert audio frame: " + ffmpeg::utils::getErrorString(ret)); outputAudio.insert(outputAudio.end(), outputChunk.begin(), outputChunk.begin() + resampledSamples * numChannels); } swr_free(&swrCtx); - return outputAudio; + return geode::Ok(outputAudio); } namespace ffmpeg { - void AudioMixer::mixVideoAudio(std::filesystem::path videoFile, std::filesystem::path audioFile, std::filesystem::path outputMp4File) { + geode::Result AudioMixer::mixVideoAudio(std::filesystem::path videoFile, std::filesystem::path audioFile, std::filesystem::path outputMp4File) { constexpr int frameSize = 1024; AVFormatContext* wavFormatContext = nullptr; - if (avformat_open_input(&wavFormatContext, audioFile.string().c_str(), nullptr, nullptr) < 0) { - geode::log::error("Could not open file."); - return; - } + int ret = 0; + if (ret = avformat_open_input(&wavFormatContext, audioFile.string().c_str(), nullptr, nullptr); ret < 0) + return geode::Err("Could not open file: " + utils::getErrorString(ret)); AVCodecParameters inputAudioParams{}; - std::vector raw = readAudioFile(audioFile.string().c_str(), 44100, AV_SAMPLE_FMT_FLTP, &inputAudioParams); + geode::Result> raw = readAudioFile(audioFile.string().c_str(), 44100, AV_SAMPLE_FMT_FLTP, &inputAudioParams); - mixVideoRaw(videoFile, raw, outputMp4File); + if(raw.isErr()) + return geode::Err(raw.unwrapErr()); + + geode::Result res = mixVideoRaw(videoFile, raw.unwrap(), outputMp4File); avformat_close_input(&wavFormatContext); + + return res; } void AudioMixer::mixVideoRaw(std::filesystem::path videoFile, const std::vector& raw, std::filesystem::path outputMp4File, uint32_t) { mixVideoRaw(videoFile, raw, outputMp4File); } - void AudioMixer::mixVideoRaw(const std::filesystem::path& videoFile, const std::vector& raw, const std::filesystem::path &outputMp4File) { + geode::Result AudioMixer::mixVideoRaw(const std::filesystem::path& videoFile, const std::vector& raw, const std::filesystem::path &outputMp4File) { constexpr int frameSize = 1024; constexpr uint32_t sampleRate = 44100; + + int ret = 0; AVFormatContext* videoFormatContext = nullptr; - if (avformat_open_input(&videoFormatContext, videoFile.string().c_str(), nullptr, nullptr) < 0) { - geode::log::error("Could not open MP4 file."); - return; - } + if (ret = avformat_open_input(&videoFormatContext, videoFile.string().c_str(), nullptr, nullptr); ret < 0) + return geode::Err("Could not open MP4 file: " + utils::getErrorString(ret)); AVFormatContext* outputFormatContext = nullptr; - avformat_alloc_output_context2(&outputFormatContext, nullptr, nullptr, outputMp4File.string().c_str()); + ret = avformat_alloc_output_context2(&outputFormatContext, nullptr, nullptr, outputMp4File.string().c_str()); if (!outputFormatContext) { - geode::log::error("Could not create output context."); avformat_close_input(&videoFormatContext); - return; + return geode::Err("Could not create output context: " + utils::getErrorString(ret)); } AVStream* outputVideoStream = avformat_new_stream(outputFormatContext, nullptr); - if (!outputVideoStream) { - geode::log::error("Failed to create video stream."); - return; - } + if (!outputVideoStream) + return geode::Err("Failed to create video stream."); int videoStreamIndex = -1; for (unsigned int i = 0; i < videoFormatContext->nb_streams; i++) { @@ -239,10 +202,8 @@ namespace ffmpeg { outputVideoStream->codecpar->codec_tag = 0; AVStream* outputAudioStream = avformat_new_stream(outputFormatContext, nullptr); - if (!outputAudioStream) { - geode::log::error("Failed to create audio stream."); - return; - } + if (!outputAudioStream) + return geode::Err("Failed to create audio stream."); constexpr int channels = 2; @@ -250,12 +211,12 @@ namespace ffmpeg { auto duration = static_cast(videoFormatContext->duration) / AV_TIME_BASE; auto newSampleRate = raw.size() / duration / channels; - std::vector resampled = resampleAudio(raw, newSampleRate, 44100); + geode::Result> resampledRes = resampleAudio(raw, newSampleRate, 44100); - if(resampled.empty()) { - geode::log::error("Failed to resample audio."); - return; - } + if(resampledRes.isErr()) + return geode::Err(resampledRes.unwrapErr()); + + auto resampled = resampledRes.unwrap(); outputAudioStream->codecpar->codec_tag = 0; outputAudioStream->codecpar->codec_type = AVMEDIA_TYPE_AUDIO; @@ -275,37 +236,31 @@ namespace ffmpeg { const AVCodec *audioCodec = avcodec_find_encoder(videoCodecParams->codec_id); AVCodecContext *audio_codec_context_encoder = avcodec_alloc_context3(audioCodec); - if (avcodec_parameters_to_context(audio_codec_context_encoder, videoCodecParams) < 0) { - geode::log::error("Failed to copy codec parameters to codec context."); - return; - } + if (ret = avcodec_parameters_to_context(audio_codec_context_encoder, videoCodecParams); ret < 0) + return geode::Err("Could not copy codec parameters to codec context: " + utils::getErrorString(ret)); audio_codec_context_encoder->sample_rate = sampleRate; audio_codec_context_encoder->ch_layout = AV_CHANNEL_LAYOUT_STEREO; audio_codec_context_encoder->sample_fmt = AV_SAMPLE_FMT_FLTP; audio_codec_context_encoder->time_base = AVRational{1, static_cast(sampleRate)}; - int ret = avcodec_open2(audio_codec_context_encoder, audioCodec, nullptr); - if (ret < 0) { - geode::log::error("Failed to open encoder."); - return; - } + ret = avcodec_open2(audio_codec_context_encoder, audioCodec, nullptr); + if (ret < 0) + return geode::Err("Could not open encoder: " + utils::getErrorString(ret)); if (!(outputFormatContext->oformat->flags & AVFMT_NOFILE)) { - if (avio_open(&outputFormatContext->pb, outputMp4File.string().c_str(), AVIO_FLAG_WRITE) < 0) { - geode::log::error("Could not open output file."); + if (ret = avio_open(&outputFormatContext->pb, outputMp4File.string().c_str(), AVIO_FLAG_WRITE); ret < 0) { avformat_free_context(outputFormatContext); avformat_close_input(&videoFormatContext); - return; + return geode::Err("Could not open output file: " + utils::getErrorString(ret)); } } - if (avformat_write_header(outputFormatContext, nullptr) < 0) { - geode::log::error("Could not write header to output file."); + if (ret = avformat_write_header(outputFormatContext, nullptr); ret < 0) { avio_closep(&outputFormatContext->pb); avformat_free_context(outputFormatContext); avformat_close_input(&videoFormatContext); - return; + return geode::Err("Could not write header to output file: " + utils::getErrorString(ret)); } AVPacket packet; @@ -323,10 +278,8 @@ namespace ffmpeg { int pts = 0; AVFrame* audioFrame = av_frame_alloc(); - if (!audioFrame) { - geode::log::error("Could not allocate audio frame."); - return; - } + if (!audioFrame) + return geode::Err("Could not allocate audio frame."); audioFrame->format = AV_SAMPLE_FMT_FLTP; audioFrame->ch_layout = AV_CHANNEL_LAYOUT_STEREO; @@ -339,20 +292,16 @@ namespace ffmpeg { pts += samplesToEncode; - if (av_frame_get_buffer(audioFrame, 0) < 0) { - geode::log::error("Could not allocate audio buffer."); - continue; - } + if (ret = av_frame_get_buffer(audioFrame, 0); ret < 0) + return geode::Err("Could not allocate audio buffer: " + utils::getErrorString(ret)); for (int j = 0; j < samplesToEncode; ++j) { reinterpret_cast(audioFrame->data[0])[j] = resampled[i + j * channels]; reinterpret_cast(audioFrame->data[1])[j] = resampled[i + j * channels + 1]; } - if (avcodec_send_frame(audio_codec_context_encoder, audioFrame) < 0) { - geode::log::error("Error sending audio frame to encoder."); - continue; - } + if (ret = avcodec_send_frame(audio_codec_context_encoder, audioFrame); ret < 0) + return geode::Err("Could not send audio frame to encoder: " + utils::getErrorString(ret)); AVPacket* audioPacket = av_packet_alloc(); audioPacket->data = nullptr; @@ -367,10 +316,8 @@ namespace ffmpeg { av_packet_unref(audioPacket); } else if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) break; - else { - geode::log::error("Error receiving audio packet."); - break; - } + else + return geode::Err("Could not receive audio packet: " + utils::getErrorString(ret)); } } @@ -383,5 +330,7 @@ namespace ffmpeg { } avformat_free_context(outputFormatContext); avformat_close_input(&videoFormatContext); + + return geode::Ok(); } } \ No newline at end of file diff --git a/src/recorder.cpp b/src/recorder.cpp index 81c1f8d..e938ade 100644 --- a/src/recorder.cpp +++ b/src/recorder.cpp @@ -1,5 +1,5 @@ #include "recorder.hpp" -#include "audio_mixer.hpp" +#include "utils.hpp" extern "C" { #include @@ -11,8 +11,6 @@ extern "C" { #include } -#include - namespace ffmpeg { std::vector Recorder::getAvailableCodecs() { @@ -41,35 +39,25 @@ const AVCodec* getCodecByName(const std::string& name) { return nullptr; } -bool Recorder::init(const RenderSettings& settings) { - avformat_alloc_output_context2(&m_formatContext, NULL, NULL, settings.m_outputFile.string().c_str()); - if (!m_formatContext) { - geode::log::error("Could not create output context."); - return false; - } +geode::Result Recorder::init(const RenderSettings& settings) { + int ret = avformat_alloc_output_context2(&m_formatContext, NULL, NULL, settings.m_outputFile.string().c_str()); + if (!m_formatContext) + return geode::Err("Could not create output context: " + utils::getErrorString(ret)); m_codec = getCodecByName(settings.m_codec); - if (!m_codec) { - geode::log::error("Could not find encoder."); - return false; - } + if (!m_codec) + return geode::Err("Could not find encoder."); m_videoStream = avformat_new_stream(m_formatContext, m_codec); - if (!m_videoStream) { - geode::log::error("Could not create video stream."); - return false; - } + if (!m_videoStream) + return geode::Err("Could not create video stream."); m_codecContext = avcodec_alloc_context3(m_codec); - if (!m_codecContext) { - geode::log::error("Could not allocate video codec context."); - return false; - } + if (!m_codecContext) + return geode::Err("Could not allocate video codec context."); - if(settings.m_hardwareAccelerationType != HardwareAccelerationType::NONE && av_hwdevice_ctx_create(&m_hwDevice, (AVHWDeviceType)settings.m_hardwareAccelerationType, NULL, NULL, 0) < 0) { - geode::log::error("Could not create hardware device context."); - return false; - } + if(settings.m_hardwareAccelerationType != HardwareAccelerationType::NONE && (ret = av_hwdevice_ctx_create(&m_hwDevice, (AVHWDeviceType)settings.m_hardwareAccelerationType, NULL, NULL, 0)); ret < 0) + return geode::Err("Could not create hardware device context: " + utils::getErrorString(ret)); m_codecContext->hw_device_ctx = m_hwDevice ? av_buffer_ref(m_hwDevice) : nullptr; m_codecContext->codec_id = m_codec->id; @@ -95,47 +83,37 @@ bool Recorder::init(const RenderSettings& settings) { else geode::log::info("Codec {} supports pixel format.", settings.m_codec); - if (avcodec_open2(m_codecContext, m_codec, nullptr) < 0) { - geode::log::error("Could not open codec."); - return false; - } + if (ret = avcodec_open2(m_codecContext, m_codec, nullptr); ret < 0) + return geode::Err("Could not open codec: " + utils::getErrorString(ret)); if (m_formatContext->oformat->flags & AVFMT_GLOBALHEADER) m_codecContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; - if (avcodec_parameters_from_context(m_videoStream->codecpar, m_codecContext) < 0) { - geode::log::error("Could not copy codec parameters."); - return false; - } + if (ret = avcodec_parameters_from_context(m_videoStream->codecpar, m_codecContext); ret < 0) + return geode::Err("Could not copy codec parameters: " + utils::getErrorString(ret)); if (!(m_formatContext->oformat->flags & AVFMT_NOFILE)) { - if (avio_open(&m_formatContext->pb, settings.m_outputFile.string().c_str(), AVIO_FLAG_WRITE) < 0) - return false; + if (ret = avio_open(&m_formatContext->pb, settings.m_outputFile.string().c_str(), AVIO_FLAG_WRITE); ret < 0) + return geode::Err("Could not open output file: " + utils::getErrorString(ret)); } - if (avformat_write_header(m_formatContext, nullptr) < 0) { - geode::log::error("Could not write header."); - return false; - } + 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_frame->format = m_codecContext->pix_fmt; m_frame->width = m_codecContext->width; m_frame->height = m_codecContext->height; - if (av_image_alloc(m_frame->data, m_frame->linesize, m_codecContext->width, m_codecContext->height, (AVPixelFormat)settings.m_pixelFormat, 32) < 0) { - geode::log::error("Could not allocate raw picture buffer."); - return false; - } + 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_convertedFrame->format = m_codecContext->pix_fmt; m_convertedFrame->width = m_codecContext->width; m_convertedFrame->height = m_codecContext->height; - if(av_image_alloc(m_convertedFrame->data, m_convertedFrame->linesize, m_convertedFrame->width, m_convertedFrame->height, m_codecContext->pix_fmt, 32) < 0) { - geode::log::error("Could not allocate raw picture buffer."); - return false; - } + 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(); @@ -148,10 +126,8 @@ bool Recorder::init(const RenderSettings& settings) { if(!settings.m_colorspaceFilters.empty()) { m_filterGraph = avfilter_graph_alloc(); - if (!m_filterGraph) { - geode::log::error("Could not allocate filter graph."); - return false; - } + if (!m_filterGraph) + return geode::Err("Could not allocate filter graph."); const AVFilter* buffersrc = avfilter_get_by_name("buffer"); const AVFilter* buffersink = avfilter_get_by_name("buffersink"); @@ -164,25 +140,34 @@ bool Recorder::init(const RenderSettings& settings) { m_codecContext->time_base.num, m_codecContext->time_base.den, m_codecContext->sample_aspect_ratio.num, m_codecContext->sample_aspect_ratio.den); - if (avfilter_graph_create_filter(&m_buffersrcCtx, buffersrc, "in", args, nullptr, m_filterGraph) < 0 || - avfilter_graph_create_filter(&m_buffersinkCtx, buffersink, "out", nullptr, nullptr, m_filterGraph) < 0 || - avfilter_graph_create_filter(&m_colorspaceCtx, colorspace, "colorspace", settings.m_colorspaceFilters.c_str(), nullptr, m_filterGraph) < 0) { - geode::log::error("Error creating filter contexts."); + if(ret = avfilter_graph_create_filter(&m_buffersrcCtx, buffersrc, "in", args, nullptr, m_filterGraph); ret < 0) { avfilter_graph_free(&m_filterGraph); - return false; + return geode::Err("Could not create input for filter graph: " + utils::getErrorString(ret)); } - if (avfilter_link(m_buffersrcCtx, 0, m_colorspaceCtx, 0) < 0 || - avfilter_link(m_colorspaceCtx, 0, m_buffersinkCtx, 0) < 0) { - geode::log::error("Error linking filters."); + if(ret = avfilter_graph_create_filter(&m_buffersinkCtx, buffersink, "out", nullptr, nullptr, m_filterGraph); ret < 0) { avfilter_graph_free(&m_filterGraph); - return false; + return geode::Err("Could not create output for filter graph: " + utils::getErrorString(ret)); } - if (avfilter_graph_config(m_filterGraph, nullptr) < 0) { - geode::log::error("Error configuring filter graph."); + 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); - return false; + 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); + 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); + 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); + return geode::Err("Could not configure filter graph: " + utils::getErrorString(ret)); } inputPixelFormat = av_buffersink_get_format(m_buffersinkCtx); @@ -191,25 +176,29 @@ bool Recorder::init(const RenderSettings& settings) { 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) { - geode::log::error("Could not create sws context."); - return false; - } + if (!m_swsCtx) + return geode::Err("Could not create sws context."); m_frameCount = 0; m_init = true; - return true; + return geode::Ok(); } -bool Recorder::writeFrame(const std::vector& frameData) { - if (!m_init || !m_frame || frameData.size() != m_frame->linesize[0] * m_frame->height) - return false; +geode::Result Recorder::writeFrame(const std::vector& frameData) { + if (!m_init || !m_frame) + return geode::Err("Recorder is not initialized."); + + if(frameData.size() != m_frame->linesize[0] * m_frame->height) + return geode::Err("Frame data size does not match expected dimensions."); if(m_buffersrcCtx) { std::memcpy(m_frame->data[0], frameData.data(), frameData.size()); - filterFrame(m_frame, m_filteredFrame); + geode::Result res = filterFrame(m_frame, m_filteredFrame); + + if(res.isErr()) + return res; sws_scale( m_swsCtx, m_filteredFrame->data, m_filteredFrame->linesize, 0, m_filteredFrame->height, @@ -228,14 +217,14 @@ bool Recorder::writeFrame(const std::vector& frameData) { int ret = avcodec_send_frame(m_codecContext, m_convertedFrame); if (ret < 0) - return false; + return geode::Err("Error while sending frame: " + utils::getErrorString(ret)); while (ret >= 0) { ret = avcodec_receive_packet(m_codecContext, m_packet); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) break; if (ret < 0) - return false; + return geode::Err("Error while receiving packet: " + utils::getErrorString(ret)); av_packet_rescale_ts(m_packet, m_codecContext->time_base, m_videoStream->time_base); m_packet->stream_index = m_videoStream->index; @@ -246,21 +235,22 @@ bool Recorder::writeFrame(const std::vector& frameData) { av_frame_unref(m_filteredFrame); - return true; + return geode::Ok(); } -void Recorder::filterFrame(AVFrame* inputFrame, AVFrame* outputFrame) { - if (av_buffersrc_add_frame(m_buffersrcCtx, inputFrame) < 0) { - geode::log::error("Error feeding frame to filter graph."); +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); - return; + return geode::Err("Could not feed frame to filter graph: " + utils::getErrorString(ret)); } - if (av_buffersink_get_frame(m_buffersinkCtx, outputFrame) < 0) { - geode::log::error("Error retrieving frame from filter graph."); + if (ret = av_buffersink_get_frame(m_buffersinkCtx, outputFrame); ret < 0) { av_frame_unref(outputFrame); - return; + return geode::Err("Could not retrieve frame from filter graph: " + utils::getErrorString(ret)); } + + return geode::Ok(); } void Recorder::stop() { diff --git a/src/utils.cpp b/src/utils.cpp new file mode 100644 index 0000000..3f82dfa --- /dev/null +++ b/src/utils.cpp @@ -0,0 +1,40 @@ +#include "utils.hpp" + +#include + +extern "C" { + #include + #include +} + +namespace ffmpeg::utils { + +static std::vector s_ffmpegLogs; + +void customLogCallback(void* ptr, int level, const char* fmt, char* vargs) { + char logBuffer[1024]; + vsnprintf(logBuffer, sizeof(logBuffer), fmt, vargs); + + if (level <= AV_LOG_WARNING) + s_ffmpegLogs.push_back(logBuffer); + + av_log_default_callback(ptr, level, fmt, vargs); +} + +std::string getErrorString(int errorCode) { + char errbuf[AV_ERROR_MAX_STRING_SIZE]; + av_strerror(errorCode, errbuf, AV_ERROR_MAX_STRING_SIZE); + std::string errStr(errbuf); + errStr += "\nDetails:\n"; + for(const std::string& log : s_ffmpegLogs) + errStr += log; + + s_ffmpegLogs.clear(); + return errStr; +} + +$on_mod(Loaded) { + av_log_set_callback(customLogCallback); +} + +} \ No newline at end of file diff --git a/src/utils.hpp b/src/utils.hpp new file mode 100644 index 0000000..71c4d18 --- /dev/null +++ b/src/utils.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include + +namespace ffmpeg::utils { + +void customLogCallback(void* ptr, int level, const char* fmt, char* vargs); + +std::string getErrorString(int errorCode); + +} \ No newline at end of file