diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a9e5d4cf..d11cc72b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -66,6 +66,7 @@ mac-builder: - cmake -DCMAKE_EXE_LINKER_FLAGS="-stdlib=libc++" -DCMAKE_SHARED_LINKER_FLAGS="-stdlib=libc++" -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON -D"CMAKE_INSTALL_PREFIX:PATH=$CI_PROJECT_DIR/build/install-x64" -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER=clang -D"CMAKE_BUILD_TYPE:STRING=Release" -D"CMAKE_OSX_SYSROOT=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk" -D"CMAKE_OSX_DEPLOYMENT_TARGET=10.12" -DCMAKE_PREFIX_PATH=/usr/local/qt5.15.X/qt5.15/5.15.0/clang_64/ -D"CMAKE_INSTALL_RPATH_USE_LINK_PATH=1" -D"ENABLE_RUBY=0" ../ - make -j 9 - make install + - make test - PROJECT_VERSION=$(grep -E '^set\(PROJECT_VERSION_FULL "(.*)' ../CMakeLists.txt | awk '{print $2}' | tr -d '")') - PROJECT_SO=$(grep -E '^set\(PROJECT_SO_VERSION (.*)' ../CMakeLists.txt | awk '{print $2}' | tr -d ')') - 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\nCI_PIPELINE_ID:$CI_PIPELINE_ID\nVERSION:$PROJECT_VERSION\nSO:$PROJECT_SO" > "install-x64/share/$CI_PROJECT_NAME.env" diff --git a/bindings/java/openshot.i b/bindings/java/openshot.i index ee339e1a..7aaf7d1e 100644 --- a/bindings/java/openshot.i +++ b/bindings/java/openshot.i @@ -91,9 +91,13 @@ #include "Settings.h" #include "TimelineBase.h" #include "Timeline.h" +#include "Qt/VideoCacheThread.h" #include "ZmqLogger.h" %} +// Prevent SWIG from ever generating a wrapper for juce::Thread’s constructor (or run()) +%ignore juce::Thread::Thread; + #ifdef USE_IMAGEMAGICK %{ #include "ImageReader.h" @@ -151,6 +155,7 @@ %include "RendererBase.h" %include "Settings.h" %include "TimelineBase.h" +%include "Qt/VideoCacheThread.h" %include "Timeline.h" %include "ZmqLogger.h" diff --git a/bindings/python/openshot.i b/bindings/python/openshot.i index 29b9c8ee..612bfb11 100644 --- a/bindings/python/openshot.i +++ b/bindings/python/openshot.i @@ -96,10 +96,14 @@ #include "Settings.h" #include "TimelineBase.h" #include "Timeline.h" +#include "Qt/VideoCacheThread.h" #include "ZmqLogger.h" %} +// Prevent SWIG from ever generating a wrapper for juce::Thread’s constructor (or run()) +%ignore juce::Thread::Thread; + #ifdef USE_IMAGEMAGICK %{ #include "ImageReader.h" @@ -317,6 +321,7 @@ %include "RendererBase.h" %include "Settings.h" %include "TimelineBase.h" +%include "Qt/VideoCacheThread.h" %include "Timeline.h" %include "ZmqLogger.h" diff --git a/bindings/ruby/openshot.i b/bindings/ruby/openshot.i index be6fa19b..655241be 100644 --- a/bindings/ruby/openshot.i +++ b/bindings/ruby/openshot.i @@ -99,6 +99,7 @@ #include "Settings.h" #include "TimelineBase.h" #include "Timeline.h" +#include "Qt/VideoCacheThread.h" #include "ZmqLogger.h" /* Move FFmpeg's RSHIFT to FF_RSHIFT, if present */ @@ -112,6 +113,9 @@ #endif %} +// Prevent SWIG from ever generating a wrapper for juce::Thread’s constructor (or run()) +%ignore juce::Thread::Thread; + #ifdef USE_IMAGEMAGICK %{ #include "ImageReader.h" @@ -190,6 +194,7 @@ %include "RendererBase.h" %include "Settings.h" %include "TimelineBase.h" +%include "Qt/VideoCacheThread.h" %include "Timeline.h" %include "ZmqLogger.h" diff --git a/examples/Example.cpp b/examples/Example.cpp index 4c7b96c2..2067fb56 100644 --- a/examples/Example.cpp +++ b/examples/Example.cpp @@ -1,68 +1,95 @@ /** * @file - * @brief Source file for Example Executable (example app for libopenshot) + * @brief Example application showing how to attach VideoCacheThread to an FFmpegReader * @author Jonathan Thomas * * @ref License */ -// Copyright (c) 2008-2019 OpenShot Studios, LLC +// Copyright (c) 2008-2025 OpenShot Studios, LLC // // SPDX-License-Identifier: LGPL-3.0-or-later -#include +#include #include #include -#include -#include "Clip.h" #include "Frame.h" #include "FFmpegReader.h" +#include "FFmpegWriter.h" #include "Timeline.h" -#include "Profiles.h" +#include "Qt/VideoCacheThread.h" // <— your new header using namespace openshot; - int main(int argc, char* argv[]) { - QString filename = "/home/jonathan/test-crash.osp"; - //QString filename = "/home/jonathan/Downloads/drive-download-20221123T185423Z-001/project-3363/project-3363.osp"; - //QString filename = "/home/jonathan/Downloads/drive-download-20221123T185423Z-001/project-3372/project-3372.osp"; - //QString filename = "/home/jonathan/Downloads/drive-download-20221123T185423Z-001/project-3512/project-3512.osp"; - QString project_json = ""; - QFile file(filename); - if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - std::cout << "File error!" << std::endl; - exit(1); - } else { - while (!file.atEnd()) { - QByteArray line = file.readLine(); - project_json += line; - } + + + // 1) Open the FFmpegReader as usual + const char* input_path = "/home/jonathan/Downloads/openshot-testing/sintel_trailer-720p.mp4"; + FFmpegReader reader(input_path); + reader.Open(); + + const int64_t total_frames = reader.info.video_length; + std::cout << "Total frames: " << total_frames << "\n"; + + + + Timeline timeline(reader.info.width, reader.info.height, reader.info.fps, reader.info.sample_rate, reader.info.channels, reader.info.channel_layout); + Clip c1(&reader); + timeline.AddClip(&c1); + timeline.Open(); + timeline.DisplayInfo(); + + + // 2) Construct a VideoCacheThread around 'reader' and start its background loop + // (VideoCacheThread inherits juce::Thread) + std::shared_ptr cache = std::make_shared(); + cache->Reader(&timeline); // attaches the FFmpegReader and internally calls Play() + cache->StartThread(); // juce::Thread method, begins run() + + // 3) Set up the writer exactly as before + FFmpegWriter writer("/home/jonathan/Downloads/performance‐cachetest.mp4"); + writer.SetAudioOptions("aac", 48000, 192000); + writer.SetVideoOptions("libx264", 1280, 720, Fraction(30, 1), 5000000); + writer.Open(); + + // 4) Forward pass: for each frame 1…N, tell the cache thread to seek to that frame, + // then immediately call cache->GetFrame(frame), which will block only if that frame + // hasn’t been decoded into the cache yet. + auto t0 = std::chrono::high_resolution_clock::now(); + cache->setSpeed(1); + for (int64_t f = 1; f <= total_frames; ++f) { + float pct = (float(f) / total_frames) * 100.0f; + std::cout << "Forward: requesting frame " << f << " (" << pct << "%)\n"; + + cache->Seek(f); // signal “I need frame f now (and please prefetch f+1, f+2, …)” + std::shared_ptr framePtr = timeline.GetFrame(f); + writer.WriteFrame(framePtr); } + auto t1 = std::chrono::high_resolution_clock::now(); + auto forward_ms = std::chrono::duration_cast(t1 - t0).count(); - // Open timeline reader - std::cout << "Project JSON length: " << project_json.length() << std::endl; - Timeline r(1280, 720, openshot::Fraction(30, 1), 44100, 2, openshot::LAYOUT_STEREO); - r.SetJson(project_json.toStdString()); - r.DisplayInfo(); - r.Open(); + // 5) Backward pass: same idea in reverse + auto t2 = std::chrono::high_resolution_clock::now(); + cache->setSpeed(-1); + for (int64_t f = total_frames; f >= 1; --f) { + float pct = (float(total_frames - f + 1) / total_frames) * 100.0f; + std::cout << "Backward: requesting frame " << f << " (" << pct << "%)\n"; - // Get max frame - int64_t max_frame = r.GetMaxFrame(); - std::cout << "max_frame: " << max_frame << ", r.info.video_length: " << r.info.video_length << std::endl; - - for (long int frame = 1; frame <= max_frame; frame++) - { - float percent = (float(frame) / max_frame) * 100.0; - std::cout << "Requesting Frame #: " << frame << " (" << percent << "%)" << std::endl; - std::shared_ptr f = r.GetFrame(frame); - - // Preview frame image - if (frame % 1 == 0) { - f->Save("preview.jpg", 1.0, "jpg", 100); - } + cache->Seek(f); + std::shared_ptr framePtr = timeline.GetFrame(f); + writer.WriteFrame(framePtr); } - r.Close(); + auto t3 = std::chrono::high_resolution_clock::now(); + auto backward_ms = std::chrono::duration_cast(t3 - t2).count(); - exit(0); + std::cout << "\nForward pass elapsed: " << forward_ms << " ms\n"; + std::cout << "Backward pass elapsed: " << backward_ms << " ms\n"; + + // 6) Shut down the cache thread, close everything + cache->StopThread(10000); // politely tells run() to exit, waits up to 10s + reader.Close(); + writer.Close(); + timeline.Close(); + return 0; } diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index c42cc386..d66749bb 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -74,8 +74,8 @@ FFmpegReader::FFmpegReader(const std::string &path, bool inspect_reader) seek_audio_frame_found(0), seek_video_frame_found(0),is_duration_known(false), largest_frame_processed(0), current_video_frame(0), packet(NULL), max_concurrent_frames(OPEN_MP_NUM_PROCESSORS), audio_pts(0), video_pts(0), pFormatCtx(NULL), videoStream(-1), audioStream(-1), pCodecCtx(NULL), aCodecCtx(NULL), - pStream(NULL), aStream(NULL), pFrame(NULL), previous_packet_location{-1,0}, - hold_packet(false) { + pStream(NULL), aStream(NULL), pFrame(NULL), previous_packet_location{-1,0}, + hold_packet(false) { // Initialize FFMpeg, and register all formats and codecs AV_REGISTER_ALL @@ -278,7 +278,7 @@ void FFmpegReader::Open() { retry_decode_open = 0; // Set number of threads equal to number of processors (not to exceed 16) - pCodecCtx->thread_count = std::min(FF_NUM_PROCESSORS, 16); + pCodecCtx->thread_count = std::min(FF_VIDEO_NUM_PROCESSORS, 16); if (pCodec == NULL) { throw InvalidCodec("A valid video codec could not be found for this file.", path); @@ -524,8 +524,8 @@ void FFmpegReader::Open() { const AVCodec *aCodec = avcodec_find_decoder(codecId); aCodecCtx = AV_GET_CODEC_CONTEXT(aStream, aCodec); - // Set number of threads equal to number of processors (not to exceed 16) - aCodecCtx->thread_count = std::min(FF_NUM_PROCESSORS, 16); + // Audio encoding does not typically use more than 2 threads (most codecs use 1 thread) + aCodecCtx->thread_count = std::min(FF_AUDIO_NUM_PROCESSORS, 2); if (aCodec == NULL) { throw InvalidCodec("A valid audio codec could not be found for this file.", path); @@ -678,6 +678,13 @@ void FFmpegReader::Close() { } } #endif // USE_HW_ACCEL + if (img_convert_ctx) { + sws_freeContext(img_convert_ctx); + img_convert_ctx = nullptr; + } + if (pFrameRGB_cached) { + AV_FREE_FRAME(&pFrameRGB_cached); + } } // Close the audio codec @@ -686,6 +693,11 @@ void FFmpegReader::Close() { avcodec_flush_buffers(aCodecCtx); } AV_FREE_CONTEXT(aCodecCtx); + if (avr_ctx) { + SWR_CLOSE(avr_ctx); + SWR_FREE(&avr_ctx); + avr_ctx = nullptr; + } } // Clear final cache @@ -1469,15 +1481,17 @@ void FFmpegReader::ProcessVideoPacket(int64_t requested_frame) { int width = info.width; int64_t video_length = info.video_length; - // Create variables for a RGB Frame (since most videos are not in RGB, we must convert it) - AVFrame *pFrameRGB = nullptr; + // Create or reuse a RGB Frame (since most videos are not in RGB, we must convert it) + AVFrame *pFrameRGB = pFrameRGB_cached; + if (!pFrameRGB) { + pFrameRGB = AV_ALLOCATE_FRAME(); + if (pFrameRGB == nullptr) + throw OutOfMemory("Failed to allocate frame buffer", path); + pFrameRGB_cached = pFrameRGB; + } + AV_RESET_FRAME(pFrameRGB); uint8_t *buffer = nullptr; - // Allocate an AVFrame structure - pFrameRGB = AV_ALLOCATE_FRAME(); - if (pFrameRGB == nullptr) - throw OutOfMemory("Failed to allocate frame buffer", path); - // Determine the max size of this source image (based on the timeline's size, the scaling mode, // and the scaling keyframes). This is a performance improvement, to keep the images as small as possible, // without losing quality. NOTE: We cannot go smaller than the timeline itself, or the add_layer timeline @@ -1554,8 +1568,12 @@ void FFmpegReader::ProcessVideoPacket(int64_t requested_frame) { // Determine required buffer size and allocate buffer const int bytes_per_pixel = 4; - int buffer_size = (width * height * bytes_per_pixel) + 128; - buffer = new unsigned char[buffer_size](); + int raw_buffer_size = (width * height * bytes_per_pixel) + 128; + + // Aligned memory allocation (for speed) + constexpr size_t ALIGNMENT = 32; // AVX2 + int buffer_size = ((raw_buffer_size + ALIGNMENT - 1) / ALIGNMENT) * ALIGNMENT; + buffer = (unsigned char*) aligned_malloc(buffer_size, ALIGNMENT); // Copy picture data from one AVFrame (or AVPicture) to another one. AV_COPY_PICTURE_DATA(pFrameRGB, buffer, PIX_FMT_RGBA, width, height); @@ -1564,8 +1582,9 @@ void FFmpegReader::ProcessVideoPacket(int64_t requested_frame) { if (openshot::Settings::Instance()->HIGH_QUALITY_SCALING) { scale_mode = SWS_BICUBIC; } - SwsContext *img_convert_ctx = sws_getContext(info.width, info.height, AV_GET_CODEC_PIXEL_FORMAT(pStream, pCodecCtx), width, - height, PIX_FMT_RGBA, scale_mode, NULL, NULL, NULL); + img_convert_ctx = sws_getCachedContext(img_convert_ctx, info.width, info.height, AV_GET_CODEC_PIXEL_FORMAT(pStream, pCodecCtx), width, height, PIX_FMT_RGBA, scale_mode, NULL, NULL, NULL); + if (!img_convert_ctx) + throw OutOfMemory("Failed to initialize sws context", path); // Resize / Convert to RGB sws_scale(img_convert_ctx, pFrame->data, pFrame->linesize, 0, @@ -1590,11 +1609,10 @@ void FFmpegReader::ProcessVideoPacket(int64_t requested_frame) { last_video_frame = f; // Free the RGB image - AV_FREE_FRAME(&pFrameRGB); + AV_RESET_FRAME(pFrameRGB); - // Remove frame and packet - RemoveAVFrame(pFrame); - sws_freeContext(img_convert_ctx); + // Remove frame and packet + RemoveAVFrame(pFrame); // Get video PTS in seconds video_pts_seconds = (double(video_pts) * info.video_timebase.ToDouble()) + pts_offset_seconds; @@ -1738,10 +1756,10 @@ void FFmpegReader::ProcessAudioPacket(int64_t requested_frame) { audio_converted->nb_samples = audio_frame->nb_samples; av_samples_alloc(audio_converted->data, audio_converted->linesize, info.channels, audio_frame->nb_samples, AV_SAMPLE_FMT_FLTP, 0); - SWRCONTEXT *avr = NULL; - - // setup resample context - avr = SWR_ALLOC(); + SWRCONTEXT *avr = avr_ctx; + // setup resample context if needed + if (!avr) { + avr = SWR_ALLOC(); #if HAVE_CH_LAYOUT av_opt_set_chlayout(avr, "in_chlayout", &AV_GET_CODEC_ATTRIBUTES(aStream, aCodecCtx)->ch_layout, 0); av_opt_set_chlayout(avr, "out_chlayout", &AV_GET_CODEC_ATTRIBUTES(aStream, aCodecCtx)->ch_layout, 0); @@ -1756,6 +1774,8 @@ void FFmpegReader::ProcessAudioPacket(int64_t requested_frame) { av_opt_set_int(avr, "in_sample_rate", info.sample_rate, 0); av_opt_set_int(avr, "out_sample_rate", info.sample_rate, 0); SWR_INIT(avr); + avr_ctx = avr; + } // Convert audio samples int nb_samples = SWR_CONVERT(avr, // audio resample context @@ -1766,10 +1786,6 @@ void FFmpegReader::ProcessAudioPacket(int64_t requested_frame) { audio_frame->linesize[0], // input plane size, in bytes (0 if unknown) audio_frame->nb_samples); // number of input samples to convert - // Deallocate resample buffer - SWR_CLOSE(avr); - SWR_FREE(&avr); - avr = NULL; int64_t starting_frame_number = -1; for (int channel_filter = 0; channel_filter < info.channels; channel_filter++) { diff --git a/src/FFmpegReader.h b/src/FFmpegReader.h index 06727d62..295cdcf3 100644 --- a/src/FFmpegReader.h +++ b/src/FFmpegReader.h @@ -31,6 +31,7 @@ #include "Clip.h" #include "OpenMPUtilities.h" #include "Settings.h" +#include namespace openshot { @@ -148,6 +149,11 @@ namespace openshot { int64_t NO_PTS_OFFSET; PacketStatus packet_status; + // Cached conversion contexts and frames for performance + SwsContext *img_convert_ctx = nullptr; ///< Cached video scaler context + SWRCONTEXT *avr_ctx = nullptr; ///< Cached audio resample context + AVFrame *pFrameRGB_cached = nullptr; ///< Temporary frame used for video conversion + int hw_de_supported = 0; // Is set by FFmpegReader #if USE_HW_ACCEL AVPixelFormat hw_de_av_pix_fmt = AV_PIX_FMT_NONE; diff --git a/src/FFmpegUtilities.h b/src/FFmpegUtilities.h index f30f7fdc..6e27dc6c 100644 --- a/src/FFmpegUtilities.h +++ b/src/FFmpegUtilities.h @@ -298,5 +298,23 @@ inline static bool ffmpeg_has_alpha(PixelFormat pix_fmt) { #define AV_COPY_PARAMS_FROM_CONTEXT(av_stream, av_codec) #endif +// Aligned memory allocation helpers (cross-platform) +#if defined(_WIN32) +#include +#endif + +inline static void* aligned_malloc(size_t size, size_t alignment = 32) +{ +#if defined(_WIN32) + return _aligned_malloc(size, alignment); +#elif defined(__APPLE__) || defined(__linux__) + void* ptr = nullptr; + if (posix_memalign(&ptr, alignment, size) != 0) + return nullptr; + return ptr; +#else +#error "aligned_malloc not implemented on this platform" +#endif +} #endif // OPENSHOT_FFMPEG_UTILITIES_H diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index 340f3657..79cbc21c 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -9,7 +9,7 @@ * @ref License */ -// Copyright (c) 2008-2024 OpenShot Studios, LLC, Fabrice Bellard +// Copyright (c) 2008-2025 OpenShot Studios, LLC, Fabrice Bellard // // SPDX-License-Identifier: LGPL-3.0-or-later @@ -75,8 +75,8 @@ static int set_hwframe_ctx(AVCodecContext *ctx, AVBufferRef *hw_device_ctx, int6 FFmpegWriter::FFmpegWriter(const std::string& path) : path(path), 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), num_of_rescalers(1), - rescaler_position(0), video_codec_ctx(NULL), audio_codec_ctx(NULL), is_writing(false), video_timestamp(0), audio_timestamp(0), + initial_audio_input_frame_size(0), img_convert_ctx(NULL), + video_codec_ctx(NULL), audio_codec_ctx(NULL), is_writing(false), video_timestamp(0), audio_timestamp(0), original_sample_rate(0), original_channels(0), avr(NULL), avr_planar(NULL), is_open(false), prepare_streams(false), write_header(false), write_trailer(false), audio_encoder_buffer_size(0), audio_encoder_buffer(NULL) { @@ -414,8 +414,6 @@ void FFmpegWriter::SetOption(StreamType stream, std::string name, std::string va else if (name == "cqp") { // 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 USE_HW_ACCEL if (hw_en_on) { av_opt_set_int(c->priv_data, "qp", std::min(std::stoi(value),63), 0); // 0-63 @@ -464,8 +462,6 @@ void FFmpegWriter::SetOption(StreamType stream, std::string name, std::string va } } 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 USE_HW_ACCEL if (hw_en_on) { double mbs = 15000000.0; @@ -539,8 +535,6 @@ void FFmpegWriter::SetOption(StreamType stream, std::string name, std::string va } } 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) { @@ -605,10 +599,7 @@ bool FFmpegWriter::IsValidCodec(std::string codec_name) { AV_REGISTER_ALL // Find the codec (if any) - if (avcodec_find_encoder_by_name(codec_name.c_str()) == NULL) - return false; - else - return true; + return avcodec_find_encoder_by_name(codec_name.c_str()) != NULL; } // Prepare & initialize streams and open codecs @@ -972,9 +963,9 @@ void FFmpegWriter::Close() { if (audio_st) close_audio(oc, audio_st); - // Deallocate image scalers - if (image_rescalers.size() > 0) - RemoveScalers(); + // Remove single software scaler + if (img_convert_ctx) + sws_freeContext(img_convert_ctx); if (!(oc->oformat->flags & AVFMT_NOFILE)) { /* close the output file */ @@ -1025,7 +1016,7 @@ AVStream *FFmpegWriter::add_audio_stream() { // Create a new audio stream AVStream* st = avformat_new_stream(oc, codec); if (!st) - throw OutOfMemory("Could not allocate memory for the video stream.", path); + throw OutOfMemory("Could not allocate memory for the audio stream.", path); // Allocate a new codec context for the stream ALLOC_CODEC_CTX(audio_codec_ctx, codec, st) @@ -1058,7 +1049,7 @@ AVStream *FFmpegWriter::add_audio_stream() { // Set sample rate c->sample_rate = info.sample_rate; -uint64_t channel_layout = info.channel_layout; + uint64_t channel_layout = info.channel_layout; #if HAVE_CH_LAYOUT // Set a valid number of channels (or throw error) AVChannelLayout ch_layout; @@ -1117,9 +1108,9 @@ uint64_t channel_layout = info.channel_layout; AV_COPY_PARAMS_FROM_CONTEXT(st, c); -int nb_channels; -const char* nb_channels_label; -const char* channel_layout_label; + int nb_channels; + const char* nb_channels_label; + const char* channel_layout_label; #if HAVE_CH_LAYOUT nb_channels = c->ch_layout.nb_channels; @@ -1343,8 +1334,8 @@ void FFmpegWriter::open_audio(AVFormatContext *oc, AVStream *st) { const AVCodec *codec; AV_GET_CODEC_FROM_STREAM(st, audio_codec_ctx) - // Set number of threads equal to number of processors (not to exceed 16) - audio_codec_ctx->thread_count = std::min(FF_NUM_PROCESSORS, 16); + // Audio encoding does not typically use more than 2 threads (most codecs use 1 thread) + audio_codec_ctx->thread_count = std::min(FF_AUDIO_NUM_PROCESSORS, 2); // Find the audio encoder codec = avcodec_find_encoder_by_name(info.acodec.c_str()); @@ -1418,8 +1409,8 @@ void FFmpegWriter::open_video(AVFormatContext *oc, AVStream *st) { const AVCodec *codec; AV_GET_CODEC_FROM_STREAM(st, video_codec_ctx) - // Set number of threads equal to number of processors (not to exceed 16) - video_codec_ctx->thread_count = std::min(FF_NUM_PROCESSORS, 16); + // Set number of threads equal to number of processors (not to exceed 16, FFmpeg doesn't recommend more than 16) + video_codec_ctx->thread_count = std::min(FF_VIDEO_NUM_PROCESSORS, 16); #if USE_HW_ACCEL if (hw_en_on && hw_en_supported) { @@ -1559,7 +1550,7 @@ void FFmpegWriter::open_video(AVFormatContext *oc, AVStream *st) { av_dict_free(&opts); // Add video metadata (if any) - for (std::map::iterator iter = info.metadata.begin(); iter != info.metadata.end(); ++iter) { + for (auto iter = info.metadata.begin(); iter != info.metadata.end(); ++iter) { av_dict_set(&st->metadata, iter->first.c_str(), iter->second.c_str(), 0); } @@ -2059,69 +2050,128 @@ AVFrame *FFmpegWriter::allocate_avframe(PixelFormat pix_fmt, int width, int heig // process video frame void FFmpegWriter::process_video_packet(std::shared_ptr frame) { - // Determine the height & width of the source image - int source_image_width = frame->GetWidth(); - int source_image_height = frame->GetHeight(); + // Source dimensions (RGBA) + int src_w = frame->GetWidth(); + int src_h = frame->GetHeight(); - // Do nothing if size is 1x1 (i.e. no image in this frame) - if (source_image_height == 1 && source_image_width == 1) + // Skip empty frames (1×1) + if (src_w == 1 && src_h == 1) return; - // Init rescalers (if not initialized yet) - if (image_rescalers.size() == 0) - InitScalers(source_image_width, source_image_height); + // Point persistent_src_frame->data to RGBA pixels + const uchar* pixels = frame->GetPixels(); + if (!persistent_src_frame) { + persistent_src_frame = av_frame_alloc(); + if (!persistent_src_frame) + throw OutOfMemory("Could not allocate persistent_src_frame", path); + persistent_src_frame->format = AV_PIX_FMT_RGBA; + persistent_src_frame->width = src_w; + persistent_src_frame->height = src_h; + persistent_src_frame->linesize[0] = src_w * 4; + } + persistent_src_frame->data[0] = const_cast( + reinterpret_cast(pixels) + ); - // Get a unique rescaler (for this thread) - SwsContext *scaler = image_rescalers[rescaler_position]; - rescaler_position++; - if (rescaler_position == num_of_rescalers) - rescaler_position = 0; + // Prepare persistent_dst_frame + buffer on first use + if (!persistent_dst_frame) { + persistent_dst_frame = av_frame_alloc(); + if (!persistent_dst_frame) + throw OutOfMemory("Could not allocate persistent_dst_frame", path); - // Allocate an RGB frame & final output frame - int bytes_source = 0; - int bytes_final = 0; - AVFrame *frame_source = NULL; - const uchar *pixels = NULL; - - // Get a list of pixels from source image - pixels = frame->GetPixels(); - - // Init AVFrame for source image & final (converted image) - frame_source = allocate_avframe(PIX_FMT_RGBA, source_image_width, source_image_height, &bytes_source, (uint8_t *) pixels); -#if IS_FFMPEG_3_2 - AVFrame *frame_final; + // Decide destination pixel format: NV12 if HW accel is on, else encoder’s pix_fmt + AVPixelFormat dst_fmt = video_codec_ctx->pix_fmt; #if USE_HW_ACCEL - if (hw_en_on && hw_en_supported) { - frame_final = allocate_avframe(AV_PIX_FMT_NV12, info.width, info.height, &bytes_final, NULL); - } else -#endif // USE_HW_ACCEL - { - frame_final = allocate_avframe( - (AVPixelFormat)(video_st->codecpar->format), - info.width, info.height, &bytes_final, NULL + if (hw_en_on && hw_en_supported) { + dst_fmt = AV_PIX_FMT_NV12; + } +#endif + persistent_dst_frame->format = dst_fmt; + persistent_dst_frame->width = info.width; + persistent_dst_frame->height = info.height; + + persistent_dst_size = av_image_get_buffer_size( + dst_fmt, info.width, info.height, 1 + ); + if (persistent_dst_size < 0) + throw ErrorEncodingVideo("Invalid destination image size", -1); + + persistent_dst_buffer = static_cast( + av_malloc(persistent_dst_size) + ); + if (!persistent_dst_buffer) + throw OutOfMemory("Could not allocate persistent_dst_buffer", path); + + av_image_fill_arrays( + persistent_dst_frame->data, + persistent_dst_frame->linesize, + persistent_dst_buffer, + dst_fmt, + info.width, + info.height, + 1 ); } -#else - AVFrame *frame_final = allocate_avframe(video_codec_ctx->pix_fmt, info.width, info.height, &bytes_final, NULL); -#endif // IS_FFMPEG_3_2 - // Fill with data - AV_COPY_PICTURE_DATA(frame_source, (uint8_t *) pixels, PIX_FMT_RGBA, source_image_width, source_image_height); - ZmqLogger::Instance()->AppendDebugMethod( - "FFmpegWriter::process_video_packet", - "frame->number", frame->number, - "bytes_source", bytes_source, - "bytes_final", bytes_final); + // Initialize SwsContext (RGBA → dst_fmt) on first use + if (!img_convert_ctx) { + int flags = SWS_FAST_BILINEAR; + if (openshot::Settings::Instance()->HIGH_QUALITY_SCALING) { + flags = SWS_BICUBIC; + } + AVPixelFormat dst_fmt = video_codec_ctx->pix_fmt; +#if USE_HW_ACCEL + if (hw_en_on && hw_en_supported) { + dst_fmt = AV_PIX_FMT_NV12; + } +#endif + img_convert_ctx = sws_getContext( + src_w, src_h, AV_PIX_FMT_RGBA, + info.width, info.height, dst_fmt, + flags, NULL, NULL, NULL + ); + if (!img_convert_ctx) + throw ErrorEncodingVideo("Could not initialize sws context", -1); + } - // Resize & convert pixel format - sws_scale(scaler, frame_source->data, frame_source->linesize, 0, - source_image_height, frame_final->data, frame_final->linesize); + // Scale RGBA → dst_fmt into persistent_dst_buffer + sws_scale( + img_convert_ctx, + persistent_src_frame->data, + persistent_src_frame->linesize, + 0, src_h, + persistent_dst_frame->data, + persistent_dst_frame->linesize + ); - // Add resized AVFrame to av_frames map - add_avframe(frame, frame_final); + // Allocate a new AVFrame + buffer, then copy scaled data into it + int bytes_final = 0; + AVPixelFormat dst_fmt = video_codec_ctx->pix_fmt; +#if USE_HW_ACCEL + if (hw_en_on && hw_en_supported) { + dst_fmt = AV_PIX_FMT_NV12; + } +#endif - // Deallocate memory - AV_FREE_FRAME(&frame_source); + AVFrame* new_frame = allocate_avframe( + dst_fmt, + info.width, + info.height, + &bytes_final, + nullptr + ); + if (!new_frame) + throw OutOfMemory("Could not allocate new_frame via allocate_avframe", path); + + // Copy persistent_dst_buffer → new_frame buffer + memcpy( + new_frame->data[0], + persistent_dst_buffer, + static_cast(bytes_final) + ); + + // Queue the deep‐copied frame for encoding + add_avframe(frame, new_frame); } // write video frame @@ -2307,55 +2357,14 @@ void FFmpegWriter::OutputStreamInfo() { av_dump_format(oc, 0, path.c_str(), 1); } -// Init a collection of software rescalers (thread safe) -void FFmpegWriter::InitScalers(int source_width, int source_height) { - int scale_mode = SWS_FAST_BILINEAR; - if (openshot::Settings::Instance()->HIGH_QUALITY_SCALING) { - scale_mode = SWS_BICUBIC; - } - - // Init software rescalers vector (many of them, one for each thread) - for (int x = 0; x < num_of_rescalers; x++) { - // Init the software scaler from FFMpeg -#if USE_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); - } else -#endif // USE_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); - } - - // Add rescaler to vector - image_rescalers.push_back(img_convert_ctx); - } -} - // Set audio resample options void FFmpegWriter::ResampleAudio(int sample_rate, int channels) { original_sample_rate = sample_rate; original_channels = channels; } -// Remove & deallocate all software scalers -void FFmpegWriter::RemoveScalers() { - // Close all rescalers - for (int x = 0; x < num_of_rescalers; x++) - sws_freeContext(image_rescalers[x]); - - // Clear vector - image_rescalers.clear(); -} - // In FFmpegWriter.cpp -void FFmpegWriter::AddSphericalMetadata(const std::string& projection, - float yaw_deg, - float pitch_deg, - float roll_deg) -{ +void FFmpegWriter::AddSphericalMetadata(const std::string& projection, float yaw_deg, float pitch_deg, float roll_deg) { if (!oc) return; if (!info.has_video || !video_st) return; @@ -2371,10 +2380,7 @@ void FFmpegWriter::AddSphericalMetadata(const std::string& projection, // Allocate the side‐data structure size_t sd_size = 0; AVSphericalMapping* map = av_spherical_alloc(&sd_size); - if (!map) { - // Allocation failed; skip metadata - return; - } + if (!map) return; // Populate it map->projection = static_cast(proj); @@ -2383,14 +2389,6 @@ void FFmpegWriter::AddSphericalMetadata(const std::string& projection, map->pitch = static_cast(pitch_deg * (1 << 16)); map->roll = static_cast(roll_deg * (1 << 16)); - // Attach to the video stream so movenc will emit an sv3d atom - av_stream_add_side_data( - video_st, - AV_PKT_DATA_SPHERICAL, - reinterpret_cast(map), - sd_size - ); -#else - // FFmpeg build too old: spherical side-data not supported + av_stream_add_side_data(video_st, AV_PKT_DATA_SPHERICAL, reinterpret_cast(map), sd_size); #endif } diff --git a/src/FFmpegWriter.h b/src/FFmpegWriter.h index 1f54eed9..4fab6a77 100644 --- a/src/FFmpegWriter.h +++ b/src/FFmpegWriter.h @@ -134,9 +134,10 @@ namespace openshot { uint8_t *audio_outbuf; uint8_t *audio_encoder_buffer; - int num_of_rescalers; - int rescaler_position; - std::vector image_rescalers; + AVFrame *persistent_src_frame = nullptr; + AVFrame *persistent_dst_frame = nullptr; + uint8_t *persistent_dst_buffer = nullptr; + int persistent_dst_size = 0; int audio_outbuf_size; int audio_input_frame_size; @@ -180,11 +181,6 @@ namespace openshot { /// initialize streams void initialize_streams(); - /// @brief Init a collection of software rescalers (thread safe) - /// @param source_width The source width of the image scalers (used to cache a bunch of scalers) - /// @param source_height The source height of the image scalers (used to cache a bunch of scalers) - void InitScalers(int source_width, int source_height); - /// open audio codec void open_audio(AVFormatContext *oc, AVStream *st); @@ -230,9 +226,6 @@ namespace openshot { /// by the Open() method if this method has not yet been called. void PrepareStreams(); - /// Remove & deallocate all software scalers - void RemoveScalers(); - /// @brief Set audio resample options /// @param sample_rate The number of samples per second of the audio /// @param channels The number of audio channels @@ -327,6 +320,6 @@ namespace openshot { }; -} +} // namespace openshot #endif diff --git a/src/OpenMPUtilities.h b/src/OpenMPUtilities.h index 43932542..a50780b4 100644 --- a/src/OpenMPUtilities.h +++ b/src/OpenMPUtilities.h @@ -20,8 +20,9 @@ #include "Settings.h" // Calculate the # of OpenMP Threads to allow -#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) )) +#define OPEN_MP_NUM_PROCESSORS std::min(omp_get_num_procs(), std::max(2, openshot::Settings::Instance()->OMP_THREADS)) +#define FF_VIDEO_NUM_PROCESSORS std::min(omp_get_num_procs(), std::max(2, openshot::Settings::Instance()->FF_THREADS)) +#define FF_AUDIO_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.) diff --git a/src/Qt/VideoCacheThread.cpp b/src/Qt/VideoCacheThread.cpp index 4697ffe8..9c0aa9aa 100644 --- a/src/Qt/VideoCacheThread.cpp +++ b/src/Qt/VideoCacheThread.cpp @@ -34,7 +34,6 @@ namespace openshot , current_display_frame(1) , cached_frame_count(0) , min_frames_ahead(4) - , max_frames_ahead(8) , timeline_max_frame(0) , reader(nullptr) , force_directional_cache(false) @@ -89,6 +88,22 @@ namespace openshot return bytes; } + /// Start the cache thread at high priority, and return true if it’s actually running. + bool VideoCacheThread::StartThread() + { + // JUCE’s startThread() returns void, so we launch it and then check if + // the thread actually started: + startThread(Priority::high); + return isThreadRunning(); + } + + /// Stop the cache thread, waiting up to timeoutMs ms. Returns true if it actually stopped. + bool VideoCacheThread::StopThread(int timeoutMs) + { + stopThread(timeoutMs); + return !isThreadRunning(); + } + void VideoCacheThread::Seek(int64_t new_position, bool start_preroll) { if (start_preroll) { @@ -203,11 +218,14 @@ namespace openshot CacheBase* cache = reader ? reader->GetCache() : nullptr; // If caching disabled or no reader, sleep briefly - if (!settings->ENABLE_PLAYBACK_CACHING || !cache) { + if (!settings->ENABLE_PLAYBACK_CACHING || !cache || !is_playing) { std::this_thread::sleep_for(double_micro_sec(50000)); continue; } + // init local vars + min_frames_ahead = settings->VIDEO_CACHE_MIN_PREROLL_FRAMES; + Timeline* timeline = static_cast(reader); int64_t timeline_end = timeline->GetMaxFrame(); int64_t playhead = requested_display_frame; @@ -219,45 +237,7 @@ namespace openshot last_dir = dir; } - // If a seek was requested, reset last_cached_index - if (userSeeked) { - handleUserSeek(playhead, dir); - userSeeked = false; - } - else if (!paused) { - // Check if last_cached_index drifted outside the new window; if so, reset it - int64_t bytes_per_frame = getBytes( - (timeline->preview_width ? timeline->preview_width : reader->info.width), - (timeline->preview_height ? timeline->preview_height : reader->info.height), - reader->info.sample_rate, - reader->info.channels, - reader->info.fps.ToFloat() - ); - int64_t max_bytes = cache->GetMaxBytes(); - if (max_bytes > 0 && bytes_per_frame > 0) { - int64_t capacity = max_bytes / bytes_per_frame; - if (capacity >= 1) { - int64_t ahead_count = static_cast(capacity * - settings->VIDEO_CACHE_PERCENT_AHEAD); - int64_t window_begin, window_end; - computeWindowBounds(playhead, - dir, - ahead_count, - timeline_end, - window_begin, - window_end); - - bool outside_window = - (dir > 0 && last_cached_index > window_end) || - (dir < 0 && last_cached_index < window_begin); - if (outside_window) { - handleUserSeek(playhead, dir); - } - } - } - } - - // Recompute capacity & ahead_count now that we’ve possibly updated last_cached_index + // Compute bytes_per_frame, max_bytes, and capacity once int64_t bytes_per_frame = getBytes( (timeline->preview_width ? timeline->preview_width : reader->info.width), (timeline->preview_height ? timeline->preview_height : reader->info.height), @@ -266,11 +246,42 @@ namespace openshot reader->info.fps.ToFloat() ); int64_t max_bytes = cache->GetMaxBytes(); - if (max_bytes <= 0 || bytes_per_frame <= 0) { - std::this_thread::sleep_for(double_micro_sec(50000)); - continue; + int64_t capacity = 0; + if (max_bytes > 0 && bytes_per_frame > 0) { + capacity = max_bytes / bytes_per_frame; + if (capacity > settings->VIDEO_CACHE_MAX_FRAMES) { + capacity = settings->VIDEO_CACHE_MAX_FRAMES; + } } - int64_t capacity = max_bytes / bytes_per_frame; + + // Handle a user-initiated seek + if (userSeeked) { + handleUserSeek(playhead, dir); + userSeeked = false; + } + else if (!paused && capacity >= 1) { + // In playback mode, check if last_cached_index drifted outside the new window + int64_t base_ahead = static_cast(capacity * settings->VIDEO_CACHE_PERCENT_AHEAD); + + int64_t window_begin, window_end; + computeWindowBounds( + playhead, + dir, + base_ahead, + timeline_end, + window_begin, + window_end + ); + + bool outside_window = + (dir > 0 && last_cached_index > window_end) || + (dir < 0 && last_cached_index < window_begin); + if (outside_window) { + handleUserSeek(playhead, dir); + } + } + + // If capacity is insufficient, sleep and retry if (capacity < 1) { std::this_thread::sleep_for(double_micro_sec(50000)); continue; @@ -294,11 +305,7 @@ namespace openshot window_end); // Attempt to fill any missing frames in that window - bool window_full = prefetchWindow(cache, - window_begin, - window_end, - dir, - reader); + bool window_full = prefetchWindow(cache, window_begin, window_end, dir, reader); // If paused and window was already full, keep playhead fresh if (paused && window_full) { diff --git a/src/Qt/VideoCacheThread.h b/src/Qt/VideoCacheThread.h index fc2c944f..bfc0c490 100644 --- a/src/Qt/VideoCacheThread.h +++ b/src/Qt/VideoCacheThread.h @@ -68,6 +68,12 @@ namespace openshot */ void Seek(int64_t new_position, bool start_preroll); + /// Start the cache thread at high priority. Returns true if it’s actually running. + bool StartThread(); + + /// Stop the cache thread (wait up to timeoutMs ms). Returns true if it stopped. + bool StopThread(int timeoutMs = 0); + /** * @brief Attach a ReaderBase (e.g. Timeline, FFmpegReader) and begin caching. * @param new_reader @@ -165,7 +171,6 @@ namespace openshot int64_t cached_frame_count; ///< Count of frames currently added to cache. int64_t min_frames_ahead; ///< Minimum number of frames considered “ready” (pre-roll). - int64_t max_frames_ahead; ///< Maximum frames to attempt to cache (mem capped). int64_t timeline_max_frame; ///< Highest valid frame index in the timeline. ReaderBase* reader; ///< The source reader (e.g., Timeline, FFmpegReader). diff --git a/src/QtUtilities.h b/src/QtUtilities.h index 410ffefb..54106f71 100644 --- a/src/QtUtilities.h +++ b/src/QtUtilities.h @@ -4,7 +4,7 @@ * @author FeRD (Frank Dana) */ -// Copyright (c) 2008-2020 OpenShot Studios, LLC +// Copyright (c) 2008-2025 OpenShot Studios, LLC // // SPDX-License-Identifier: LGPL-3.0-or-later @@ -14,6 +14,7 @@ #include #include #include +#include // Fix Qt::endl for older Qt versions // From: https://bugreports.qt.io/browse/QTBUG-82680 @@ -26,14 +27,25 @@ namespace Qt { namespace openshot { + + // Cross-platform aligned free function + inline void aligned_free(void* ptr) + { +#if defined(_WIN32) + _aligned_free(ptr); +#else + free(ptr); +#endif + } + // Clean up buffer after QImage is deleted static inline void cleanUpBuffer(void *info) { if (!info) return; - // Remove buffer since QImage tells us to - uint8_t *qbuffer = reinterpret_cast(info); - delete[] qbuffer; + + // Free the aligned memory buffer + aligned_free(info); } } // namespace diff --git a/src/Settings.cpp b/src/Settings.cpp index 7afbd1d0..b2c16045 100644 --- a/src/Settings.cpp +++ b/src/Settings.cpp @@ -10,8 +10,8 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later -#include // For std::getenv - +#include +#include #include "Settings.h" using namespace openshot; @@ -25,22 +25,8 @@ Settings *Settings::Instance() if (!m_pInstance) { // Create the actual instance of Settings only once m_pInstance = new Settings; - m_pInstance->HARDWARE_DECODER = 0; - m_pInstance->HIGH_QUALITY_SCALING = false; - m_pInstance->OMP_THREADS = 12; - m_pInstance->FF_THREADS = 8; - m_pInstance->DE_LIMIT_HEIGHT_MAX = 1100; - m_pInstance->DE_LIMIT_WIDTH_MAX = 1950; - m_pInstance->HW_DE_DEVICE_SET = 0; - m_pInstance->HW_EN_DEVICE_SET = 0; - m_pInstance->VIDEO_CACHE_PERCENT_AHEAD = 0.7; - m_pInstance->VIDEO_CACHE_MIN_PREROLL_FRAMES = 24; - m_pInstance->VIDEO_CACHE_MAX_PREROLL_FRAMES = 48; - m_pInstance->VIDEO_CACHE_MAX_FRAMES = 30 * 10; - m_pInstance->ENABLE_PLAYBACK_CACHING = true; - m_pInstance->PLAYBACK_AUDIO_DEVICE_NAME = ""; - m_pInstance->PLAYBACK_AUDIO_DEVICE_TYPE = ""; - m_pInstance->DEBUG_TO_STDERR = false; + m_pInstance->OMP_THREADS = omp_get_num_procs(); + m_pInstance->FF_THREADS = omp_get_num_procs(); auto env_debug = std::getenv("LIBOPENSHOT_DEBUG"); if (env_debug != nullptr) m_pInstance->DEBUG_TO_STDERR = true; diff --git a/src/Settings.h b/src/Settings.h index e35b2be9..0474e3d1 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -65,10 +65,10 @@ namespace openshot { bool HIGH_QUALITY_SCALING = false; /// Number of threads of OpenMP - int OMP_THREADS = 12; + int OMP_THREADS = 16; /// Number of threads that ffmpeg uses - int FF_THREADS = 8; + int FF_THREADS = 16; /// Maximum rows that hardware decode can handle int DE_LIMIT_HEIGHT_MAX = 1100; diff --git a/tests/Profiles.cpp b/tests/Profiles.cpp index d515655c..8e319a3b 100644 --- a/tests/Profiles.cpp +++ b/tests/Profiles.cpp @@ -11,6 +11,7 @@ // SPDX-License-Identifier: LGPL-3.0-or-later #include "openshot_catch.h" +#include #include "Profiles.h" diff --git a/tests/Settings.cpp b/tests/Settings.cpp index f4717b37..0e300e34 100644 --- a/tests/Settings.cpp +++ b/tests/Settings.cpp @@ -13,15 +13,21 @@ #include "openshot_catch.h" #include "Settings.h" +#include + using namespace openshot; TEST_CASE( "Constructor", "[libopenshot][settings]" ) { + // Get system cpu count + int cpu_count = omp_get_num_procs(); + // Create an empty color Settings *s = Settings::Instance(); - CHECK(s->OMP_THREADS == 12); + CHECK(s->OMP_THREADS == cpu_count); + CHECK(s->FF_THREADS == cpu_count); CHECK_FALSE(s->HIGH_QUALITY_SCALING); } @@ -29,13 +35,13 @@ TEST_CASE( "Change settings", "[libopenshot][settings]" ) { // Create an empty color Settings *s = Settings::Instance(); - s->OMP_THREADS = 8; + s->OMP_THREADS = 13; s->HIGH_QUALITY_SCALING = true; - CHECK(s->OMP_THREADS == 8); + CHECK(s->OMP_THREADS == 13); CHECK(s->HIGH_QUALITY_SCALING == true); - CHECK(Settings::Instance()->OMP_THREADS == 8); + CHECK(Settings::Instance()->OMP_THREADS == 13); CHECK(Settings::Instance()->HIGH_QUALITY_SCALING == true); }