Skip to content

Commit

Permalink
implement threads for transcoder properly
Browse files Browse the repository at this point in the history
now uses threads to transcode batches of frames at a time, then compresses them synchronously
  • Loading branch information
Like4Schnitzel committed Jun 2, 2024
1 parent f978f1d commit 40fb647
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 104 deletions.
2 changes: 1 addition & 1 deletion src/libs/img2cimat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<cv::Vec3b>(x, y);
auto pixel = slice.at<cv::Vec3b>(y, x);
result += pixel * pixelPercent;
pixelCount += pixelPercent;
}
Expand Down
12 changes: 11 additions & 1 deletion src/transcoder/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ int main(int argc, char** argv)
uint16_t tWidth;
uint16_t tHeight;
uint32_t memoryCap;
uint threadCount;

map<string, string> cliArgs;

Expand Down Expand Up @@ -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;
}
188 changes: 88 additions & 100 deletions src/transcoder/videotranscoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
Expand All @@ -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<CharInfo[]> frameCIs = std::make_shared<CharInfo[]>(totalTerminalChars);
std::shared_ptr<CharInfo[]> previousFrameChars;
// plus 1 to keep one previous frame saved in transcodedFrames[0]
std::unique_ptr<std::shared_ptr<CharInfo[]>[]> transcodedFrames = std::make_unique<std::shared_ptr<CharInfo[]>[]>(maxThreads+1);
std::unique_ptr<std::thread[]> transcodingThreads = std::make_unique<std::thread[]>(maxThreads);
for (int i = 1; i <= maxThreads; i++)
{
transcodedFrames[i] = std::make_shared<CharInfo[]>(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<Byte> 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<Byte> 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<CharInfo[]>(totalTerminalChars);
} while (!frame.empty());

std::cout << "\33[2k\r" << "100\% done! \n" << std::flush;
}

auto findBiggestRectangle(const std::shared_ptr<bool[]> bitmap, const int bitCount, const int rowLength)
Expand Down Expand Up @@ -230,11 +253,6 @@ std::vector<Byte> VideoTranscoder::compressFrame(std::shared_ptr<CharInfo[]> cur
}
else
{
std::vector<std::thread> threads;
std::vector<std::vector<Byte>> threadResults;
threads.reserve(bitmaps.size());
threadResults.resize(bitmaps.size());

result.push_back(0); // marks new info

// now compress the bitmaps
Expand All @@ -243,82 +261,61 @@ std::vector<Byte> VideoTranscoder::compressFrame(std::shared_ptr<CharInfo[]> 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)
Expand All @@ -328,13 +325,4 @@ std::vector<Byte> VideoTranscoder::compressFrame(std::shared_ptr<CharInfo[]> cur
return result;
}

std::shared_ptr<CharInfo []> VideoTranscoder::transcodeFrame()
{
std::shared_ptr<CharInfo[]> frameInfo = std::make_shared<CharInfo[]>(vidTWidth*vidTHeight);

imgToCIMat<std::shared_ptr<CharInfo[]>>(frame, vidTWidth, vidTHeight, frameInfo);

return frameInfo;
}

}
3 changes: 1 addition & 2 deletions src/transcoder/videotranscoder.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<CharInfo []> transcodeFrame();
void transcodeFile(const uint maxThreads);
std::vector<Byte> compressFrame(std::shared_ptr<CharInfo[]> currentFrame, std::shared_ptr<CharInfo[]> prevFrame);
};

Expand Down

0 comments on commit 40fb647

Please sign in to comment.