/* * Acid Cam v2 - Qt/OpenCV Edition * written by Jared Bruni ( http://lostsidedead.com ) * (C) 2017 GPL */ #include"playback_thread.h" #include<chrono> namespace { void ApplyNegateSwapOnce(cv::Mat &frame, bool negate_enabled) { const bool do_swap = (ac::color_order != 0) || (ac::swapColor_r != 0) || (ac::swapColor_g != 0) || (ac::swapColor_b != 0); if(!do_swap && !negate_enabled) return; for(int z = 0; z < frame.rows; ++z) { for(int i = 0; i < frame.cols; ++i) { if(do_swap) { ac::swapColors_(frame, z, i); } if(negate_enabled) { cv::Vec3b &pixel = frame.at<cv::Vec3b>(z, i); pixel[0] = ~pixel[0]; pixel[1] = ~pixel[1]; pixel[2] = ~pixel[2]; } } } } } Playback::Playback(QObject *parent) : QThread(parent) { stop = true; isStep = false; isPaused = false; bright_ = gamma_ = saturation_ = 0; single_mode = true; alpha = 0; prev_filter = FilterValue(0, 0, -1); flip_frame1 = false; flip_frame2 = false; repeat_video = false; fadefilter = false; cycle_on = 0; cycle_index = 0; frame_num = 0; _custom_cycle = false; _custom_cycle_index = 0; fps_delay = 60; filter_map_ex = filter_map; blend_image_copy_set = false; ffmpeg_pipe = nullptr; use_ffmpeg = false; ffmpeg_mux_audio = false; encoder_thread = new FFmpegEncoderThread(this); } bool Playback::VideoRelease() { bool ret = false; mutex.lock(); if(ac::v_cap.isOpened()) { ac::v_cap.release(); ret = true; video_is_set = false; } mutex.unlock(); return ret; } void Playback::setCustomCycle(bool b) { _custom_cycle = b; _custom_cycle_index = 0; } void Playback::setCustomCycleDelay(int delay) { fps_delay = delay; } void Playback::Play() { if(!isRunning()) { if(isStopped()) { stop = false; } } prev_filter = FilterValue(0, 0, -1); alpha = 0; //start(LowPriority); start(HighPriority); } void Playback::setPngPath(std::string path) { mutex.lock(); png_path = path; png_index = 0; mutex.unlock(); } void Playback::setVideo(cv::VideoCapture cap, cv::VideoWriter wr, bool record, bool rec_png) { mutex.lock(); mode = MODE_VIDEO; record_png = rec_png; capture = cap; writer = wr; recording = record; if(capture.isOpened()) { frame_rate = capture.get(cv::CAP_PROP_FPS); if(frame_rate <= 0) frame_rate = 24; } mutex.unlock(); } bool Playback::setVideoCamera(std::string name, int type, int device, int res, cv::VideoWriter wr, bool record) { mutex.lock(); mode = MODE_CAMERA; device_num = device; //#if defined(__linux__) || defined(__APPLE__) #ifdef _WIN32 capture.open(device, cv::CAP_DSHOW); #elif __linux__ capture.open(device, cv::CAP_V4L2); #else capture.open(device); #endif if(!capture.isOpened()) { mutex.unlock(); return false; } capture.set(cv::CAP_PROP_FOURCC, cv::VideoWriter::fourcc('M','J','P','G')); recording = record; writer = wr; int res_w = 640, res_h = 480; switch(res) { case 0: res_w = 640; res_h = 480; break; case 1: res_w = 1280; res_h = 720; break; case 2: res_w = 1920; res_h = 1080; break; } capture.set(cv::CAP_PROP_FRAME_WIDTH, res_w); capture.set(cv::CAP_PROP_FRAME_HEIGHT, res_h); double fps = capture.get(cv::CAP_PROP_FPS); res_w = capture.get(cv::CAP_PROP_FRAME_WIDTH); res_h = capture.get(cv::CAP_PROP_FRAME_HEIGHT); if(record == true && name.size()>0) { writer = cv::VideoWriter(name, type, fps, cv::Size(res_w, res_h), true); if(!writer.isOpened()) { return false; } } mutex.unlock(); return true; } bool Playback::videoIsOpen() { return video_is_set; } bool Playback::openVideo(std::string vname) { mutex.lock(); ac::v_cap.open(vname); if(ac::v_cap.isOpened() == false) { mutex.unlock(); video_is_set = false; return false; } mutex.unlock(); video_is_set = true; return true; } void Playback::setVector(std::vector<FilterValue> v) { mutex.lock(); // here: //ac::release_all_objects(); current = v; mutex.unlock(); } void Playback::setFilterMapEx(std::unordered_map<std::string, FilterValue> f) { mutex.lock(); filter_map_ex_set = f; setFilterMap = true; mutex.unlock(); } unsigned long Playback::calcMem() { mutex.lock(); unsigned long calc = ac::calculateMemory(); mutex.unlock(); return calc; } void Playback::setAlpha(int a) { mutex.lock(); ac::alpha_increase = a; mutex.unlock(); } bool Playback::getProgramMode() { return single_mode; } void Playback::setProcMode(int p) { mutex.lock(); ac::setProcMode(ac::PROC_MODE_TYPE(p)); mutex.unlock(); } void Playback::setMaxAlloc(int a) { mutex.lock(); ac::setMaxAllocated(a); mutex.unlock(); } unsigned int Playback::getObjectSize() { mutex.lock(); unsigned int s = ac::all_objects.size(); mutex.unlock(); return s; } unsigned long Playback::allocatedFrames() { mutex.lock(); unsigned long l = ac::getCurrentAllocatedFrames(); mutex.unlock(); return l; } unsigned long Playback::getMaxAlloc() { mutex.lock(); unsigned long l = ac::getMaxAllocated(); mutex.unlock(); return l; } void Playback::setWaitColorLevel(int w, int l) { mutex.lock(); ac::setVariableWait(w); ac::setColorLevel(l); mutex.unlock(); } void Playback::setOptions(bool n, int c) { mutex.lock(); ac::isNegative = n; negate = n; reverse = c; ac::color_order = c; mutex.unlock(); } void Playback::setCycle(int type, int frame_skip, std::vector<std::string> &v) { mutex.lock(); cycle_on = type; if(!cycle_values.empty()) cycle_values.erase(cycle_values.begin(), cycle_values.end()); if(v.size() > 1) { for(auto &i : v) { cv::Mat value = cv::imread(i); cycle_values.push_back(value); } cycle_index = 0; frame_num = frame_skip; blend_image_copy = cv::imread(v[0]); blend_image_copy_set = true; blend_set = true; static std::random_device r; static auto rng = std::default_random_engine(r()); std::shuffle(cycle_values.begin(), cycle_values.end(), rng); } mutex.unlock(); } void Playback::setCycle(int type) { cycle_on = type; cycle_index = 0; blend_set = false; } void Playback::reset_filters() { mutex.lock(); if(ac::reset_alpha == false) { ac::reset_alpha = true; } ac::image_matrix_reset = true; ac::frames_released = true; mutex.unlock(); } void Playback::clearImage() { mutex.lock(); blend_set = false; blend_image.release(); mutex.unlock(); } void Playback::SetFlip(bool f1, bool f2) { flip_frame1 = f1; flip_frame2 = f2; } void Playback::setColorOptions(int b, int g, int s) { bright_ = b; gamma_ = g; saturation_ = s; } void Playback::setPref(int thread_count, int intense) { mutex.lock(); ac::setThreadCount(thread_count); ac::setPixelCollection(intense); mutex.unlock(); } void Playback::setIndexChanged(std::string value) { mutex.lock(); prev_filter = current_filter; current_filter = filter_map[value]; // here: //ac::release_all_objects(); alpha = 1.0; mutex.unlock(); } void Playback::setSingleMode(bool val) { mutex.lock(); single_mode = val; mutex.unlock(); } void Playback::setRGB(int r, int g, int b) { mutex.lock(); ac::swapColor_r = r; ac::swapColor_g = g; ac::swapColor_b = b; mutex.unlock(); } void Playback::setColorMap(int c) { mutex.lock(); ac::set_color_map = c; mutex.unlock(); } void Playback::setDisplayed(bool shown) { mutex.lock(); video_shown = shown; mutex.unlock(); } void Playback::drawEffects(cv::Mat &frame) { if(ac::set_color_map > 0) ac::ApplyColorMap(frame); if(bright_ > 0) { ac::setBrightness(frame, 1.0, bright_); } if(gamma_ > 0) { cv::Mat gam = frame.clone(); ac::setGamma(gam, frame, gamma_); } if(saturation_ > 0) { ac::setSaturation(frame, saturation_); } if(colorkey_filter == true || (colorkey_replace == true && !color_replace_image.empty())) { cv::Mat cframe = frame.clone(); cv::Vec3b well_color(255,0,255); ac::filterColorKeyed(well_color, ac::orig_frame, cframe, frame); } } void Playback::drawFilter(cv::Mat &frame, FilterValue &f) { if(f.index == 0) { if(f.filter < 0 || f.filter >= static_cast<int>(ac::draw_strings.size())) return; const std::string &filter_name = ac::draw_strings[f.filter]; if(single_mode == true && filter_name.find("SubFilter") != std::string::npos) return; if(single_mode == false && filter_name.find("SubFilter") != std::string::npos && f.subfilter == -1) return; if(ac::getMaxAllocated() < 1080 && filter_name.find("Intertwine") != std::string::npos) return; if(ac::getMaxAllocated() < 1080 && filter_name.find("InOrder") != std::string::npos) return; if(ac::getMaxAllocated() < 1080 && filter_name.find("Slit") != std::string::npos) return; ac::setSubFilter(f.subfilter); ac::CallFilter(filter_name, frame); ac::setSubFilter(-1); } else if(current_filter.index == 1) { current_filterx = f.filter; ac::alphaFlame(frame); } else if(f.index == 2) { draw_plugin(frame, f.filter); } } void Playback::run() { #ifdef _WIN32 int duration = (1000/ac::fps)/4; #else int duration = 10; #endif while(!stop) { std::chrono::time_point<std::chrono::system_clock> now = std::chrono::system_clock::now(); mutex.lock(); if(ac::release_frames) { std::cout << "Cleared Frames...\n"; ac::release_all_objects(); ac::release_frames = false; } if(!capture.read(frame)) { if(repeat_video && mode == MODE_VIDEO) { mutex.unlock(); setFrameIndex(0); emit resetIndex(); continue; } stop = true; ac::release_all_objects(); mutex.unlock(); emit stopRecording(); return; } static std::vector<FilterValue> cur; cur = current; ac::orig_frame = frame.clone(); FilterValue current_filterxv = current_filter, prev_filterx = prev_filter; std::string png_pathx = png_path; if(blend_image_copy_set) { blend_image = blend_image_copy.clone(); blend_image_copy_set = false; } if(chroma_image_set) { color_replace_image = chroma_image.clone(); chroma_image_set = false; } if(setFilterMap) { filter_map_ex = filter_map_ex_set; setFilterMap = false; } mutex.unlock(); cv::Mat temp_frame; if(flip_frame1 == true) { cv::flip(frame, temp_frame, 1); frame = temp_frame; } if(flip_frame2 == true) { cv::flip(frame, temp_frame, 0); frame = temp_frame; } if(cycle_on > 0) { cv::Mat *cycle_image = 0; static int frame_count = 0; ++frame_count; if(frame_count > frame_num) { mutex.lock(); frame_count = 0; switch(cycle_on) { case 0: break; case 1: cycle_image = &cycle_values[rand()%cycle_values.size()]; break; case 2: cycle_image = &cycle_values[cycle_index]; ++cycle_index; if(cycle_index > static_cast<int>(cycle_values.size()-1)) cycle_index = 0; break; case 3: cycle_image = &cycle_values[cycle_index]; ++cycle_index; if(cycle_index > static_cast<int>(cycle_values.size()-1)) { cycle_index = 0; static std::random_device r; static auto rng = std::default_random_engine(r()); std::shuffle(cycle_values.begin(), cycle_values.end(), rng); } break; } if(blend_set == true && cycle_image != 0) blend_image = cycle_image->clone(); mutex.unlock(); } } if(single_mode == true && alpha > 0) { if(fadefilter == true) filterFade(frame, current_filterxv, prev_filterx, alpha); drawEffects(frame); alpha = alpha-0.08; } else if(single_mode == true) { ac::setSubFilter(-1); ac::in_custom = false; drawFilter(frame, current_filterxv); drawEffects(frame); msleep(duration); } else if(cur.size()>0) { ac::in_custom = true; if(_custom_cycle == false) { for(unsigned int i = 0; i < cur.size(); ++i) { drawFilter(frame, cur[i]); //msleep(duration); } } else { if(_custom_cycle_index > static_cast<int>(cur.size()-1)) _custom_cycle_index = 0; if(_custom_cycle_index >= 0 && _custom_cycle_index < static_cast<int>(cur.size())) { drawFilter(frame, cur[_custom_cycle_index]); //msleep(duration); } } drawEffects(frame); ApplyNegateSwapOnce(frame, ac::isNegative); ac::in_custom = false; static int delay_counter = 0; ++delay_counter; if(delay_counter > (fps_delay * static_cast<int>(ac::fps))) { delay_counter = 0; ++_custom_cycle_index; if(_custom_cycle_index > static_cast<int>(cur.size()-1)) _custom_cycle_index = 0; } } else { msleep(duration); } if(record_png) { std::ostringstream stream; stream << png_pathx << "/" << std::setfill('0') << std::setw(15) << png_index << ".png"; ++png_index; cv::imwrite(stream.str(), frame); } mutex.lock(); if(recording && use_ffmpeg && encoder_thread && encoder_thread->isEncoding()) { // Send frame to encoder thread (non-blocking) encoder_thread->enqueueFrame(frame); } else if(recording && writer.isOpened()) { writer.write(frame); } mutex.unlock(); if(video_shown == true) { if(frame.channels()==3) { cv::cvtColor(frame, rgb_frame, cv::COLOR_BGR2RGB); img = QImage((const unsigned char*)(rgb_frame.data), rgb_frame.cols, rgb_frame.rows, QImage::Format_RGB888); } else { img = QImage((const unsigned char*)(frame.data), frame.cols, frame.rows, QImage::Format_Indexed8); } emit procImage(img); if(isStep == true) { isStep = false; return; } } else { emit frameIncrement(); } std::chrono::time_point<std::chrono::system_clock> nowx = std::chrono::system_clock::now(); auto m = std::chrono::duration_cast<std::chrono::milliseconds>(nowx - now).count(); if (ac::fps > 0) { int fps_mil = 1000 / ac::fps; if (m < fps_mil) std::this_thread::sleep_for(std::chrono::milliseconds(fps_mil - m - 1)); } } mutex.lock(); ac::release_all_objects(); mutex.unlock(); } Playback::~Playback() { // Stop the encoder thread first if (encoder_thread) { encoder_thread->stopEncoding(); delete encoder_thread; encoder_thread = nullptr; } mutex.lock(); stop = true; #if defined(__linux__) || defined(__APPLE__) condition.wakeOne(); #endif mutex.unlock(); #if defined(__linux__) || defined(__APPLE__) wait(); #endif } void Playback::setFrameIndex(const long &index) { mutex.lock(); capture.set(cv::CAP_PROP_POS_FRAMES, index); mutex.unlock(); } bool Playback::getFrame(QImage &img, const int &index) { QImage image; setFrameIndex(index); mutex.lock(); cv::Mat frame; if(mode == MODE_VIDEO && capture.read(frame)) { cv::cvtColor(frame, rgb_frame, cv::COLOR_BGR2RGB); img = QImage((const unsigned char*)(rgb_frame.data), rgb_frame.cols, rgb_frame.rows, QImage::Format_RGB888); mutex.unlock(); setFrameIndex(index); return true; } mutex.unlock(); return false; } void Playback::enableRepeat(bool re) { repeat_video = re; } void Playback::Clear() { blend_set = false; colorkey_set = false; mutex.lock(); blend_image.release(); color_image.release(); mutex.unlock(); } void Playback::Stop() { stop = true; alpha = 0; mutex.lock(); prev_filter = FilterValue(0, 0, -1); mutex.unlock(); } void Playback::Release() { mutex.lock(); stop = true; if(capture.isOpened() && mode == MODE_VIDEO) capture.release(); if(writer.isOpened()) writer.release(); mutex.unlock(); if(use_ffmpeg && encoder_thread && encoder_thread->isEncoding()) { encoder_thread->stopEncoding(); closeFFmpeg(); } } void Playback::msleep(int ms) { QThread::msleep(ms); } bool Playback::isStopped() const { return this->stop; } void Playback::setStep() { isStep = true; } void Playback::setImage(const cv::Mat &frame) { blend_set = true; mutex.lock(); blend_image_copy = frame.clone(); blend_image_copy_set = true; mutex.unlock(); } void Playback::setChromaImage(cv::Mat &frame) { mutex.lock(); chroma_image = frame.clone(); chroma_image_set = true; mutex.unlock(); } void Playback::setFadeFilter(bool f) { mutex.lock(); fadefilter = f; mutex.unlock(); } void Playback::setColorKey(const cv::Mat &image) { colorkey_set = true; mutex.lock(); color_image = image; mutex.unlock(); } void Playback::filterFade(cv::Mat &frame, FilterValue &filter1, FilterValue &filter2, double alpha) { unsigned int h = frame.rows; // frame height unsigned int w = frame.cols;// framew idth // make copies of original frame cv::Mat frame1 = frame.clone(), frame2 = frame.clone(); // apply filters on two copies of original frame drawFilter(frame1,filter1); drawFilter(frame2,filter2); // loop through image setting each pixel with alphablended pixel for(unsigned int z = 0; z < h; ++z) { for(unsigned int i = 0; i < w; ++i) { cv::Vec3b &pixel = frame.at<cv::Vec3b>(z, i); // target pixel cv::Vec3b frame1_pix = frame1.at<cv::Vec3b>(z, i); // frame1 pixel cv::Vec3b frame2_pix = frame2.at<cv::Vec3b>(z, i); // frame2 pixel // loop through pixel components and set target pixel to alpha blended pixel of two frames for(unsigned int q = 0; q < 3; ++q) pixel[q] = frame2_pix[q]+(frame1_pix[q]*alpha); } } } void Playback::setVideoFFmpeg(cv::VideoCapture cap, const std::string &outputPath, FFmpegCodec codec, int crf, double fps, int width, int height, bool muxAudio, const std::string &sourcePath) { mutex.lock(); mode = MODE_VIDEO; capture = cap; recording = true; use_ffmpeg = true; ffmpeg_mux_audio = muxAudio; ffmpeg_source_path = sourcePath; ffmpeg_output_path = outputPath; if(capture.isOpened()) { frame_rate = capture.get(cv::CAP_PROP_FPS); if(frame_rate <= 0) frame_rate = 24; } std::ostringstream resStream; resStream << width << "x" << height; std::string resolution = resStream.str(); // Start background encoding thread if (encoder_thread && encoder_thread->startEncoding(outputPath, codec, resolution, resolution, fps, crf)) { std::cout << "acidcam: Background FFmpeg encoder thread started\n"; } else { std::cerr << "acidcam: Failed to start background FFmpeg encoder thread\n"; use_ffmpeg = false; recording = false; } mutex.unlock(); } bool Playback::setVideoCameraFFmpeg(const std::string &outputPath, int device, int res, FFmpegCodec codec, int crf) { mutex.lock(); mode = MODE_CAMERA; device_num = device; #ifdef _WIN32 capture.open(device, cv::CAP_DSHOW); #else capture.open(device); #endif if(!capture.isOpened()) { mutex.unlock(); return false; } recording = true; use_ffmpeg = true; ffmpeg_output_path = outputPath; int res_w = 640, res_h = 480; switch(res) { case 0: res_w = 640; res_h = 480; break; case 1: res_w = 1280; res_h = 720; break; case 2: res_w = 1920; res_h = 1080; break; } capture.set(cv::CAP_PROP_FRAME_WIDTH, res_w); capture.set(cv::CAP_PROP_FRAME_HEIGHT, res_h); double fps = capture.get(cv::CAP_PROP_FPS); if(fps <= 0) fps = 30; res_w = static_cast<int>(capture.get(cv::CAP_PROP_FRAME_WIDTH)); res_h = static_cast<int>(capture.get(cv::CAP_PROP_FRAME_HEIGHT)); std::ostringstream resStream; resStream << res_w << "x" << res_h; std::string resolution = resStream.str(); // Start background encoding thread if (encoder_thread && encoder_thread->startEncoding(outputPath, codec, resolution, resolution, fps, crf)) { std::cout << "acidcam: Background FFmpeg encoder thread started (camera)\n"; } else { std::cerr << "acidcam: Failed to start background FFmpeg encoder thread (camera)\n"; use_ffmpeg = false; recording = false; mutex.unlock(); return false; } mutex.unlock(); return true; } void Playback::closeFFmpeg() { mutex.lock(); // Stop the encoder thread if(encoder_thread && encoder_thread->isEncoding()) { encoder_thread->stopEncoding(); } // If we need to mux audio from source if(ffmpeg_mux_audio && !ffmpeg_source_path.empty() && !ffmpeg_output_path.empty()) { // Create final output path (replace temp_ prefix or add _final) std::string finalPath = ffmpeg_output_path; auto lastSlash = finalPath.rfind('/'); if(lastSlash == std::string::npos) lastSlash = finalPath.rfind('\\'); std::string dir = (lastSlash != std::string::npos) ? finalPath.substr(0, lastSlash + 1) : ""; std::string filename = (lastSlash != std::string::npos) ? finalPath.substr(lastSlash + 1) : finalPath; // If filename starts with temp_, create non-temp version if(filename.substr(0, 5) == "temp_") { filename = filename.substr(5); } else { // Add _with_audio before extension auto dotPos = filename.rfind('.'); if(dotPos != std::string::npos) { filename = filename.substr(0, dotPos) + "_with_audio" + filename.substr(dotPos); } } finalPath = dir + filename; emit ffmpegFinished(QString::fromStdString(ffmpeg_output_path), QString::fromStdString(ffmpeg_source_path), QString::fromStdString(finalPath)); } use_ffmpeg = false; recording = false; ffmpeg_mux_audio = false; ffmpeg_pipe = nullptr; mutex.unlock(); }