diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 06c0c6ab..b04c9475 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2,6 +2,9 @@ stages: - build-libopenshot - trigger-openshot-qt +variables: + GIT_LOG_FORMAT: "- %h %ad %s [%aN]" + linux-builder: stage: build-libopenshot artifacts: @@ -22,7 +25,7 @@ linux-builder: - make doc - ~/auto-update-docs "$CI_PROJECT_DIR/build" "$CI_COMMIT_REF_NAME" - echo -e "CI_PROJECT_NAME:$CI_PROJECT_NAME\nCI_COMMIT_REF_NAME:$CI_COMMIT_REF_NAME\nCI_COMMIT_SHA:$CI_COMMIT_SHA\nCI_JOB_ID:$CI_JOB_ID" > "install-x64/share/$CI_PROJECT_NAME" - - git log $(git describe --tags --abbrev=0 @^)..@ --oneline --pretty=format:"%C(auto,yellow)%h%C(auto,magenta)% %C(auto,blue)%>(12,trunc)%ad %C(auto,green)%<(25,trunc)%aN%C(auto,reset)%s%C(auto,red)% gD% D" --date=short > "install-x64/share/$CI_PROJECT_NAME.log" + - git log $(git describe --tags --abbrev=0 '@^')..@ --oneline --no-abbrev --date=short --no-merges --pretty="tformat:$GIT_LOG_FORMAT" > "install-x64/share/$CI_PROJECT_NAME.log" when: always except: - tags @@ -47,7 +50,7 @@ mac-builder: - make - make install - echo -e "CI_PROJECT_NAME:$CI_PROJECT_NAME\nCI_COMMIT_REF_NAME:$CI_COMMIT_REF_NAME\nCI_COMMIT_SHA:$CI_COMMIT_SHA\nCI_JOB_ID:$CI_JOB_ID" > "install-x64/share/$CI_PROJECT_NAME" - - git log $(git describe --tags --abbrev=0 @^)..@ --oneline --pretty=format:"%C(auto,yellow)%h%C(auto,magenta)% %C(auto,blue)%>(12,trunc)%ad %C(auto,green)%<(25,trunc)%aN%C(auto,reset)%s%C(auto,red)% gD% D" --date=short > "install-x64/share/$CI_PROJECT_NAME.log" + - git log $(git describe --tags --abbrev=0 '@^')..@ --oneline --no-abbrev --date=short --no-merges --pretty="tformat:$GIT_LOG_FORMAT" > "install-x64/share/$CI_PROJECT_NAME.log" when: always except: - tags @@ -74,7 +77,7 @@ windows-builder-x64: - mingw32-make install - New-Item -path "install-x64/share/" -Name "$CI_PROJECT_NAME" -Value "CI_PROJECT_NAME:$CI_PROJECT_NAME`nCI_COMMIT_REF_NAME:$CI_COMMIT_REF_NAME`nCI_COMMIT_SHA:$CI_COMMIT_SHA`nCI_JOB_ID:$CI_JOB_ID" -ItemType file -force - $PREV_GIT_LABEL=(git describe --tags --abbrev=0 '@^') - - git log "$PREV_GIT_LABEL..@" --oneline --pretty=format:"%C(auto,yellow)%h%C(auto,magenta)% %C(auto,blue)%>(12,trunc)%ad %C(auto,green)%<(25,trunc)%aN%C(auto,reset)%s%C(auto,red)% gD% D" --date=short > "install-x64/share/$CI_PROJECT_NAME.log" + - git log "$PREV_GIT_LABEL..@" --oneline --no-abbrev --date=short --no-merges --pretty="tformat:$GIT_LOG_FORMAT" > "install-x64/share/$CI_PROJECT_NAME.log" when: always except: - tags @@ -101,7 +104,7 @@ windows-builder-x86: - mingw32-make install - New-Item -path "install-x86/share/" -Name "$CI_PROJECT_NAME" -Value "CI_PROJECT_NAME:$CI_PROJECT_NAME`nCI_COMMIT_REF_NAME:$CI_COMMIT_REF_NAME`nCI_COMMIT_SHA:$CI_COMMIT_SHA`nCI_JOB_ID:$CI_JOB_ID" -ItemType file -force - $PREV_GIT_LABEL=(git describe --tags --abbrev=0 '@^') - - git log "$PREV_GIT_LABEL..@" --oneline --pretty=format:"%C(auto,yellow)%h%C(auto,magenta)% %C(auto,blue)%>(12,trunc)%ad %C(auto,green)%<(25,trunc)%aN%C(auto,reset)%s%C(auto,red)% gD% D" --date=short > "install-x86/share/$CI_PROJECT_NAME.log" + - git log "$PREV_GIT_LABEL..@" --oneline --no-abbrev --date=short --no-merges --pretty="tformat:$GIT_LOG_FORMAT" > "install-x86/share/$CI_PROJECT_NAME.log" when: always except: - tags diff --git a/include/DummyReader.h b/include/DummyReader.h index 9a75751d..af06656d 100644 --- a/include/DummyReader.h +++ b/include/DummyReader.h @@ -69,7 +69,7 @@ namespace openshot * // Create blank frame (with specific frame #, samples, and channels) * // Sample count should be 44100 / 30 fps = 1470 samples per frame * int sample_count = 1470; - * std::shared_ptr f(new openshot::Frame(frame_number, sample_count, 2)); + * auto f = std::make_shared(frame_number, sample_count, 2); * * // Create test samples with incrementing value * float *audio_buffer = new float[sample_count]; diff --git a/include/FFmpegReader.h b/include/FFmpegReader.h index ec082965..35e11a42 100644 --- a/include/FFmpegReader.h +++ b/include/FFmpegReader.h @@ -233,14 +233,13 @@ namespace openshot { /// codecs have trouble seeking, and can introduce artifacts or blank images into the video. bool enable_seek; - /// Constructor for FFmpegReader. This automatically opens the media file and loads - /// frame 1, or it throws one of the following exceptions. - FFmpegReader(std::string path); - - /// Constructor for FFmpegReader. This only opens the media file to inspect its properties - /// if inspect_reader=true. When not inspecting the media file, it's much faster, and useful - /// when you are inflating the object using JSON after instantiating it. - FFmpegReader(std::string path, bool inspect_reader); + /// @brief Constructor for FFmpegReader. + /// + /// Sets (and possibly opens) the media file path, + /// or throws an exception. + /// @param path The filesystem location to load + /// @param inspect_reader if true (the default), automatically open the media file and loads frame 1. + FFmpegReader(const std::string& path, bool inspect_reader=true); /// Destructor virtual ~FFmpegReader(); diff --git a/include/FFmpegWriter.h b/include/FFmpegWriter.h index 98fbbb59..6d9da6e5 100644 --- a/include/FFmpegWriter.h +++ b/include/FFmpegWriter.h @@ -47,8 +47,6 @@ #include #include -#include -#include #include #include "CacheMemory.h" #include "Exceptions.h" @@ -251,9 +249,11 @@ namespace openshot { public: - /// @brief Constructor for FFmpegWriter. Throws one of the following exceptions. + /// @brief Constructor for FFmpegWriter. + /// Throws an exception on failure to open path. + /// /// @param path The file path of the video file you want to open and read - FFmpegWriter(std::string path); + FFmpegWriter(const std::string& path); /// Close the writer void Close(); diff --git a/include/Frame.h b/include/Frame.h index f4ff54d4..b3416d38 100644 --- a/include/Frame.h +++ b/include/Frame.h @@ -34,16 +34,8 @@ #include #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include #include #include #include "ZmqLogger.h" @@ -73,17 +65,17 @@ namespace openshot * There are many ways to create an instance of an openshot::Frame: * @code * - * // Most basic: a blank frame (300x200 blank image, 48kHz audio silence) + * // Most basic: a blank frame (all default values) * Frame(); * - * // Image only settings (48kHz audio silence) + * // Image only settings * Frame(1, // Frame number * 720, // Width of image * 480, // Height of image * "#000000" // HTML color code of background color * ); * - * // Audio only (300x200 blank image) + * // Audio only * Frame(number, // Frame number * 44100, // Sample rate of audio stream * 2 // Number of audio channels @@ -99,7 +91,7 @@ namespace openshot * ); * * // Some methods require a shared pointer to an openshot::Frame object. - * std::shared_ptr f(new Frame(1, 720, 480, "#000000", 44100, 2)); + * auto f = std::make_shared(1, 720, 480, "#000000", 44100, 2); * * @endcode */ @@ -131,13 +123,13 @@ namespace openshot bool has_image_data; ///< This frame has been loaded with pixel data - /// Constructor - blank frame (300x200 blank image, 48kHz audio silence) + /// Constructor - blank frame Frame(); - /// Constructor - image only (48kHz audio silence) + /// Constructor - image only Frame(int64_t number, int width, int height, std::string color); - /// Constructor - audio only (300x200 blank image) + /// Constructor - audio only Frame(int64_t number, int samples, int channels); /// Constructor - image & audio diff --git a/include/OpenMPUtilities.h b/include/OpenMPUtilities.h index 30bdd199..b523bbd1 100644 --- a/include/OpenMPUtilities.h +++ b/include/OpenMPUtilities.h @@ -41,5 +41,12 @@ #define OPEN_MP_NUM_PROCESSORS (std::min(omp_get_num_procs(), std::max(2, openshot::Settings::Instance()->OMP_THREADS) )) #define FF_NUM_PROCESSORS (std::min(omp_get_num_procs(), std::max(2, openshot::Settings::Instance()->FF_THREADS) )) +// Set max-active-levels to the max supported, if possible +// (supported_active_levels is OpenMP 5.0 (November 2018) or later, only.) +#if (_OPENMP >= 201811) + #define OPEN_MP_MAX_ACTIVE openmp_get_supported_active_levels() +#else + #define OPEN_MP_MAX_ACTIVE OPEN_MP_NUM_PROCESSORS +#endif #endif diff --git a/include/Qt/VideoRenderWidget.h b/include/Qt/VideoRenderWidget.h index 07c61037..4d9ac17f 100644 --- a/include/Qt/VideoRenderWidget.h +++ b/include/Qt/VideoRenderWidget.h @@ -31,11 +31,13 @@ #ifndef OPENSHOT_VIDEO_RENDERER_WIDGET_H #define OPENSHOT_VIDEO_RENDERER_WIDGET_H -#include -#include #include "../Fraction.h" #include "VideoRenderer.h" +#include +#include +#include +#include class VideoRenderWidget : public QWidget { diff --git a/include/Timeline.h b/include/Timeline.h index a57c87cf..22718342 100644 --- a/include/Timeline.h +++ b/include/Timeline.h @@ -219,19 +219,23 @@ namespace openshot { public: - /// @brief Default Constructor for the timeline (which sets the canvas width and height and FPS) - /// @param width The width of the timeline (and thus, the generated openshot::Frame objects) - /// @param height The height of the timeline (and thus, the generated openshot::Frame objects) - /// @param fps The frames rate of the timeline - /// @param sample_rate The sample rate of the timeline's audio - /// @param channels The number of audio channels of the timeline + /// @brief Default Constructor for the timeline (which configures the default frame properties) + /// @param width The image width of generated openshot::Frame objects + /// @param height The image height of generated openshot::Frame objects + /// @param fps The frame rate of the generated video + /// @param sample_rate The audio sample rate + /// @param channels The number of audio channels /// @param channel_layout The channel layout (i.e. mono, stereo, 3 point surround, etc...) Timeline(int width, int height, openshot::Fraction fps, int sample_rate, int channels, openshot::ChannelLayout channel_layout); - /// @brief Constructor for the timeline (which loads a JSON structure from a file path, and initializes a timeline) + /// @brief Project-file constructor for the timeline + /// + /// Loads a JSON structure from a file path, and + /// initializes the timeline described within. + /// /// @param projectPath The path of the UTF-8 *.osp project file (JSON contents). Contents will be loaded automatically. - /// @param convert_absolute_paths Should all paths be converted to absolute paths (based on the folder of the path provided) - Timeline(std::string projectPath, bool convert_absolute_paths); + /// @param convert_absolute_paths Should all paths be converted to absolute paths (relative to the location of projectPath) + Timeline(const std::string& projectPath, bool convert_absolute_paths); virtual ~Timeline(); diff --git a/include/effects/Pixelate.h b/include/effects/Pixelate.h index 50f9b673..8090847f 100644 --- a/include/effects/Pixelate.h +++ b/include/effects/Pixelate.h @@ -36,7 +36,6 @@ #include #include #include -#include "../Color.h" #include "../Json.h" #include "../KeyFrame.h" diff --git a/include/effects/Saturation.h b/include/effects/Saturation.h index 127f6829..b07a1700 100644 --- a/include/effects/Saturation.h +++ b/include/effects/Saturation.h @@ -64,15 +64,21 @@ namespace openshot void init_effect_details(); public: - Keyframe saturation; ///< The color saturation: 0.0 = black and white, 1.0 = normal, 2.0 = double saturation + Keyframe saturation; ///< Overall color saturation: 0.0 = greyscale, 1.0 = normal, 2.0 = double saturation + Keyframe saturation_R; ///< Red color saturation + Keyframe saturation_G; ///< Green color saturation + Keyframe saturation_B; ///< Blue color saturation /// Blank constructor, useful when using Json to load the effect properties Saturation(); - /// Default constructor, which takes 1 curve, to adjust the color saturation over time. + /// Default constructor, which takes four curves (one common curve and one curve per color), to adjust the color saturation over time. /// - /// @param new_saturation The curve to adjust the saturation of the frame's image (0.0 = black and white, 1.0 = normal, 2.0 = double saturation) - Saturation(Keyframe new_saturation); + /// @param saturation The curve to adjust the saturation of the frame's image (0.0 = greyscale, 1.0 = normal, 2.0 = double saturation) + /// @param saturation_R The curve to adjust red saturation of the frame's image (0.0 = greyscale, 1.0 = normal, 2.0 = double saturation) + /// @param saturation_G The curve to adjust green saturation of the frame's image (0.0 = greyscale, 1.0 = normal, 2.0 = double saturation) + /// @param saturation_B The curve to adjust blue saturation of the frame's image (0.0 = greyscale, 1.0 = normal, 2.0 = double saturation) + Saturation(Keyframe saturation, Keyframe saturation_R, Keyframe saturation_G, Keyframe saturation_B); /// @brief This method is required for all derived classes of ClipBase, and returns a /// new openshot::Frame object. All Clip keyframes and effects are resolved into diff --git a/src/CacheDisk.cpp b/src/CacheDisk.cpp index e4ef4c9d..23985ea6 100644 --- a/src/CacheDisk.cpp +++ b/src/CacheDisk.cpp @@ -231,14 +231,14 @@ std::shared_ptr CacheDisk::GetFrame(int64_t frame_number) if (path.exists(frame_path)) { // Load image file - std::shared_ptr image = std::shared_ptr(new QImage()); + auto image = std::make_shared(); image->load(frame_path); // Set pixel formatimage-> - image = std::shared_ptr(new QImage(image->convertToFormat(QImage::Format_RGBA8888_Premultiplied))); + image = std::make_shared(image->convertToFormat(QImage::Format_RGBA8888_Premultiplied)); // Create frame object - std::shared_ptr frame(new Frame()); + auto frame = std::make_shared(); frame->number = frame_number; frame->AddImage(image); diff --git a/src/ChunkWriter.cpp b/src/ChunkWriter.cpp index b6256eaf..8a8e559c 100644 --- a/src/ChunkWriter.cpp +++ b/src/ChunkWriter.cpp @@ -134,7 +134,9 @@ void ChunkWriter::WriteFrame(std::shared_ptr frame) writer_thumb->WriteFrame(last_frame); } else { // Write the 1st frame (of the 1st chunk)... since no previous chunk is available - std::shared_ptr blank_frame(new Frame(1, info.width, info.height, "#000000", info.sample_rate, info.channels)); + auto blank_frame = std::make_shared( + 1, info.width, info.height, "#000000", + info.sample_rate, info.channels); blank_frame->AddColor(info.width, info.height, "#000000"); writer_final->WriteFrame(blank_frame); writer_preview->WriteFrame(blank_frame); diff --git a/src/Clip.cpp b/src/Clip.cpp index 166f716c..1970884d 100644 --- a/src/Clip.cpp +++ b/src/Clip.cpp @@ -387,7 +387,7 @@ std::shared_ptr Clip::GetFrame(std::shared_ptr frame, in // Copy the image from the odd field if (enabled_video) - frame->AddImage(std::shared_ptr(new QImage(*original_frame->GetImage()))); + frame->AddImage(std::make_shared(*original_frame->GetImage())); // Loop through each channel, add audio if (enabled_audio && reader->info.has_audio) diff --git a/src/DecklinkInput.cpp b/src/DecklinkInput.cpp index da5a8d00..b03ad8e4 100644 --- a/src/DecklinkInput.cpp +++ b/src/DecklinkInput.cpp @@ -139,7 +139,7 @@ HRESULT DeckLinkInputDelegate::VideoInputFrameArrived(IDeckLinkVideoInputFrame* { // Handle Video Frame if(videoFrame) - { + { if (videoFrame->GetFlags() & bmdFrameHasNoInputSource) { @@ -245,7 +245,8 @@ omp_set_nested(true); m_rgbFrame->GetBytes(&frameBytes); // *********** CREATE OPENSHOT FRAME ********** - std::shared_ptr f(new openshot::Frame(copy_frameCount, width, height, "#000000", 2048, 2)); + auto f = std::make_shared( + copy_frameCount, width, height, "#000000", 2048, 2); // Add Image data to openshot frame // TODO: Fix Decklink support with QImage Upgrade @@ -289,6 +290,3 @@ HRESULT DeckLinkInputDelegate::VideoInputFormatChanged(BMDVideoInputFormatChange { return S_OK; } - - - diff --git a/src/DecklinkWriter.cpp b/src/DecklinkWriter.cpp index 4ebbd1f0..3eafda1c 100644 --- a/src/DecklinkWriter.cpp +++ b/src/DecklinkWriter.cpp @@ -142,7 +142,7 @@ void DecklinkWriter::Open() // throw DecklinkError("Failed to enable audio output. Is another application using the card?"); // Begin video preroll by scheduling a second of frames in hardware - //std::shared_ptr f(new Frame(1, displayMode->GetWidth(), displayMode->GetHeight(), "Blue")); + //auto f = std::make_shared(1, displayMode->GetWidth(), displayMode->GetHeight(), "Blue"); //f->AddColor(displayMode->GetWidth(), displayMode->GetHeight(), "Blue"); // Preroll 1 second of video diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index 19bca924..3e2fa976 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -86,7 +86,7 @@ int hw_de_on = 0; AVHWDeviceType hw_de_av_device_type_global = AV_HWDEVICE_TYPE_NONE; #endif -FFmpegReader::FFmpegReader(std::string path) +FFmpegReader::FFmpegReader(const std::string& path, bool inspect_reader) : last_frame(0), is_seeking(0), seeking_pts(0), seeking_frame(0), seek_count(0), audio_pts_offset(99999), video_pts_offset(99999), path(path), is_video_seek(true), check_interlace(false), check_fps(false), enable_seek(true), is_open(false), seek_audio_frame_found(0), seek_video_frame_found(0), @@ -94,27 +94,11 @@ FFmpegReader::FFmpegReader(std::string path) current_video_frame(0), has_missing_frames(false), num_packets_since_video_frame(0), num_checks_since_final(0), packet(NULL) { - // Initialize FFMpeg, and register all formats and codecs - AV_REGISTER_ALL - AVCODEC_REGISTER_ALL - - // Init cache - working_cache.SetMaxBytesFromInfo(OPEN_MP_NUM_PROCESSORS * info.fps.ToDouble() * 2, info.width, info.height, info.sample_rate, info.channels); - missing_frames.SetMaxBytesFromInfo(OPEN_MP_NUM_PROCESSORS * 2, info.width, info.height, info.sample_rate, info.channels); - final_cache.SetMaxBytesFromInfo(OPEN_MP_NUM_PROCESSORS * 2, info.width, info.height, info.sample_rate, info.channels); - - // Open and Close the reader, to populate its attributes (such as height, width, etc...) - Open(); - Close(); -} - -FFmpegReader::FFmpegReader(std::string path, bool inspect_reader) - : last_frame(0), is_seeking(0), seeking_pts(0), seeking_frame(0), seek_count(0), - audio_pts_offset(99999), video_pts_offset(99999), path(path), is_video_seek(true), check_interlace(false), - check_fps(false), enable_seek(true), is_open(false), seek_audio_frame_found(0), seek_video_frame_found(0), - prev_samples(0), prev_pts(0), pts_total(0), pts_counter(0), is_duration_known(false), largest_frame_processed(0), - current_video_frame(0), has_missing_frames(false), num_packets_since_video_frame(0), num_checks_since_final(0), - packet(NULL) { + // Configure OpenMP parallelism + // Default number of threads per section + omp_set_num_threads(OPEN_MP_NUM_PROCESSORS); + // Allow nested parallel sections as deeply as supported + omp_set_max_active_levels(OPEN_MP_MAX_ACTIVE); // Initialize FFMpeg, and register all formats and codecs AV_REGISTER_ALL @@ -901,11 +885,6 @@ std::shared_ptr FFmpegReader::ReadStream(int64_t requested_frame) { int minimum_packets = OPEN_MP_NUM_PROCESSORS; int max_packets = 4096; - // Set the number of threads in OpenMP - omp_set_num_threads(OPEN_MP_NUM_PROCESSORS); - // Allow nested OpenMP sections - omp_set_nested(true); - // Debug output ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ReadStream", "requested_frame", requested_frame, "OPEN_MP_NUM_PROCESSORS", OPEN_MP_NUM_PROCESSORS); @@ -2160,7 +2139,7 @@ bool FFmpegReader::CheckMissingFrame(int64_t requested_frame) { // Add this frame to the processed map (since it's already done) std::shared_ptr parent_image = parent_frame->GetImage(); if (parent_image) { - missing_frame->AddImage(std::shared_ptr(new QImage(*parent_image))); + missing_frame->AddImage(std::make_shared(*parent_image)); processed_video_frames[missing_frame->number] = missing_frame->number; } } @@ -2250,7 +2229,7 @@ void FFmpegReader::CheckWorkingFrames(bool end_of_stream, int64_t requested_fram if (info.has_video && !is_video_ready && last_video_frame) { // Copy image from last frame - f->AddImage(std::shared_ptr(new QImage(*last_video_frame->GetImage()))); + f->AddImage(std::make_shared(*last_video_frame->GetImage())); is_video_ready = true; } @@ -2272,7 +2251,7 @@ void FFmpegReader::CheckWorkingFrames(bool end_of_stream, int64_t requested_fram // Add missing image (if needed - sometimes end_of_stream causes frames with only audio) if (info.has_video && !is_video_ready && last_video_frame) // Copy image from last frame - f->AddImage(std::shared_ptr(new QImage(*last_video_frame->GetImage()))); + f->AddImage(std::make_shared(*last_video_frame->GetImage())); // Reset counter since last 'final' frame num_checks_since_final = 0; diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index b2aec5f1..76bc9890 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -33,6 +33,8 @@ #include "../include/FFmpegWriter.h" +#include + using namespace openshot; #if HAVE_HW_ACCEL @@ -59,7 +61,7 @@ static int set_hwframe_ctx(AVCodecContext *ctx, AVBufferRef *hw_device_ctx, int6 int err = 0; if (!(hw_frames_ref = av_hwframe_ctx_alloc(hw_device_ctx))) { - fprintf(stderr, "Failed to create HW frame context.\n"); + std::clog << "Failed to create HW frame context.\n"; return -1; } frames_ctx = (AVHWFramesContext *)(hw_frames_ref->data); @@ -69,8 +71,8 @@ static int set_hwframe_ctx(AVCodecContext *ctx, AVBufferRef *hw_device_ctx, int6 frames_ctx->height = height; frames_ctx->initial_pool_size = 20; if ((err = av_hwframe_ctx_init(hw_frames_ref)) < 0) { - fprintf(stderr, "Failed to initialize HW frame context." - "Error code: %s\n",av_err2str(err)); + std::clog << "Failed to initialize HW frame context. " << + "Error code: " << av_err2str(err) << "\n"; av_buffer_unref(&hw_frames_ref); return err; } @@ -83,7 +85,7 @@ static int set_hwframe_ctx(AVCodecContext *ctx, AVBufferRef *hw_device_ctx, int6 } #endif // HAVE_HW_ACCEL -FFmpegWriter::FFmpegWriter(std::string path) : +FFmpegWriter::FFmpegWriter(const std::string& path) : path(path), fmt(NULL), oc(NULL), audio_st(NULL), video_st(NULL), samples(NULL), audio_outbuf(NULL), audio_outbuf_size(0), audio_input_frame_size(0), audio_input_position(0), initial_audio_input_frame_size(0), img_convert_ctx(NULL), cache_size(8), num_of_rescalers(32), @@ -95,6 +97,12 @@ FFmpegWriter::FFmpegWriter(std::string path) : info.has_audio = false; info.has_video = false; + // Configure OpenMP parallelism + // Default number of threads per block + omp_set_num_threads(OPEN_MP_NUM_PROCESSORS); + // Allow nested parallel sections as deeply as supported + omp_set_max_active_levels(OPEN_MP_MAX_ACTIVE); + // Initialize FFMpeg, and register all formats and codecs AV_REGISTER_ALL @@ -220,10 +228,10 @@ void FFmpegWriter::SetVideoOptions(bool has_video, std::string codec, Fraction f hw_en_on = 0; hw_en_supported = 0; } - #else // is FFmpeg 3 but not linux +#else // unknown OS new_codec = avcodec_find_encoder_by_name(codec.c_str()); - #endif //__linux__ -#else // not ffmpeg 3 +#endif //__linux__/_WIN32/__APPLE__ +#else // HAVE_HW_ACCEL new_codec = avcodec_find_encoder_by_name(codec.c_str()); #endif // HAVE_HW_ACCEL if (new_codec == NULL) @@ -253,9 +261,9 @@ void FFmpegWriter::SetVideoOptions(bool has_video, std::string codec, Fraction f info.pixel_ratio.num = pixel_ratio.num; info.pixel_ratio.den = pixel_ratio.den; } - if (bit_rate >= 1000) // bit_rate is the bitrate in b/s + if (bit_rate >= 1000) // bit_rate is the bitrate in b/s info.video_bit_rate = bit_rate; - if ((bit_rate >= 0) && (bit_rate < 256)) // bit_rate is the bitrate in crf + if ((bit_rate >= 0) && (bit_rate < 256)) // bit_rate is the bitrate in crf info.video_bit_rate = bit_rate; info.interlaced_frame = interlaced; @@ -280,8 +288,10 @@ void FFmpegWriter::SetVideoOptions(bool has_video, std::string codec, Fraction f // Set video export options (overloaded function) void FFmpegWriter::SetVideoOptions(std::string codec, int width, int height, Fraction fps, int bit_rate) { // Call full signature with some default parameters - FFmpegWriter::SetVideoOptions(true, codec, fps, width, height, - openshot::Fraction(1, 1), false, true, bit_rate); + FFmpegWriter::SetVideoOptions( + true, codec, fps, width, height, + openshot::Fraction(1, 1), false, true, bit_rate + ); } @@ -314,7 +324,11 @@ void FFmpegWriter::SetAudioOptions(bool has_audio, std::string codec, int sample if (original_channels == 0) original_channels = info.channels; - ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::SetAudioOptions (" + codec + ")", "sample_rate", sample_rate, "channels", channels, "bit_rate", bit_rate); + ZmqLogger::Instance()->AppendDebugMethod( + "FFmpegWriter::SetAudioOptions (" + codec + ")", + "sample_rate", sample_rate, + "channels", channels, + "bit_rate", bit_rate); // Enable / Disable audio info.has_audio = has_audio; @@ -324,8 +338,10 @@ void FFmpegWriter::SetAudioOptions(bool has_audio, std::string codec, int sample // Set audio export options (overloaded function) void FFmpegWriter::SetAudioOptions(std::string codec, int sample_rate, int bit_rate) { // Call full signature with some default parameters - FFmpegWriter::SetAudioOptions(true, codec, sample_rate, 2, - openshot::LAYOUT_STEREO, bit_rate); + FFmpegWriter::SetAudioOptions( + true, codec, sample_rate, 2, + openshot::LAYOUT_STEREO, bit_rate + ); } @@ -340,6 +356,14 @@ void FFmpegWriter::SetOption(StreamType stream, std::string name, std::string va st = video_st; // Get codec context c = AV_GET_CODEC_PAR_CONTEXT(st, video_codec_ctx); + // Was a codec / stream found? + if (c) { + if (info.interlaced_frame) { + c->field_order = info.top_field_first ? AV_FIELD_TT : AV_FIELD_BB; + // We only use these two version and ignore AV_FIELD_TB and AV_FIELD_BT + // Otherwise we would need to change the whole export window + } + } } else if (info.has_audio && stream == AUDIO_STREAM && audio_st) { st = audio_st; // Get codec context @@ -408,58 +432,56 @@ void FFmpegWriter::SetOption(StreamType stream, std::string name, std::string va // encode quality and special settings like lossless // This might be better in an extra methods as more options // and way to set quality are possible - #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 39, 101) - #if HAVE_HW_ACCEL - if (hw_en_on) { - av_opt_set_int(c->priv_data, "qp", std::min(std::stoi(value),63), 0); // 0-63 - } else - #endif // HAVE_HW_ACCEL - { +#if HAVE_HW_ACCEL + if (hw_en_on) { + av_opt_set_int(c->priv_data, "qp", std::min(std::stoi(value),63), 0); // 0-63 + } else +#endif // HAVE_HW_ACCEL + { switch (c->codec_id) { - #if (LIBAVCODEC_VERSION_MAJOR >= 58) - case AV_CODEC_ID_AV1 : - c->bit_rate = 0; - av_opt_set_int(c->priv_data, "qp", std::min(std::stoi(value),63), 0); // 0-63 - break; - #endif - case AV_CODEC_ID_VP8 : - c->bit_rate = 10000000; - av_opt_set_int(c->priv_data, "qp", std::max(std::min(std::stoi(value), 63), 4), 0); // 4-63 - break; - case AV_CODEC_ID_VP9 : - c->bit_rate = 0; // Must be zero! - av_opt_set_int(c->priv_data, "qp", std::min(std::stoi(value), 63), 0); // 0-63 - if (std::stoi(value) == 0) { - av_opt_set(c->priv_data, "preset", "veryslow", 0); - av_opt_set_int(c->priv_data, "lossless", 1, 0); - } - break; - case AV_CODEC_ID_H264 : - av_opt_set_int(c->priv_data, "qp", std::min(std::stoi(value), 51), 0); // 0-51 - if (std::stoi(value) == 0) { - av_opt_set(c->priv_data, "preset", "veryslow", 0); +#if (LIBAVCODEC_VERSION_MAJOR >= 58) + // FFmpeg 4.0+ + case AV_CODEC_ID_AV1 : + c->bit_rate = 0; + av_opt_set_int(c->priv_data, "qp", std::min(std::stoi(value),63), 0); // 0-63 + break; +#endif + case AV_CODEC_ID_VP8 : + c->bit_rate = 10000000; + av_opt_set_int(c->priv_data, "qp", std::max(std::min(std::stoi(value), 63), 4), 0); // 4-63 + break; + case AV_CODEC_ID_VP9 : + c->bit_rate = 0; // Must be zero! + av_opt_set_int(c->priv_data, "qp", std::min(std::stoi(value), 63), 0); // 0-63 + if (std::stoi(value) == 0) { + av_opt_set(c->priv_data, "preset", "veryslow", 0); + av_opt_set_int(c->priv_data, "lossless", 1, 0); + } + break; + case AV_CODEC_ID_H264 : + av_opt_set_int(c->priv_data, "qp", std::min(std::stoi(value), 51), 0); // 0-51 + if (std::stoi(value) == 0) { + av_opt_set(c->priv_data, "preset", "veryslow", 0); c->pix_fmt = PIX_FMT_YUV444P; // no chroma subsampling - } - break; - case AV_CODEC_ID_HEVC : - av_opt_set_int(c->priv_data, "qp", std::min(std::stoi(value), 51), 0); // 0-51 - if (std::stoi(value) == 0) { - av_opt_set(c->priv_data, "preset", "veryslow", 0); - av_opt_set_int(c->priv_data, "lossless", 1, 0); - } - break; - default: - // For all other codecs assume a range of 0-63 - av_opt_set_int(c->priv_data, "qp", std::min(std::stoi(value), 63), 0); // 0-63 - c->bit_rate = 0; + } + break; + case AV_CODEC_ID_HEVC : + av_opt_set_int(c->priv_data, "qp", std::min(std::stoi(value), 51), 0); // 0-51 + if (std::stoi(value) == 0) { + av_opt_set(c->priv_data, "preset", "veryslow", 0); + av_opt_set_int(c->priv_data, "lossless", 1, 0); + } + break; + default: + // For all other codecs assume a range of 0-63 + av_opt_set_int(c->priv_data, "qp", std::min(std::stoi(value), 63), 0); // 0-63 + c->bit_rate = 0; } } - #endif } else if (name == "crf") { // encode quality and special settings like lossless // This might be better in an extra methods as more options // and way to set quality are possible -#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 39, 101) #if HAVE_HW_ACCEL if (hw_en_on) { double mbs = 15000000.0; @@ -477,6 +499,7 @@ void FFmpegWriter::SetOption(StreamType stream, std::string name, std::string va { switch (c->codec_id) { #if (LIBAVCODEC_VERSION_MAJOR >= 58) + // FFmpeg 4.0+ case AV_CODEC_ID_AV1 : c->bit_rate = 0; // AV1 only supports "crf" quality values @@ -530,12 +553,12 @@ void FFmpegWriter::SetOption(StreamType stream, std::string name, std::string va c->bit_rate = (int) (mbs); } } -#endif } else if (name == "qp") { // encode quality and special settings like lossless // This might be better in an extra methods as more options // and way to set quality are possible #if (LIBAVCODEC_VERSION_MAJOR >= 58) + // FFmpeg 4.0+ switch (c->codec_id) { case AV_CODEC_ID_AV1 : c->bit_rate = 0; @@ -565,7 +588,7 @@ void FFmpegWriter::SetOption(StreamType stream, std::string name, std::string va } break; } -#endif +#endif // FFmpeg 4.0+ } else { // Set AVOption AV_OPTION_SET(st, c->priv_data, name.c_str(), value.c_str(), c); @@ -678,15 +701,8 @@ void FFmpegWriter::WriteFrame(std::shared_ptr frame) { // Write the frames once it reaches the correct cache size if ((int)spooled_video_frames.size() == cache_size || (int)spooled_audio_frames.size() == cache_size) { - // Is writer currently writing? - if (!is_writing) - // Write frames to video file - write_queued_frames(); - - else { - // Write frames to video file - write_queued_frames(); - } + // Write frames to video file + write_queued_frames(); } // Keep track of the last frame added @@ -708,11 +724,6 @@ void FFmpegWriter::write_queued_frames() { spooled_video_frames.clear(); spooled_audio_frames.clear(); - // Set the number of threads in OpenMP - omp_set_num_threads(OPEN_MP_NUM_PROCESSORS); - // Allow nested OpenMP sections - omp_set_nested(true); - // Create blank exception bool has_error_encoding_video = false; @@ -844,6 +855,7 @@ void FFmpegWriter::flush_encoders() { if (info.has_audio && audio_codec_ctx && AV_GET_CODEC_TYPE(audio_st) == AVMEDIA_TYPE_AUDIO && AV_GET_CODEC_ATTRIBUTES(audio_st, audio_codec_ctx)->frame_size <= 1) return; #if (LIBAVFORMAT_VERSION_MAJOR < 58) + // FFmpeg < 4.0 if (info.has_video && video_codec_ctx && AV_GET_CODEC_TYPE(video_st) == AVMEDIA_TYPE_VIDEO && (oc->oformat->flags & AVFMT_RAWPICTURE) && AV_FIND_DECODER_CODEC_ID(video_st) == AV_CODEC_ID_RAWVIDEO) return; #else @@ -863,9 +875,6 @@ void FFmpegWriter::flush_encoders() { pkt.data = NULL; pkt.size = 0; - // Pointer for video buffer (if using old FFmpeg version) - uint8_t *video_outbuf = NULL; - /* encode the image */ int got_packet = 0; int error_code = 0; @@ -896,28 +905,9 @@ void FFmpegWriter::flush_encoders() { } #else // IS_FFMPEG_3_2 -#if LIBAVFORMAT_VERSION_MAJOR >= 54 // Encode video packet (older than FFmpeg 3.2) error_code = avcodec_encode_video2(video_codec_ctx, &pkt, NULL, &got_packet); -#else - // Encode video packet (even older version of FFmpeg) - int video_outbuf_size = 0; - - /* encode the image */ - int out_size = avcodec_encode_video(video_codec_ctx, NULL, video_outbuf_size, NULL); - - /* if zero size, it means the image was buffered */ - if (out_size > 0) { - if(video_codec_ctx->coded_frame->key_frame) - pkt.flags |= AV_PKT_FLAG_KEY; - pkt.data= video_outbuf; - pkt.size= out_size; - - // got data back (so encode this frame) - got_packet = 1; - } -#endif // LIBAVFORMAT_VERSION_MAJOR >= 54 #endif // IS_FFMPEG_3_2 if (error_code < 0) { @@ -948,12 +938,8 @@ void FFmpegWriter::flush_encoders() { for (;;) { // Increment PTS (in samples and scaled to the codec's timebase) -#if LIBAVFORMAT_VERSION_MAJOR >= 54 // for some reason, it requires me to multiply channels X 2 write_audio_count += av_rescale_q(audio_input_position / (audio_codec_ctx->channels * av_get_bytes_per_sample(AV_SAMPLE_FMT_S16)), av_make_q(1, info.sample_rate), audio_codec_ctx->time_base); -#else - write_audio_count += av_rescale_q(audio_input_position / audio_codec_ctx->channels, av_make_q(1, info.sample_rate), audio_codec_ctx->time_base); -#endif AVPacket pkt; av_init_packet(&pkt); @@ -1112,11 +1098,7 @@ AVStream *FFmpegWriter::add_audio_stream() { AV_FORMAT_NEW_STREAM(oc, audio_codec_ctx, codec, st) c->codec_id = codec->id; -#if LIBAVFORMAT_VERSION_MAJOR >= 53 c->codec_type = AVMEDIA_TYPE_AUDIO; -#else - c->codec_type = CODEC_TYPE_AUDIO; -#endif // Set the sample parameters c->bit_rate = info.audio_bit_rate; @@ -1170,6 +1152,7 @@ AVStream *FFmpegWriter::add_audio_stream() { // some formats want stream headers to be separate if (oc->oformat->flags & AVFMT_GLOBALHEADER) #if (LIBAVCODEC_VERSION_MAJOR >= 57) + // FFmpeg 3.0+ c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; #else c->flags |= CODEC_FLAG_GLOBAL_HEADER; @@ -1195,11 +1178,7 @@ AVStream *FFmpegWriter::add_video_stream() { AV_FORMAT_NEW_STREAM(oc, video_codec_ctx, codec, st) c->codec_id = codec->id; -#if LIBAVFORMAT_VERSION_MAJOR >= 53 c->codec_type = AVMEDIA_TYPE_VIDEO; -#else - c->codec_type = CODEC_TYPE_VIDEO; -#endif /* Init video encoder options */ if (info.video_bit_rate >= 1000 @@ -1219,8 +1198,8 @@ AVStream *FFmpegWriter::add_video_stream() { } else { // Check if codec supports crf or qp switch (c->codec_id) { -#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 39, 101) #if (LIBAVCODEC_VERSION_MAJOR >= 58) + // FFmpeg 4.0+ case AV_CODEC_ID_AV1 : // TODO: Set `crf` or `qp` according to bitrate, as bitrate is not supported by these encoders yet. if (info.video_bit_rate >= 1000) { @@ -1260,7 +1239,6 @@ AVStream *FFmpegWriter::add_video_stream() { #endif case AV_CODEC_ID_VP9 : case AV_CODEC_ID_HEVC : -#endif case AV_CODEC_ID_VP8 : case AV_CODEC_ID_H264 : if (info.video_bit_rate < 40) { @@ -1297,7 +1275,7 @@ AVStream *FFmpegWriter::add_video_stream() { identically 1. */ c->time_base.num = info.video_timebase.num; c->time_base.den = info.video_timebase.den; -// AVCodecContext->framerate was added in FFmpeg 2.2 +// AVCodecContext->framerate was added in FFmpeg 2.6 #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(56, 26, 0) c->framerate = av_inv_q(c->time_base); #endif @@ -1325,6 +1303,7 @@ AVStream *FFmpegWriter::add_video_stream() { // some formats want stream headers to be separate if (oc->oformat->flags & AVFMT_GLOBALHEADER) #if (LIBAVCODEC_VERSION_MAJOR >= 57) + // FFmpeg 3.0+ c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; #else c->flags |= CODEC_FLAG_GLOBAL_HEADER; @@ -1346,6 +1325,7 @@ AVStream *FFmpegWriter::add_video_stream() { c->pix_fmt = PIX_FMT_RGB24; #if (LIBAVFORMAT_VERSION_MAJOR < 58) + // FFmpeg < 4.0 if (strcmp(fmt->name, "gif") != 0) // If not GIF format, skip the encoding process // Set raw picture flag (so we don't encode this video) @@ -1359,6 +1339,7 @@ AVStream *FFmpegWriter::add_video_stream() { AV_COPY_PARAMS_FROM_CONTEXT(st, c); #if (LIBAVFORMAT_VERSION_MAJOR < 58) + // FFmpeg < 4.0 ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::add_video_stream (" + (std::string)fmt->name + " : " + (std::string)av_get_pix_fmt_name(c->pix_fmt) + ")", "c->codec_id", c->codec_id, "c->bit_rate", c->bit_rate, "c->pix_fmt", c->pix_fmt, "oc->oformat->flags", oc->oformat->flags, "AVFMT_RAWPICTURE", AVFMT_RAWPICTURE); #else ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::add_video_stream (" + (std::string)fmt->name + " : " + (std::string)av_get_pix_fmt_name(c->pix_fmt) + ")", "c->codec_id", c->codec_id, "c->bit_rate", c->bit_rate, "c->pix_fmt", c->pix_fmt, "oc->oformat->flags", oc->oformat->flags); @@ -1454,15 +1435,13 @@ void FFmpegWriter::open_video(AVFormatContext *oc, AVStream *st) { int adapter_num; // Use the hw device given in the environment variable HW_EN_DEVICE_SET or the default if not set adapter_num = openshot::Settings::Instance()->HW_EN_DEVICE_SET; - fprintf(stderr, "\n\nEncodiing Device Nr: %d\n", adapter_num); + std::clog << "Encoding Device Nr: " << adapter_num << "\n"; if (adapter_num < 3 && adapter_num >=0) { #if defined(__linux__) snprintf(adapter,sizeof(adapter),"/dev/dri/renderD%d", adapter_num+128); // Maybe 127 is better because the first card would be 1?! adapter_ptr = adapter; -#elif defined(_WIN32) - adapter_ptr = NULL; -#elif defined(__APPLE__) +#elif defined(_WIN32) || defined(__APPLE__) adapter_ptr = NULL; #endif } @@ -1472,20 +1451,18 @@ void FFmpegWriter::open_video(AVFormatContext *oc, AVStream *st) { // Check if it is there and writable #if defined(__linux__) if( adapter_ptr != NULL && access( adapter_ptr, W_OK ) == 0 ) { -#elif defined(_WIN32) - if( adapter_ptr != NULL ) { -#elif defined(__APPLE__) +#elif defined(_WIN32) || defined(__APPLE__) if( adapter_ptr != NULL ) { #endif ZmqLogger::Instance()->AppendDebugMethod("Encode Device present using device", "adapter", adapter_num); } else { adapter_ptr = NULL; // use default - ZmqLogger::Instance()->AppendDebugMethod("Encode Device not present using default"); + ZmqLogger::Instance()->AppendDebugMethod("Encode Device not present, using default"); } if (av_hwdevice_ctx_create(&hw_device_ctx, hw_en_av_device_type, adapter_ptr, NULL, 0) < 0) { - ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::open_video : Codec name: ", info.vcodec.c_str(), -1, " ERROR creating\n", -1); + ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::open_video ERROR creating hwdevice, Codec name:", info.vcodec.c_str(), -1); throw InvalidCodec("Could not create hwdevice", path); } } @@ -1715,20 +1692,25 @@ void FFmpegWriter::write_audio_packets(bool is_final) { int nb_samples = 0; // Convert audio samples - nb_samples = SWR_CONVERT(avr, // audio resample context - audio_converted->data, // output data pointers - audio_converted->linesize[0], // output plane size, in bytes. (0 if unknown) - audio_converted->nb_samples, // maximum number of samples that the output buffer can hold - audio_frame->data, // input data pointers - audio_frame->linesize[0], // input plane size, in bytes (0 if unknown) - audio_frame->nb_samples); // number of input samples to convert + nb_samples = SWR_CONVERT( + avr, // audio resample context + audio_converted->data, // output data pointers + audio_converted->linesize[0], // output plane size, in bytes. (0 if unknown) + audio_converted->nb_samples, // maximum number of samples that the output buffer can hold + audio_frame->data, // input data pointers + audio_frame->linesize[0], // input plane size, in bytes (0 if unknown) + audio_frame->nb_samples // number of input samples to convert + ); // Set remaining samples remaining_frame_samples = nb_samples * av_get_bytes_per_sample(AV_SAMPLE_FMT_S16); // Create a new array (to hold all resampled S16 audio samples) all_resampled_samples = (int16_t *) av_malloc( - sizeof(int16_t) * nb_samples * info.channels * (av_get_bytes_per_sample(output_sample_fmt) / av_get_bytes_per_sample(AV_SAMPLE_FMT_S16))); + sizeof(int16_t) * nb_samples * info.channels + * (av_get_bytes_per_sample(output_sample_fmt) / + av_get_bytes_per_sample(AV_SAMPLE_FMT_S16) ) + ); // Copy audio samples over original samples memcpy(all_resampled_samples, audio_converted->data[0], nb_samples * info.channels * av_get_bytes_per_sample(output_sample_fmt)); @@ -1759,8 +1741,14 @@ void FFmpegWriter::write_audio_packets(bool is_final) { // Copy frame samples into the packet samples array if (!is_final) //TODO: Make this more sane - memcpy(samples + (audio_input_position * (av_get_bytes_per_sample(output_sample_fmt) / av_get_bytes_per_sample(AV_SAMPLE_FMT_S16))), - all_resampled_samples + samples_position, diff * av_get_bytes_per_sample(output_sample_fmt)); + memcpy( + samples + (audio_input_position + * (av_get_bytes_per_sample(output_sample_fmt) / + av_get_bytes_per_sample(AV_SAMPLE_FMT_S16) ) + ), + all_resampled_samples + samples_position, + diff * av_get_bytes_per_sample(output_sample_fmt) + ); // Increment counters audio_input_position += diff; @@ -1775,8 +1763,16 @@ void FFmpegWriter::write_audio_packets(bool is_final) { // Convert to planar (if needed by audio codec) AVFrame *frame_final = AV_ALLOCATE_FRAME(); AV_RESET_FRAME(frame_final); - if (av_sample_fmt_is_planar(audio_codec_ctx->sample_fmt)) { - ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::write_audio_packets (2nd resampling for Planar formats)", "in_sample_fmt", output_sample_fmt, "out_sample_fmt", audio_codec_ctx->sample_fmt, "in_sample_rate", info.sample_rate, "out_sample_rate", info.sample_rate, "in_channels", info.channels, "out_channels", info.channels); + if (av_sample_fmt_is_planar(audio_codec_ctx->sample_fmt)) { + ZmqLogger::Instance()->AppendDebugMethod( + "FFmpegWriter::write_audio_packets (2nd resampling for Planar formats)", + "in_sample_fmt", output_sample_fmt, + "out_sample_fmt", audio_codec_ctx->sample_fmt, + "in_sample_rate", info.sample_rate, + "out_sample_rate", info.sample_rate, + "in_channels", info.channels, + "out_channels", info.channels + ); // setup resample context if (!avr_planar) { @@ -1799,31 +1795,39 @@ void FFmpegWriter::write_audio_packets(bool is_final) { // Create a new array final_samples_planar = (int16_t *) av_malloc( - sizeof(int16_t) * audio_frame->nb_samples * info.channels * (av_get_bytes_per_sample(output_sample_fmt) / av_get_bytes_per_sample(AV_SAMPLE_FMT_S16))); + sizeof(int16_t) * audio_frame->nb_samples * info.channels + * (av_get_bytes_per_sample(output_sample_fmt) / + av_get_bytes_per_sample(AV_SAMPLE_FMT_S16) ) + ); // Copy audio into buffer for frame memcpy(final_samples_planar, samples, audio_frame->nb_samples * info.channels * av_get_bytes_per_sample(output_sample_fmt)); // Fill input frame with sample data - avcodec_fill_audio_frame(audio_frame, info.channels, output_sample_fmt, (uint8_t *) final_samples_planar, - audio_encoder_buffer_size, 0); + avcodec_fill_audio_frame(audio_frame, info.channels, output_sample_fmt, + (uint8_t *) final_samples_planar, audio_encoder_buffer_size, 0); // Create output frame (and allocate arrays) frame_final->nb_samples = audio_input_frame_size; - av_samples_alloc(frame_final->data, frame_final->linesize, info.channels, frame_final->nb_samples, audio_codec_ctx->sample_fmt, 0); + av_samples_alloc(frame_final->data, frame_final->linesize, info.channels, + frame_final->nb_samples, audio_codec_ctx->sample_fmt, 0); // Convert audio samples - int nb_samples = SWR_CONVERT(avr_planar, // audio resample context - frame_final->data, // output data pointers - frame_final->linesize[0], // output plane size, in bytes. (0 if unknown) - frame_final->nb_samples, // maximum number of samples that the output buffer can hold - audio_frame->data, // input data pointers - audio_frame->linesize[0], // input plane size, in bytes (0 if unknown) - audio_frame->nb_samples); // number of input samples to convert + int nb_samples = SWR_CONVERT( + avr_planar, // audio resample context + frame_final->data, // output data pointers + frame_final->linesize[0], // output plane size, in bytes. (0 if unknown) + frame_final->nb_samples, // maximum number of samples that the output buffer can hold + audio_frame->data, // input data pointers + audio_frame->linesize[0], // input plane size, in bytes (0 if unknown) + audio_frame->nb_samples // number of input samples to convert + ); // Copy audio samples over original samples - if (nb_samples > 0) - memcpy(samples, frame_final->data[0], nb_samples * av_get_bytes_per_sample(audio_codec_ctx->sample_fmt) * info.channels); + if (nb_samples > 0) { + memcpy(samples, frame_final->data[0], + nb_samples * av_get_bytes_per_sample(audio_codec_ctx->sample_fmt) * info.channels); + } // deallocate AVFrame av_freep(&(audio_frame->data[0])); @@ -1835,17 +1839,22 @@ void FFmpegWriter::write_audio_packets(bool is_final) { } else { // Create a new array final_samples = (int16_t *) av_malloc( - sizeof(int16_t) * audio_input_position * (av_get_bytes_per_sample(audio_codec_ctx->sample_fmt) / av_get_bytes_per_sample(AV_SAMPLE_FMT_S16))); + sizeof(int16_t) * audio_input_position + * (av_get_bytes_per_sample(audio_codec_ctx->sample_fmt) / + av_get_bytes_per_sample(AV_SAMPLE_FMT_S16) ) + ); // Copy audio into buffer for frame - memcpy(final_samples, samples, audio_input_position * av_get_bytes_per_sample(audio_codec_ctx->sample_fmt)); + memcpy(final_samples, samples, + audio_input_position * av_get_bytes_per_sample(audio_codec_ctx->sample_fmt)); // Init the nb_samples property frame_final->nb_samples = audio_input_frame_size; // Fill the final_frame AVFrame with audio (non planar) - avcodec_fill_audio_frame(frame_final, audio_codec_ctx->channels, audio_codec_ctx->sample_fmt, (uint8_t *) final_samples, - audio_encoder_buffer_size, 0); + avcodec_fill_audio_frame(frame_final, audio_codec_ctx->channels, + audio_codec_ctx->sample_fmt, (uint8_t *) final_samples, + audio_encoder_buffer_size, 0); } // Increment PTS (in samples) @@ -1916,10 +1925,7 @@ void FFmpegWriter::write_audio_packets(bool is_final) { pkt.flags |= AV_PKT_FLAG_KEY; /* write the compressed frame in the media file */ - int error_code = av_interleaved_write_frame(oc, &pkt); - if (error_code < 0) { - ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::write_audio_packets ERROR [" + (std::string) av_err2str(error_code) + "]", "error_code", error_code); - } + error_code = av_interleaved_write_frame(oc, &pkt); } if (error_code < 0) { @@ -2020,7 +2026,10 @@ void FFmpegWriter::process_video_packet(std::shared_ptr frame) { } else #endif // HAVE_HW_ACCEL { - frame_final = allocate_avframe((AVPixelFormat)(video_st->codecpar->format), info.width, info.height, &bytes_final, NULL); + frame_final = allocate_avframe( + (AVPixelFormat)(video_st->codecpar->format), + info.width, info.height, &bytes_final, NULL + ); } #else AVFrame *frame_final = allocate_avframe(video_codec_ctx->pix_fmt, info.width, info.height, &bytes_final, NULL); @@ -2032,7 +2041,7 @@ void FFmpegWriter::process_video_packet(std::shared_ptr frame) { // Resize & convert pixel format sws_scale(scaler, frame_source->data, frame_source->linesize, 0, - source_image_height, frame_final->data, frame_final->linesize); + source_image_height, frame_final->data, frame_final->linesize); // Add resized AVFrame to av_frames map #pragma omp critical (av_frames_section) @@ -2048,11 +2057,15 @@ void FFmpegWriter::process_video_packet(std::shared_ptr frame) { // write video frame bool FFmpegWriter::write_video_packet(std::shared_ptr frame, AVFrame *frame_final) { #if (LIBAVFORMAT_VERSION_MAJOR >= 58) - ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::write_video_packet", "frame->number", frame->number, "oc->oformat->flags", oc->oformat->flags); + // FFmpeg 4.0+ + ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::write_video_packet", + "frame->number", frame->number, "oc->oformat->flags", oc->oformat->flags); if (AV_GET_CODEC_TYPE(video_st) == AVMEDIA_TYPE_VIDEO && AV_FIND_DECODER_CODEC_ID(video_st) == AV_CODEC_ID_RAWVIDEO) { #else - ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::write_video_packet", "frame->number", frame->number, "oc->oformat->flags & AVFMT_RAWPICTURE", oc->oformat->flags & AVFMT_RAWPICTURE); + ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::write_video_packet", + "frame->number", frame->number, + "oc->oformat->flags & AVFMT_RAWPICTURE", oc->oformat->flags & AVFMT_RAWPICTURE); if (oc->oformat->flags & AVFMT_RAWPICTURE) { #endif @@ -2088,9 +2101,6 @@ bool FFmpegWriter::write_video_packet(std::shared_ptr frame, AVFrame *fra pkt.size = 0; pkt.pts = pkt.dts = AV_NOPTS_VALUE; - // Pointer for video buffer (if using old FFmpeg version) - uint8_t *video_outbuf = NULL; - // Increment PTS (in frames and scaled to the codec's timebase) write_video_count += av_rescale_q(1, av_make_q(info.fps.den, info.fps.num), video_codec_ctx->time_base); @@ -2099,17 +2109,17 @@ bool FFmpegWriter::write_video_packet(std::shared_ptr frame, AVFrame *fra #if HAVE_HW_ACCEL if (hw_en_on && hw_en_supported) { if (!(hw_frame = av_frame_alloc())) { - fprintf(stderr, "Error code: av_hwframe_alloc\n"); + std::clog << "Error code: av_hwframe_alloc\n"; } if (av_hwframe_get_buffer(video_codec_ctx->hw_frames_ctx, hw_frame, 0) < 0) { - fprintf(stderr, "Error code: av_hwframe_get_buffer\n"); + std::clog << "Error code: av_hwframe_get_buffer\n"; } if (!hw_frame->hw_frames_ctx) { - fprintf(stderr, "Error hw_frames_ctx.\n"); + std::clog << "Error hw_frames_ctx.\n"; } hw_frame->format = AV_PIX_FMT_NV12; if ( av_hwframe_transfer_data(hw_frame, frame_final, 0) < 0) { - fprintf(stderr, "Error while transferring frame data to surface.\n"); + std::clog << "Error while transferring frame data to surface.\n"; } av_frame_copy_props(hw_frame, frame_final); } @@ -2133,10 +2143,10 @@ bool FFmpegWriter::write_video_packet(std::shared_ptr frame, AVFrame *fra if (ret < 0 ) { ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::write_video_packet (Frame not sent)"); if (ret == AVERROR(EAGAIN) ) { - std::cerr << "Frame EAGAIN" << "\n"; + std::clog << "Frame EAGAIN\n"; } if (ret == AVERROR_EOF ) { - std::cerr << "Frame AVERROR_EOF" << "\n"; + std::clog << "Frame AVERROR_EOF\n"; } avcodec_send_frame(video_codec_ctx, NULL); } @@ -2156,34 +2166,14 @@ bool FFmpegWriter::write_video_packet(std::shared_ptr frame, AVFrame *fra } } #else -#if LIBAVFORMAT_VERSION_MAJOR >= 54 // Write video packet (older than FFmpeg 3.2) error_code = avcodec_encode_video2(video_codec_ctx, &pkt, frame_final, &got_packet_ptr); if (error_code != 0) { - std::cerr << "Frame AVERROR_EOF" << "\n"; + ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::write_video_packet ERROR [" + (std::string) av_err2str(error_code) + "]", "error_code", error_code); } if (got_packet_ptr == 0) { - std::cerr << "Frame gotpacket error" << "\n"; + ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::write_video_packet (Frame gotpacket error)"); } -#else - // Write video packet (even older versions of FFmpeg) - int video_outbuf_size = 200000; - video_outbuf = (uint8_t*) av_malloc(200000); - - /* encode the image */ - int out_size = avcodec_encode_video(video_codec_ctx, video_outbuf, video_outbuf_size, frame_final); - - /* if zero size, it means the image was buffered */ - if (out_size > 0) { - if(video_codec_ctx->coded_frame->key_frame) - pkt.flags |= AV_PKT_FLAG_KEY; - pkt.data= video_outbuf; - pkt.size= out_size; - - // got data back (so encode this frame) - got_packet_ptr = 1; - } -#endif // LIBAVFORMAT_VERSION_MAJOR >= 54 #endif // IS_FFMPEG_3_2 /* if zero size, it means the image was buffered */ @@ -2203,17 +2193,13 @@ bool FFmpegWriter::write_video_packet(std::shared_ptr frame, AVFrame *fra pkt.stream_index = video_st->index; /* write the compressed frame in the media file */ - int error_code = av_interleaved_write_frame(oc, &pkt); - if (error_code < 0) { - ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::write_video_packet ERROR [" + (std::string) av_err2str(error_code) + "]", "error_code", error_code); + int result = av_interleaved_write_frame(oc, &pkt); + if (result < 0) { + ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::write_video_packet ERROR [" + (std::string) av_err2str(result) + "]", "result", result); return false; } } - // Deallocate memory (if needed) - if (video_outbuf) - delete[] video_outbuf; - // Deallocate packet AV_FREE_PACKET(&pkt); #if HAVE_HW_ACCEL @@ -2248,12 +2234,14 @@ void FFmpegWriter::InitScalers(int source_width, int source_height) { // Init the software scaler from FFMpeg #if HAVE_HW_ACCEL if (hw_en_on && hw_en_supported) { - img_convert_ctx = sws_getContext(source_width, source_height, PIX_FMT_RGBA, info.width, info.height, AV_PIX_FMT_NV12, scale_mode, NULL, NULL, NULL); + img_convert_ctx = sws_getContext(source_width, source_height, PIX_FMT_RGBA, + info.width, info.height, AV_PIX_FMT_NV12, scale_mode, NULL, NULL, NULL); } else #endif // HAVE_HW_ACCEL { - img_convert_ctx = sws_getContext(source_width, source_height, PIX_FMT_RGBA, info.width, info.height, AV_GET_CODEC_PIXEL_FORMAT(video_st, video_st->codec), scale_mode, - NULL, NULL, NULL); + img_convert_ctx = sws_getContext(source_width, source_height, PIX_FMT_RGBA, + info.width, info.height, AV_GET_CODEC_PIXEL_FORMAT(video_st, video_st->codec), + scale_mode, NULL, NULL, NULL); } // Add rescaler to vector diff --git a/src/Frame.cpp b/src/Frame.cpp index dcd26dd8..b4a8b2d4 100644 --- a/src/Frame.cpp +++ b/src/Frame.cpp @@ -29,6 +29,21 @@ */ #include "../include/Frame.h" +#include "JuceHeader.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include // for std::this_thread::sleep_for #include // for std::chrono::milliseconds @@ -36,57 +51,31 @@ using namespace std; using namespace openshot; -// Constructor - blank frame (300x200 blank image, 48kHz audio silence) -Frame::Frame() : number(1), pixel_ratio(1,1), channels(2), width(1), height(1), color("#000000"), - channel_layout(LAYOUT_STEREO), sample_rate(44100), qbuffer(NULL), has_audio_data(false), has_image_data(false), - max_audio_sample(0) -{ - // Init the image magic and audio buffer - audio = std::shared_ptr(new juce::AudioSampleBuffer(channels, 0)); - - // initialize the audio samples to zero (silence) - audio->clear(); -} - -// Constructor - image only (48kHz audio silence) -Frame::Frame(int64_t number, int width, int height, std::string color) - : number(number), pixel_ratio(1,1), channels(2), width(width), height(height), color(color), - channel_layout(LAYOUT_STEREO), sample_rate(44100), qbuffer(NULL), has_audio_data(false), has_image_data(false), - max_audio_sample(0) -{ - // Init the image magic and audio buffer - audio = std::shared_ptr(new juce::AudioSampleBuffer(channels, 0)); - - // initialize the audio samples to zero (silence) - audio->clear(); -} - -// Constructor - audio only (300x200 blank image) -Frame::Frame(int64_t number, int samples, int channels) : - number(number), pixel_ratio(1,1), channels(channels), width(1), height(1), color("#000000"), - channel_layout(LAYOUT_STEREO), sample_rate(44100), qbuffer(NULL), has_audio_data(false), has_image_data(false), - max_audio_sample(0) -{ - // Init the image magic and audio buffer - audio = std::shared_ptr(new juce::AudioSampleBuffer(channels, samples)); - - // initialize the audio samples to zero (silence) - audio->clear(); -} - // Constructor - image & audio Frame::Frame(int64_t number, int width, int height, std::string color, int samples, int channels) - : number(number), pixel_ratio(1,1), channels(channels), width(width), height(height), color(color), - channel_layout(LAYOUT_STEREO), sample_rate(44100), qbuffer(NULL), has_audio_data(false), has_image_data(false), + : audio(std::make_shared(channels, samples)), + number(number), width(width), height(height), + pixel_ratio(1,1), color(color), qbuffer(NULL), + channels(channels), channel_layout(LAYOUT_STEREO), + sample_rate(44100), + has_audio_data(false), has_image_data(false), max_audio_sample(0) { - // Init the image magic and audio buffer - audio = std::shared_ptr(new juce::AudioSampleBuffer(channels, samples)); - - // initialize the audio samples to zero (silence) + // zero (fill with silence) the audio buffer audio->clear(); } +// Delegating Constructor - blank frame +Frame::Frame() : Frame::Frame(1, 1, 1, "#000000", 0, 2) {}; + +// Delegating Constructor - image only +Frame::Frame(int64_t number, int width, int height, std::string color) + : Frame::Frame(number, width, height, color, 0, 2) {}; + +// Delegating Constructor - audio only +Frame::Frame(int64_t number, int samples, int channels) + : Frame::Frame(number, 1, 1, "#000000", samples, channels) {}; + // Copy constructor Frame::Frame ( const Frame &other ) @@ -120,11 +109,11 @@ void Frame::DeepCopy(const Frame& other) max_audio_sample = other.max_audio_sample; if (other.image) - image = std::shared_ptr(new QImage(*(other.image))); + image = std::make_shared(*(other.image)); if (other.audio) - audio = std::shared_ptr(new juce::AudioSampleBuffer(*(other.audio))); + audio = std::make_shared(*(other.audio)); if (other.wave_image) - wave_image = std::shared_ptr(new QImage(*(other.wave_image))); + wave_image = std::make_shared(*(other.wave_image)); } // Destructor @@ -141,7 +130,7 @@ void Frame::Display() // Only create the QApplication once static int argc = 1; static char* argv[1] = {NULL}; - previewApp = std::shared_ptr(new QApplication(argc, argv)); + previewApp = std::make_shared(argc, argv); } // Get preview image @@ -155,7 +144,8 @@ void Frame::Display() int new_height = previewImage->size().height() * pixel_ratio.Reciprocal().ToDouble(); // Resize to fix DAR - previewImage = std::shared_ptr(new QImage(previewImage->scaled(new_width, new_height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation))); + previewImage = std::make_shared(previewImage->scaled( + new_width, new_height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); } // Create window @@ -231,7 +221,8 @@ std::shared_ptr Frame::GetWaveform(int width, int height, int Red, int G } // Create blank image - wave_image = std::shared_ptr(new QImage(total_width, total_height, QImage::Format_RGBA8888_Premultiplied)); + wave_image = std::make_shared( + total_width, total_height, QImage::Format_RGBA8888_Premultiplied); wave_image->fill(QColor(0,0,0,0)); // Load QPainter with wave_image device @@ -256,13 +247,13 @@ std::shared_ptr Frame::GetWaveform(int width, int height, int Red, int G // Resize Image (if requested) if (width != total_width || height != total_height) { QImage scaled_wave_image = wave_image->scaled(width, height, Qt::IgnoreAspectRatio, Qt::FastTransformation); - wave_image = std::shared_ptr(new QImage(scaled_wave_image)); + wave_image = std::make_shared(scaled_wave_image); } } else { // No audio samples present - wave_image = std::shared_ptr(new QImage(width, height, QImage::Format_RGBA8888_Premultiplied)); + wave_image = std::make_shared(width, height, QImage::Format_RGBA8888_Premultiplied); wave_image->fill(QColor(QString::fromStdString("#000000"))); } @@ -297,7 +288,7 @@ void Frame::DisplayWaveform() // Only create the QApplication once static int argc = 1; static char* argv[1] = {NULL}; - previewApp = std::shared_ptr(new QApplication(argc, argv)); + previewApp = std::make_shared(argc, argv); } // Create window @@ -602,11 +593,15 @@ void Frame::Save(std::string path, float scale, std::string format, int quality) int new_height = previewImage->size().height() * pixel_ratio.Reciprocal().ToDouble(); // Resize to fix DAR - previewImage = std::shared_ptr(new QImage(previewImage->scaled(new_width, new_height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation))); + previewImage = std::make_shared(previewImage->scaled( + new_width, new_height, + Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); } // Resize image - previewImage = std::shared_ptr(new QImage(previewImage->scaled(new_width * scale, new_height * scale, Qt::KeepAspectRatio, Qt::SmoothTransformation))); + previewImage = std::make_shared(previewImage->scaled( + new_width * scale, new_height * scale, + Qt::KeepAspectRatio, Qt::SmoothTransformation)); } // Save image @@ -618,7 +613,8 @@ void Frame::Thumbnail(std::string path, int new_width, int new_height, std::stri std::string background_color, bool ignore_aspect, std::string format, int quality, float rotate) { // Create blank thumbnail image & fill background color - std::shared_ptr thumbnail = std::shared_ptr(new QImage(new_width, new_height, QImage::Format_RGBA8888_Premultiplied)); + auto thumbnail = std::make_shared( + new_width, new_height, QImage::Format_RGBA8888_Premultiplied); thumbnail->fill(QColor(QString::fromStdString(background_color))); // Create painter @@ -636,16 +632,22 @@ void Frame::Thumbnail(std::string path, int new_width, int new_height, std::stri int aspect_height = previewImage->size().height() * pixel_ratio.Reciprocal().ToDouble(); // Resize to fix DAR - previewImage = std::shared_ptr(new QImage(previewImage->scaled(aspect_width, aspect_height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation))); + previewImage = std::make_shared(previewImage->scaled( + aspect_width, aspect_height, + Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); } // Resize frame image if (ignore_aspect) // Ignore aspect ratio - previewImage = std::shared_ptr(new QImage(previewImage->scaled(new_width, new_height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation))); + previewImage = std::make_shared(previewImage->scaled( + new_width, new_height, + Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); else // Maintain aspect ratio - previewImage = std::shared_ptr(new QImage(previewImage->scaled(new_width, new_height, Qt::KeepAspectRatio, Qt::SmoothTransformation))); + previewImage = std::make_shared(previewImage->scaled( + new_width, new_height, + Qt::KeepAspectRatio, Qt::SmoothTransformation)); // Composite frame image onto background (centered) int x = (new_width - previewImage->size().width()) / 2.0; // center @@ -669,14 +671,16 @@ void Frame::Thumbnail(std::string path, int new_width, int new_height, std::stri // Overlay Image (if any) if (overlay_path != "") { // Open overlay - std::shared_ptr overlay = std::shared_ptr(new QImage()); + auto overlay = std::make_shared(); overlay->load(QString::fromStdString(overlay_path)); // Set pixel format - overlay = std::shared_ptr(new QImage(overlay->convertToFormat(QImage::Format_RGBA8888_Premultiplied))); + overlay = std::make_shared( + overlay->convertToFormat(QImage::Format_RGBA8888_Premultiplied)); // Resize to fit - overlay = std::shared_ptr(new QImage(overlay->scaled(new_width, new_height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation))); + overlay = std::make_shared(overlay->scaled( + new_width, new_height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); // Composite onto thumbnail painter.setCompositionMode(QPainter::CompositionMode_SourceOver); @@ -687,14 +691,16 @@ void Frame::Thumbnail(std::string path, int new_width, int new_height, std::stri // Mask Image (if any) if (mask_path != "") { // Open mask - std::shared_ptr mask = std::shared_ptr(new QImage()); + auto mask = std::make_shared(); mask->load(QString::fromStdString(mask_path)); // Set pixel format - mask = std::shared_ptr(new QImage(mask->convertToFormat(QImage::Format_RGBA8888_Premultiplied))); + mask = std::make_shared( + mask->convertToFormat(QImage::Format_RGBA8888_Premultiplied)); // Resize to fit - mask = std::shared_ptr(new QImage(mask->scaled(new_width, new_height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation))); + mask = std::make_shared(mask->scaled( + new_width, new_height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); // Negate mask mask->invertPixels(); @@ -747,7 +753,7 @@ void Frame::AddColor(int new_width, int new_height, std::string new_color) const GenericScopedLock lock(addingImageSection); #pragma omp critical (AddImage) { - image = std::shared_ptr(new QImage(new_width, new_height, QImage::Format_RGBA8888_Premultiplied)); + image = std::make_shared(new_width, new_height, QImage::Format_RGBA8888_Premultiplied); // Fill with solid color image->fill(QColor(QString::fromStdString(color))); @@ -759,30 +765,31 @@ void Frame::AddColor(int new_width, int new_height, std::string new_color) } // Add (or replace) pixel data to the frame -void Frame::AddImage(int new_width, int new_height, int bytes_per_pixel, QImage::Format type, const unsigned char *pixels_) +void Frame::AddImage( + int new_width, int new_height, int bytes_per_pixel, + QImage::Format type, const unsigned char *pixels_) { // Create new buffer - const GenericScopedLock lock(addingImageSection); - int buffer_size = new_width * new_height * bytes_per_pixel; - qbuffer = new unsigned char[buffer_size](); - - // Copy buffer data - memcpy((unsigned char*)qbuffer, pixels_, buffer_size); - - // Create new image object, and fill with pixel data - #pragma omp critical (AddImage) { - image = std::shared_ptr(new QImage(qbuffer, new_width, new_height, new_width * bytes_per_pixel, type, (QImageCleanupFunction) &openshot::Frame::cleanUpBuffer, (void*) qbuffer)); + const GenericScopedLock lock(addingImageSection); + int buffer_size = new_width * new_height * bytes_per_pixel; + qbuffer = new unsigned char[buffer_size](); - // Always convert to RGBA8888 (if different) - if (image->format() != QImage::Format_RGBA8888_Premultiplied) - *image = image->convertToFormat(QImage::Format_RGBA8888_Premultiplied); + // Copy buffer data + memcpy((unsigned char*)qbuffer, pixels_, buffer_size); - // Update height and width - width = image->width(); - height = image->height(); - has_image_data = true; - } + } // Release addingImageSection lock + + // Create new image object from pixel data + auto new_image = std::make_shared( + qbuffer, + new_width, new_height, + new_width * bytes_per_pixel, + type, + (QImageCleanupFunction) &openshot::Frame::cleanUpBuffer, + (void*) qbuffer + ); + AddImage(new_image); } // Add (or replace) pixel data to the frame @@ -822,7 +829,6 @@ void Frame::AddImage(std::shared_ptr new_image, bool only_odd_lines) AddImage(new_image); } else { - // Ignore image of different sizes or formats bool ret=false; #pragma omp critical (AddImage) @@ -831,7 +837,8 @@ void Frame::AddImage(std::shared_ptr new_image, bool only_odd_lines) ret = true; } else if (new_image->format() != QImage::Format_RGBA8888_Premultiplied) { - new_image = std::shared_ptr(new QImage(new_image->convertToFormat(QImage::Format_RGBA8888_Premultiplied))); + new_image = std::make_shared( + new_image->convertToFormat(QImage::Format_RGBA8888_Premultiplied)); } } if (ret) { @@ -941,7 +948,8 @@ std::shared_ptr Frame::GetMagickImage() const QRgb *tmpBits = (const QRgb*)image->constBits(); // Create new image object, and fill with pixel data - std::shared_ptr magick_image = std::shared_ptr(new Magick::Image(image->width(), image->height(),"RGBA", Magick::CharPixel, tmpBits)); + auto magick_image = std::make_shared( + image->width(), image->height(),"RGBA", Magick::CharPixel, tmpBits); // Give image a transparent background color magick_image->backgroundColor(Magick::Color("none")); @@ -970,7 +978,9 @@ void Frame::AddMagickImage(std::shared_ptr new_image) MagickCore::ExportImagePixels(new_image->constImage(), 0, 0, new_image->columns(), new_image->rows(), "RGBA", Magick::CharPixel, buffer, &exception); // Create QImage of frame data - image = std::shared_ptr(new QImage(qbuffer, width, height, width * BPP, QImage::Format_RGBA8888_Premultiplied, (QImageCleanupFunction) &cleanUpBuffer, (void*) qbuffer)); + image = std::make_shared( + qbuffer, width, height, width * BPP, QImage::Format_RGBA8888_Premultiplied, + (QImageCleanupFunction) &cleanUpBuffer, (void*) qbuffer); // Update height and width width = image->width(); diff --git a/src/FrameMapper.cpp b/src/FrameMapper.cpp index 33221173..d43cbcdf 100644 --- a/src/FrameMapper.cpp +++ b/src/FrameMapper.cpp @@ -478,7 +478,8 @@ std::shared_ptr FrameMapper::GetFrame(int64_t requested_frame) } // Create a new frame - std::shared_ptr frame = std::make_shared(frame_number, 1, 1, "#000000", samples_in_frame, channels_in_frame); + auto frame = std::make_shared( + frame_number, 1, 1, "#000000", samples_in_frame, channels_in_frame); frame->SampleRate(mapped_frame->SampleRate()); frame->ChannelsLayout(mapped_frame->ChannelsLayout()); @@ -488,13 +489,14 @@ std::shared_ptr FrameMapper::GetFrame(int64_t requested_frame) odd_frame = GetOrCreateFrame(mapped.Odd.Frame); if (odd_frame) - frame->AddImage(std::shared_ptr(new QImage(*odd_frame->GetImage())), true); + frame->AddImage(std::make_shared(*odd_frame->GetImage()), true); if (mapped.Odd.Frame != mapped.Even.Frame) { // Add even lines (if different than the previous image) std::shared_ptr even_frame; even_frame = GetOrCreateFrame(mapped.Even.Frame); if (even_frame) - frame->AddImage(std::shared_ptr(new QImage(*even_frame->GetImage())), false); + frame->AddImage( + std::make_shared(*even_frame->GetImage()), false); } // Resample audio on frame (if needed) diff --git a/src/ImageReader.cpp b/src/ImageReader.cpp index 9ce3a70f..9a001879 100644 --- a/src/ImageReader.cpp +++ b/src/ImageReader.cpp @@ -61,7 +61,7 @@ void ImageReader::Open() try { // load image - image = std::shared_ptr(new Magick::Image(path)); + image = std::make_shared(path); // Give image a transparent background color image->backgroundColor(Magick::Color("none")); @@ -126,7 +126,9 @@ std::shared_ptr ImageReader::GetFrame(int64_t requested_frame) throw ReaderClosed("The FFmpegReader is closed. Call Open() before calling this method.", path); // Create or get frame object - std::shared_ptr image_frame(new Frame(requested_frame, image->size().width(), image->size().height(), "#000000", 0, 2)); + auto image_frame = std::make_shared( + requested_frame, image->size().width(), image->size().height(), + "#000000", 0, 2); // Add Image data to frame image_frame->AddMagickImage(image); diff --git a/src/Qt/VideoRenderWidget.cpp b/src/Qt/VideoRenderWidget.cpp index 2bfe8fa2..4af1ac6a 100644 --- a/src/Qt/VideoRenderWidget.cpp +++ b/src/Qt/VideoRenderWidget.cpp @@ -29,7 +29,13 @@ */ #include "../../include/Qt/VideoRenderWidget.h" -#include +#include +#include +#include +#include +#include +#include + VideoRenderWidget::VideoRenderWidget(QWidget *parent) : QWidget(parent), renderer(new VideoRenderer(this)) diff --git a/src/QtHtmlReader.cpp b/src/QtHtmlReader.cpp index e27f1c0f..a93fdd21 100644 --- a/src/QtHtmlReader.cpp +++ b/src/QtHtmlReader.cpp @@ -62,7 +62,7 @@ void QtHtmlReader::Open() if (!is_open) { // create image - image = std::shared_ptr(new QImage(width, height, QImage::Format_RGBA8888_Premultiplied)); + image = std::make_shared(width, height, QImage::Format_RGBA8888_Premultiplied); image->fill(QColor(background_color.c_str())); //start painting @@ -162,7 +162,9 @@ std::shared_ptr QtHtmlReader::GetFrame(int64_t requested_frame) if (image) { // Create or get frame object - std::shared_ptr image_frame(new Frame(requested_frame, image->size().width(), image->size().height(), background_color, 0, 2)); + auto image_frame = std::make_shared( + requested_frame, image->size().width(), image->size().height(), + background_color, 0, 2); // Add Image data to frame image_frame->AddImage(image); @@ -171,7 +173,8 @@ std::shared_ptr QtHtmlReader::GetFrame(int64_t requested_frame) return image_frame; } else { // return empty frame - std::shared_ptr image_frame(new Frame(1, 640, 480, background_color, 0, 2)); + auto image_frame = std::make_shared( + 1, 640, 480, background_color, 0, 2); // return frame object return image_frame; diff --git a/src/QtImageReader.cpp b/src/QtImageReader.cpp index 0f4fdc38..67218cba 100644 --- a/src/QtImageReader.cpp +++ b/src/QtImageReader.cpp @@ -82,7 +82,8 @@ void QtImageReader::Open() ResvgRenderer renderer(path); if (renderer.isValid()) { - image = std::shared_ptr(new QImage(renderer.defaultSize(), QImage::Format_RGBA8888_Premultiplied)); + image = std::make_shared( + renderer.defaultSize(), QImage::Format_RGBA8888_Premultiplied); image->fill(Qt::transparent); QPainter p(image.get()); @@ -95,7 +96,7 @@ void QtImageReader::Open() if (!loaded) { // Attempt to open file using Qt's build in image processing capabilities - image = std::shared_ptr(new QImage()); + image = std::make_shared(); success = image->load(path); } @@ -236,7 +237,9 @@ std::shared_ptr QtImageReader::GetFrame(int64_t requested_frame) svg_size.scale(max_width, max_height, Qt::KeepAspectRatio); // Create empty QImage - cached_image = std::shared_ptr(new QImage(QSize(svg_size.width(), svg_size.height()), QImage::Format_RGBA8888_Premultiplied)); + cached_image = std::make_shared( + QSize(svg_size.width(), svg_size.height()), + QImage::Format_RGBA8888_Premultiplied); cached_image->fill(Qt::transparent); // Render SVG into QImage @@ -251,7 +254,8 @@ std::shared_ptr QtImageReader::GetFrame(int64_t requested_frame) if (!rendered) { // We need to resize the original image to a smaller image (for performance reasons) // Only do this once, to prevent tons of unneeded scaling operations - cached_image = std::shared_ptr(new QImage(image->scaled(max_width, max_height, Qt::KeepAspectRatio, Qt::SmoothTransformation))); + cached_image = std::make_shared(image->scaled( + max_width, max_height, Qt::KeepAspectRatio, Qt::SmoothTransformation)); } // Set max size (to later determine if max_size is changed) @@ -260,7 +264,10 @@ std::shared_ptr QtImageReader::GetFrame(int64_t requested_frame) } // Create or get frame object - std::shared_ptr image_frame(new Frame(requested_frame, cached_image->width(), cached_image->height(), "#000000", Frame::GetSamplesPerFrame(requested_frame, info.fps, info.sample_rate, info.channels), info.channels)); + auto image_frame = std::make_shared( + requested_frame, cached_image->width(), cached_image->height(), "#000000", + Frame::GetSamplesPerFrame(requested_frame, info.fps, info.sample_rate, info.channels), + info.channels); // Add Image data to frame image_frame->AddImage(cached_image); diff --git a/src/QtTextReader.cpp b/src/QtTextReader.cpp index 4764cd0d..cbab42df 100644 --- a/src/QtTextReader.cpp +++ b/src/QtTextReader.cpp @@ -67,7 +67,7 @@ void QtTextReader::Open() if (!is_open) { // create image - image = std::shared_ptr(new QImage(width, height, QImage::Format_RGBA8888_Premultiplied)); + image = std::make_shared(width, height, QImage::Format_RGBA8888_Premultiplied); image->fill(QColor(background_color.c_str())); QPainter painter; @@ -179,7 +179,9 @@ std::shared_ptr QtTextReader::GetFrame(int64_t requested_frame) if (image) { // Create or get frame object - std::shared_ptr image_frame(new Frame(requested_frame, image->size().width(), image->size().height(), background_color, 0, 2)); + auto image_frame = std::make_shared( + requested_frame, image->size().width(), image->size().height(), + background_color, 0, 2); // Add Image data to frame image_frame->AddImage(image); @@ -188,7 +190,7 @@ std::shared_ptr QtTextReader::GetFrame(int64_t requested_frame) return image_frame; } else { // return empty frame - std::shared_ptr image_frame(new Frame(1, 640, 480, background_color, 0, 2)); + auto image_frame = std::make_shared(1, 640, 480, background_color, 0, 2); // return frame object return image_frame; diff --git a/src/TextReader.cpp b/src/TextReader.cpp index e317700c..be8c7375 100644 --- a/src/TextReader.cpp +++ b/src/TextReader.cpp @@ -66,7 +66,8 @@ void TextReader::Open() if (!is_open) { // create image - image = std::shared_ptr(new Magick::Image(Magick::Geometry(width,height), Magick::Color(background_color))); + image = std::make_shared( + Magick::Geometry(width,height), Magick::Color(background_color)); // Give image a transparent background color image->backgroundColor(Magick::Color("none")); @@ -166,10 +167,12 @@ std::shared_ptr TextReader::GetFrame(int64_t requested_frame) if (image) { // Create or get frame object - std::shared_ptr image_frame(new Frame(requested_frame, image->size().width(), image->size().height(), "#000000", 0, 2)); + auto image_frame = std::make_shared( + requested_frame, image->size().width(), image->size().height(), + "#000000", 0, 2); // Add Image data to frame - std::shared_ptr copy_image(new Magick::Image(*image.get())); + auto copy_image = std::make_shared(*image.get()); copy_image->modifyImage(); // actually copy the image data to this object //TODO: Reimplement this with QImage image_frame->AddMagickImage(copy_image); @@ -178,7 +181,7 @@ std::shared_ptr TextReader::GetFrame(int64_t requested_frame) return image_frame; } else { // return empty frame - std::shared_ptr image_frame(new Frame(1, 640, 480, "#000000", 0, 2)); + auto image_frame = std::make_shared(1, 640, 480, "#000000", 0, 2); // return frame object return image_frame; diff --git a/src/Timeline.cpp b/src/Timeline.cpp index 736bee15..f7f018e7 100644 --- a/src/Timeline.cpp +++ b/src/Timeline.cpp @@ -69,7 +69,13 @@ Timeline::Timeline(int width, int height, Fraction fps, int sample_rate, int cha info.acodec = "openshot::timeline"; info.vcodec = "openshot::timeline"; - // Init max image size + // Configure OpenMP parallelism + // Default number of threads per block + omp_set_num_threads(OPEN_MP_NUM_PROCESSORS); + // Allow nested parallel sections as deeply as supported + omp_set_max_active_levels(OPEN_MP_MAX_ACTIVE); + + // Init max image size SetMaxSize(info.width, info.height); // Init cache @@ -78,7 +84,7 @@ Timeline::Timeline(int width, int height, Fraction fps, int sample_rate, int cha } // Constructor for the timeline (which loads a JSON structure from a file path, and initializes a timeline) -Timeline::Timeline(std::string projectPath, bool convert_absolute_paths) : +Timeline::Timeline(const std::string& projectPath, bool convert_absolute_paths) : is_open(false), auto_map_clips(true), managed_cache(true), path(projectPath) { // Create CrashHandler and Attach (incase of errors) @@ -196,6 +202,12 @@ Timeline::Timeline(std::string projectPath, bool convert_absolute_paths) : info.has_video = true; info.has_audio = true; + // Configure OpenMP parallelism + // Default number of threads per section + omp_set_num_threads(OPEN_MP_NUM_PROCESSORS); + // Allow nested parallel sections as deeply as supported + omp_set_max_active_levels(OPEN_MP_MAX_ACTIVE); + // Init max image size SetMaxSize(info.width, info.height); @@ -716,10 +728,6 @@ std::shared_ptr Timeline::GetFrame(int64_t requested_frame) #pragma omp critical (T_GetFrame) nearby_clips = find_intersecting_clips(requested_frame, minimum_frames, true); - omp_set_num_threads(OPEN_MP_NUM_PROCESSORS); - // Allow nested OpenMP sections - omp_set_nested(true); - // Debug output ZmqLogger::Instance()->AppendDebugMethod("Timeline::GetFrame", "requested_frame", requested_frame, "minimum_frames", minimum_frames, "OPEN_MP_NUM_PROCESSORS", OPEN_MP_NUM_PROCESSORS); diff --git a/src/effects/Bars.cpp b/src/effects/Bars.cpp index 7180bdcd..42898054 100644 --- a/src/effects/Bars.cpp +++ b/src/effects/Bars.cpp @@ -68,7 +68,8 @@ std::shared_ptr Bars::GetFrame(std::shared_ptr frame, int64_t fram std::shared_ptr frame_image = frame->GetImage(); // Get bar color (and create small color image) - std::shared_ptr tempColor = std::shared_ptr(new QImage(frame_image->width(), 1, QImage::Format_RGBA8888_Premultiplied)); + auto tempColor = std::make_shared( + frame_image->width(), 1, QImage::Format_RGBA8888_Premultiplied); tempColor->fill(QColor(QString::fromStdString(color.GetColorHex(frame_number)))); // Get current keyframe values diff --git a/src/effects/Crop.cpp b/src/effects/Crop.cpp index 170f6fa5..6813cf80 100644 --- a/src/effects/Crop.cpp +++ b/src/effects/Crop.cpp @@ -68,7 +68,8 @@ std::shared_ptr Crop::GetFrame(std::shared_ptr frame, int64_t fram std::shared_ptr frame_image = frame->GetImage(); // Get transparent color (and create small transparent image) - std::shared_ptr tempColor = std::shared_ptr(new QImage(frame_image->width(), 1, QImage::Format_RGBA8888_Premultiplied)); + auto tempColor = std::make_shared( + frame_image->width(), 1, QImage::Format_RGBA8888_Premultiplied); tempColor->fill(QColor(QString::fromStdString("transparent"))); // Get current keyframe values diff --git a/src/effects/Deinterlace.cpp b/src/effects/Deinterlace.cpp index 24402288..71373080 100644 --- a/src/effects/Deinterlace.cpp +++ b/src/effects/Deinterlace.cpp @@ -86,7 +86,9 @@ std::shared_ptr Deinterlace::GetFrame(std::shared_ptr frame, int64 } // Resize deinterlaced image back to original size, and update frame's image - image = std::shared_ptr(new QImage(deinterlaced_image.scaled(original_width, original_height, Qt::IgnoreAspectRatio, Qt::FastTransformation))); + image = std::make_shared(deinterlaced_image.scaled( + original_width, original_height, + Qt::IgnoreAspectRatio, Qt::FastTransformation)); // Update image on frame frame->AddImage(image); diff --git a/src/effects/Mask.cpp b/src/effects/Mask.cpp index c5ed16c1..d0e86cd1 100644 --- a/src/effects/Mask.cpp +++ b/src/effects/Mask.cpp @@ -84,13 +84,14 @@ std::shared_ptr Mask::GetFrame(std::shared_ptr frame, int64_t fram (original_mask && original_mask->size() != frame_image->size())) { // Only get mask if needed - std::shared_ptr mask_without_sizing = std::shared_ptr( - new QImage(*reader->GetFrame(frame_number)->GetImage())); + auto mask_without_sizing = std::make_shared( + *reader->GetFrame(frame_number)->GetImage()); // Resize mask image to match frame size - original_mask = std::shared_ptr(new QImage( - mask_without_sizing->scaled(frame_image->width(), frame_image->height(), Qt::IgnoreAspectRatio, - Qt::SmoothTransformation))); + original_mask = std::make_shared( + mask_without_sizing->scaled( + frame_image->width(), frame_image->height(), + Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); } } diff --git a/src/effects/Pixelate.cpp b/src/effects/Pixelate.cpp index ed219e4d..057b28e2 100644 --- a/src/effects/Pixelate.cpp +++ b/src/effects/Pixelate.cpp @@ -29,6 +29,12 @@ */ #include "../../include/effects/Pixelate.h" +#include "Json.h" + +#include +#include +#include +#include using namespace openshot; diff --git a/src/effects/Saturation.cpp b/src/effects/Saturation.cpp index d5c295eb..cad9c08d 100644 --- a/src/effects/Saturation.cpp +++ b/src/effects/Saturation.cpp @@ -33,13 +33,14 @@ using namespace openshot; /// Blank constructor, useful when using Json to load the effect properties -Saturation::Saturation() : saturation(1.0) { +Saturation::Saturation() : saturation(1.0), saturation_R(1.0), saturation_G(1.0), saturation_B(1.0) { // Init effect properties init_effect_details(); } // Default constructor -Saturation::Saturation(Keyframe new_saturation) : saturation(new_saturation) +Saturation::Saturation(Keyframe saturation, Keyframe saturation_R, Keyframe saturation_G, Keyframe saturation_B) : + saturation(saturation), saturation_R(saturation_R), saturation_G(saturation_G), saturation_B(saturation_B) { // Init effect properties init_effect_details(); @@ -73,6 +74,9 @@ std::shared_ptr Saturation::GetFrame(std::shared_ptr frame, int64_ // Get keyframe values for this frame float saturation_value = saturation.GetValue(frame_number); + float saturation_value_R = saturation_R.GetValue(frame_number); + float saturation_value_G = saturation_G.GetValue(frame_number); + float saturation_value_B = saturation_B.GetValue(frame_number); // Constants used for color saturation formula const double pR = .299; @@ -90,15 +94,66 @@ std::shared_ptr Saturation::GetFrame(std::shared_ptr frame, int64_ int G = pixels[pixel * 4 + 1]; int B = pixels[pixel * 4 + 2]; + /* + * Common saturation adjustment + */ + // Calculate the saturation multiplier double p = sqrt( (R * R * pR) + - (G * G * pG) + - (B * B * pB) ); + (G * G * pG) + + (B * B * pB) ); - // Apply adjusted and constrained saturation - pixels[pixel * 4] = constrain(p + (R - p) * saturation_value); - pixels[pixel * 4 + 1] = constrain(p + (G - p) * saturation_value); - pixels[pixel * 4 + 2] = constrain(p + (B - p) * saturation_value); + // Adjust the saturation + R = p + (R - p) * saturation_value; + G = p + (G - p) * saturation_value; + B = p + (B - p) * saturation_value; + + // Constrain the value from 0 to 255 + R = constrain(R); + G = constrain(G); + B = constrain(B); + + /* + * Color-separated saturation adjustment + * + * Splitting each of the three subpixels (R, G and B) into three distincs sub-subpixels (R, G and B in turn) + * which in their optical sum reproduce the original subpixel's color OR produce white light in the brightness + * of the original subpixel (dependening on the color channel's slider value). + */ + + // Compute the brightness ("saturation multiplier") of the replaced subpixels + // Actually mathematical no-ops mostly, verbosity is kept just for clarification + const double p_r = sqrt(R * R * pR); + const double p_g = sqrt(G * G * pG); + const double p_b = sqrt(B * B * pB); + + // Adjust the saturation + const int Rr = p_r + (R - p_r) * saturation_value_R; + const int Gr = p_r + (0 - p_r) * saturation_value_R; + const int Br = p_r + (0 - p_r) * saturation_value_R; + + const int Rg = p_g + (0 - p_g) * saturation_value_G; + const int Gg = p_g + (G - p_g) * saturation_value_G; + const int Bg = p_g + (0 - p_g) * saturation_value_G; + + const int Rb = p_b + (0 - p_b) * saturation_value_B; + const int Gb = p_b + (0 - p_b) * saturation_value_B; + const int Bb = p_b + (B - p_b) * saturation_value_B; + + // Recombine brightness of sub-subpixels (Rx, Gx and Bx) into sub-pixels (R, G and B) again + R = Rr + Rg + Rb; + G = Gr + Gg + Gb; + B = Br + Bg + Bb; + + // Constrain the value from 0 to 255 + R = constrain(R); + G = constrain(G); + B = constrain(B); + + // Set all pixels to new value + pixels[pixel * 4] = R; + pixels[pixel * 4 + 1] = G; + pixels[pixel * 4 + 2] = B; } // return the modified frame @@ -119,6 +174,9 @@ Json::Value Saturation::JsonValue() const { Json::Value root = EffectBase::JsonValue(); // get parent properties root["type"] = info.class_name; root["saturation"] = saturation.JsonValue(); + root["saturation_R"] = saturation_R.JsonValue(); + root["saturation_G"] = saturation_G.JsonValue(); + root["saturation_B"] = saturation_B.JsonValue(); // return JsonValue return root; @@ -150,6 +208,12 @@ void Saturation::SetJsonValue(const Json::Value root) { // Set data from Json (if key is found) if (!root["saturation"].isNull()) saturation.SetJsonValue(root["saturation"]); + if (!root["saturation_R"].isNull()) + saturation_R.SetJsonValue(root["saturation_R"]); + if (!root["saturation_G"].isNull()) + saturation_G.SetJsonValue(root["saturation_G"]); + if (!root["saturation_B"].isNull()) + saturation_B.SetJsonValue(root["saturation_B"]); } // Get all properties for a specific frame @@ -166,6 +230,9 @@ std::string Saturation::PropertiesJSON(int64_t requested_frame) const { // Keyframes root["saturation"] = add_property_json("Saturation", saturation.GetValue(requested_frame), "float", "", &saturation, 0.0, 4.0, false, requested_frame); + root["saturation_R"] = add_property_json("Saturation (Red)", saturation_R.GetValue(requested_frame), "float", "", &saturation_R, 0.0, 4.0, false, requested_frame); + root["saturation_G"] = add_property_json("Saturation (Green)", saturation_G.GetValue(requested_frame), "float", "", &saturation_G, 0.0, 4.0, false, requested_frame); + root["saturation_B"] = add_property_json("Saturation (Blue)", saturation_B.GetValue(requested_frame), "float", "", &saturation_B, 0.0, 4.0, false, requested_frame); // Return formatted string return root.toStyledString();