From 40fb6477945f91b85d84b0cb5c62e62842f2282d Mon Sep 17 00:00:00 2001 From: Schnitzel Date: Sun, 2 Jun 2024 20:57:15 +0200 Subject: [PATCH] implement threads for transcoder properly now uses threads to transcode batches of frames at a time, then compresses them synchronously --- src/libs/img2cimat.cpp | 2 +- src/transcoder/main.cpp | 12 +- src/transcoder/videotranscoder.cpp | 188 ++++++++++++++--------------- src/transcoder/videotranscoder.hpp | 3 +- 4 files changed, 101 insertions(+), 104 deletions(-) diff --git a/src/libs/img2cimat.cpp b/src/libs/img2cimat.cpp index 5838042..3e5e649 100644 --- a/src/libs/img2cimat.cpp +++ b/src/libs/img2cimat.cpp @@ -50,7 +50,7 @@ cv::Vec3b meanColWithSubPixels(cv::Mat mat, double x, double y, double width, do if (y == maxH - 1) pixelPercent *= y1Percent; - auto pixel = slice.at(x, y); + auto pixel = slice.at(y, x); result += pixel * pixelPercent; pixelCount += pixelPercent; } diff --git a/src/transcoder/main.cpp b/src/transcoder/main.cpp index e1720b6..90c7387 100644 --- a/src/transcoder/main.cpp +++ b/src/transcoder/main.cpp @@ -11,6 +11,7 @@ int main(int argc, char** argv) uint16_t tWidth; uint16_t tHeight; uint32_t memoryCap; + uint threadCount; map cliArgs; @@ -92,8 +93,17 @@ int main(int argc, char** argv) cin >> tHeight; } + if (cliArgs.count("--threads") > 0) + { + threadCount = VariousUtils::stringToInt(cliArgs["--threads"]); + } + else + { + threadCount = max(std::thread::hardware_concurrency(), 1); + } + VideoTranscoder trans(videoPath, vtdiFilePath, tWidth, tHeight); - trans.transcodeFile(); + trans.transcodeFile(threadCount); return 0; } \ No newline at end of file diff --git a/src/transcoder/videotranscoder.cpp b/src/transcoder/videotranscoder.cpp index 1155b1d..b07b19a 100644 --- a/src/transcoder/videotranscoder.cpp +++ b/src/transcoder/videotranscoder.cpp @@ -6,24 +6,18 @@ VideoTranscoder::VideoTranscoder(const std::string path, const std::string vtdiP { vidPath = path; this->vtdiPath = vtdiPath; - std::cout << "Attempting to open \"" << path << "\".\n"; vidCap.open(vidPath); if (!vidCap.isOpened()) { throw std::invalid_argument("The video at the provided path could not be read."); } - std::cout << "\"" << path << "\" opened successfully.\n"; vidWidth = vidCap.get(cv::CAP_PROP_FRAME_WIDTH); vidHeight = vidCap.get(cv::CAP_PROP_FRAME_HEIGHT); - std::cout << "Video dimensions: " << vidWidth << "x" << vidHeight << "\n"; vidFPS = vidCap.get(cv::CAP_PROP_FPS); - std::cout << "Video FPS: " << vidFPS << "\n"; vidFrames = vidCap.get(cv::CAP_PROP_FRAME_COUNT); - std::cout << "Video Frames: " << vidFrames << "\n"; vidTWidth = terminalWidth; vidTHeight = terminalHeight; - std::cout << "Terminal dimensions: " << terminalWidth << "x" << terminalHeight << " characters\n"; widthPixelsPerChar = (double) vidWidth / vidTWidth; heightPixelsPerChar = (double) vidHeight / vidTHeight; @@ -40,9 +34,9 @@ cv::Mat VideoTranscoder::getFrame() return frame; } -void VideoTranscoder::transcodeFile() +void VideoTranscoder::transcodeFile(const uint maxThreads) { - const uint16_t versionNumber = 2; // change if updates to the file format are made + constexpr uint16_t versionNumber = 2; // change if updates to the file format are made // reset output file just in case BinaryUtils::writeToFile(vtdiPath, (char*)nullptr, 0, false); @@ -55,34 +49,63 @@ void VideoTranscoder::transcodeFile() (..., BinaryUtils::writeToFile(vtdiPath, (char*)BinaryUtils::numToByteArray(args).data(), sizeof(args), true)); }, args); - // settings constants for video byte writing + // setting constants for video byte writing const int totalTerminalChars = vidTWidth * vidTHeight; - std::shared_ptr frameCIs = std::make_shared(totalTerminalChars); - std::shared_ptr previousFrameChars; + // plus 1 to keep one previous frame saved in transcodedFrames[0] + std::unique_ptr[]> transcodedFrames = std::make_unique[]>(maxThreads+1); + std::unique_ptr transcodingThreads = std::make_unique(maxThreads); + for (int i = 1; i <= maxThreads; i++) + { + transcodedFrames[i] = std::make_shared(totalTerminalChars); + } uint32_t frameBytesIndex = 0; - int frameIndex = 0; - double progress = -1; - // writing transcoded video bytes + int completedFrames = 0; + double previousProgress = -1; + + const auto updateProgress = [&completedFrames, &previousProgress, *this]() + { + double newProgress = (int)((double) completedFrames / this->vidFrames * 10000) / 100.; // round to 4 digits + if (newProgress != previousProgress) + { + previousProgress = newProgress; + std::cout << std::fixed << std::setprecision(2) << "\33[2K\r" << newProgress << "\% done..." << std::flush; + } + }; + + const auto transcodeOneFrame = [&transcodedFrames, &completedFrames, updateProgress, *this](int i, cv::Mat img) + { + imgToCIMat(img, this->vidTWidth, this->vidTHeight, transcodedFrames[i]); + completedFrames++; + updateProgress(); + }; + std::cout << "Transcoding frames...\n"; - for (vidCap>>frame; !frame.empty(); vidCap>>frame) + + do { - // progress update - double newProgress = (int)((double) frameIndex / vidFrames * 10000) / 100.; // round to 4 digits - if (newProgress != progress) + int i; + for (i = 0; i < maxThreads; i++) { - progress = newProgress; - std::cout << std::fixed << std::setprecision(2) << "\33[2K\r" << progress << "\% done..." << std::flush; + vidCap>>frame; + if (frame.empty()) + break; + transcodingThreads[i] = std::thread(transcodeOneFrame, i+1, frame.clone()); } - frameIndex++; - frameCIs = transcodeFrame(); - std::vector frameBytes = compressFrame(frameCIs, previousFrameChars); - BinaryUtils::writeToFile(vtdiPath, (char*)frameBytes.data(), frameBytes.size(), true); - previousFrameChars = frameCIs; - } + for (int j = 0; j < i; j++) + { + transcodingThreads[j].join(); + + std::vector frameBytes = compressFrame(transcodedFrames[j+1], transcodedFrames[j]); + BinaryUtils::writeToFile(vtdiPath, (char*)frameBytes.data(), frameBytes.size(), true); + } - std::cout << "\33[2k\r" << "100\% done! \n" << std::flush; + transcodedFrames[0] = transcodedFrames[maxThreads]; + transcodedFrames[maxThreads] = std::make_shared(totalTerminalChars); + } while (!frame.empty()); + + std::cout << "\33[2k\r" << "100\% done! \n" << std::flush; } auto findBiggestRectangle(const std::shared_ptr bitmap, const int bitCount, const int rowLength) @@ -230,11 +253,6 @@ std::vector VideoTranscoder::compressFrame(std::shared_ptr cur } else { - std::vector threads; - std::vector> threadResults; - threads.reserve(bitmaps.size()); - threadResults.resize(bitmaps.size()); - result.push_back(0); // marks new info // now compress the bitmaps @@ -243,82 +261,61 @@ std::vector VideoTranscoder::compressFrame(std::shared_ptr cur ulong ciHash = it->first; auto bitmap = it->second; - threads.emplace_back( - [ - &compressedBytes = threadResults[threads.size()], - ciHash, - bitmap, - vidTWidth = this->vidTWidth, - arraySize - ] - () + // append CI bits + auto ciHashBytes = BinaryUtils::numToByteArray(ciHash); + for (int i = 1; i <= sizeof(CharInfo); i++) // start at the second byte, since CIs only have 7 but ulongs have 8 { - // append CI bits - auto ciHashBytes = BinaryUtils::numToByteArray(ciHash); - for (int i = 1; i <= sizeof(CharInfo); i++) // start at the second byte, since CIs only have 7 but ulongs have 8 - { - compressedBytes.push_back(ciHashBytes[i]); - } + result.push_back(ciHashBytes[i]); + } - auto rect = findBiggestRectangle(bitmap, arraySize*sizeof(bool), vidTWidth); - while(rect[0] != -1) + auto rect = findBiggestRectangle(bitmap, arraySize*sizeof(bool), vidTWidth); + while(rect[0] != -1) + { + // write rectangle info to resulting bit vector + // true if the rectangle is just 1 element + if (rect[0] == rect[2] && rect[1] == rect[3]) { - // write rectangle info to resulting bit vector - // true if the rectangle is just 1 element - if (rect[0] == rect[2] && rect[1] == rect[3]) - { - // 1 is the code for position - compressedBytes.push_back(1); + // 1 is the code for position + result.push_back(1); - for (int i = 0; i < 2; i++) - { - for (Byte byte : BinaryUtils::numToByteArray((uint16_t) rect[i])) - { - compressedBytes.push_back(byte); - } - } - } - else + for (int i = 0; i < 2; i++) { - // 0 is the code for rectangle - compressedBytes.push_back(0); - - for (int i = 0; i < 4; i++) + for (Byte byte : BinaryUtils::numToByteArray((uint16_t) rect[i])) { - for (Byte byte : BinaryUtils::numToByteArray((uint16_t) rect[i])) - { - compressedBytes.push_back(byte); - } + result.push_back(byte); } } + } + else + { + // 0 is the code for rectangle + result.push_back(0); - // clear rectangle from bitmap - // y coordinate - for (int i = rect[1]; i <= rect[3]; i++) + for (int i = 0; i < 4; i++) { - // x coordinate - for (int j = rect[0]; j <= rect[2]; j++) + for (Byte byte : BinaryUtils::numToByteArray((uint16_t) rect[i])) { - bitmap.get()[i*vidTWidth+j] = 0; + result.push_back(byte); } } - - rect = findBiggestRectangle(bitmap, arraySize*sizeof(bool), vidTWidth); } - // 2 is the code for end of CI segment - compressedBytes.push_back(2); - }); - } + // clear rectangle from bitmap + // y coordinate + for (int i = rect[1]; i <= rect[3]; i++) + { + // x coordinate + for (int j = rect[0]; j <= rect[2]; j++) + { + bitmap.get()[i*vidTWidth+j] = 0; + } + } - // wait for all threads to finish - for (int i = 0; i < threads.size(); i++) - { - threads[i].join(); - for (Byte byte : threadResults[i]) - { - result.push_back(byte); + rect = findBiggestRectangle(bitmap, arraySize*sizeof(bool), vidTWidth); } + + // 2 is the code for end of CI segment + result.push_back(2); } // replace last end of CI (2) with end of frame (3) @@ -328,13 +325,4 @@ std::vector VideoTranscoder::compressFrame(std::shared_ptr cur return result; } -std::shared_ptr VideoTranscoder::transcodeFrame() -{ - std::shared_ptr frameInfo = std::make_shared(vidTWidth*vidTHeight); - - imgToCIMat>(frame, vidTWidth, vidTHeight, frameInfo); - - return frameInfo; -} - } diff --git a/src/transcoder/videotranscoder.hpp b/src/transcoder/videotranscoder.hpp index e401730..c1a47a5 100644 --- a/src/transcoder/videotranscoder.hpp +++ b/src/transcoder/videotranscoder.hpp @@ -34,8 +34,7 @@ class VideoTranscoder VideoTranscoder(const std::string path, const std::string vtdiPath, const uint16_t terminalWidth, const uint16_t terminalHeight); ~VideoTranscoder(); cv::Mat getFrame(); - void transcodeFile(); - std::shared_ptr transcodeFrame(); + void transcodeFile(const uint maxThreads); std::vector compressFrame(std::shared_ptr currentFrame, std::shared_ptr prevFrame); };