From e96d885e4c96baf2d45bde21eca6fb1b887105d6 Mon Sep 17 00:00:00 2001 From: James H Ball Date: Sun, 1 Dec 2024 18:16:21 +0000 Subject: [PATCH] ffmpeg support now fully working on windows --- Source/concurrency/WriteProcess.h | 137 ++++++++++++++++++++++ Source/visualiser/VisualiserComponent.cpp | 24 +--- Source/visualiser/VisualiserComponent.h | 3 +- osci-render.jucer | 1 + sosci.jucer | 1 + 5 files changed, 145 insertions(+), 21 deletions(-) create mode 100644 Source/concurrency/WriteProcess.h diff --git a/Source/concurrency/WriteProcess.h b/Source/concurrency/WriteProcess.h new file mode 100644 index 0000000..62a8671 --- /dev/null +++ b/Source/concurrency/WriteProcess.h @@ -0,0 +1,137 @@ +#pragma once + +#include +#if JUCE_WINDOWS + #include +#endif + + +class WriteProcess { +public: + + WriteProcess() {} + + void start(juce::String cmd) { + if (isRunning()) { + close(); + } +#if JUCE_WINDOWS + cmd = "cmd /c \"" + cmd + "\""; + + SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES), NULL, TRUE }; + + if (!CreatePipe(&hReadPipe, &hWritePipe, &sa, 0)) { + errorExit(TEXT("CreatePipe")); + return; + } + + // Mark the write handle as inheritable + if (!SetHandleInformation(hWritePipe, HANDLE_FLAG_INHERIT, 0)) { + CloseHandle(hReadPipe); + CloseHandle(hWritePipe); + errorExit(TEXT("SetHandleInformation")); + return; + } + + STARTUPINFO si = { 0 }; + + si.cb = sizeof(STARTUPINFO); + si.dwFlags = STARTF_USESTDHANDLES; + si.hStdInput = hReadPipe; // Child process reads from here + si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); // Forward to parent stdout + si.hStdError = GetStdHandle(STD_ERROR_HANDLE); // Forward to parent stderr + + // Create the process + if (!CreateProcess(NULL, const_cast(cmd.toStdString().c_str()), NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) { + CloseHandle(hReadPipe); + CloseHandle(hWritePipe); + errorExit(TEXT("CreateProcess")); + return; + } +#else + process = popen(cmd.toStdString().c_str(), "w"); + if (process == nullptr) { + DBG("popen failed: " + juce::String(std::strerror(errno))); + jassertfalse; + } +#endif + } + + void write(void* data, size_t size) { +#if JUCE_WINDOWS + DWORD bytesWritten; + if (!WriteFile(hWritePipe, data, size, &bytesWritten, NULL)) { + errorExit(TEXT("WriteFile")); + } +#else + fwrite(data, size, 1, process); +#endif + } + + void close() { + if (isRunning()) { +#if JUCE_WINDOWS + // Close the write handle to signal EOF to the process + CloseHandle(hWritePipe); + + // Wait for the process to finish + WaitForSingleObject(pi.hProcess, INFINITE); + + // Clean up + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + CloseHandle(hReadPipe); + + hWritePipe = INVALID_HANDLE_VALUE; + hReadPipe = INVALID_HANDLE_VALUE; +#else + pclose(process); + process = nullptr; +#endif + } + } + + bool isRunning() { +#if JUCE_WINDOWS + return hWritePipe != INVALID_HANDLE_VALUE; +#else + return process != nullptr; +#endif + } + +#if JUCE_WINDOWS + // from https://learn.microsoft.com/en-us/windows/win32/ProcThread/creating-a-child-process-with-redirected-input-and-output + void errorExit(PCTSTR lpszFunction) { + LPVOID lpMsgBuf; + DWORD dw = GetLastError(); + + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + dw, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &lpMsgBuf, + 0, NULL ); + + DBG("Error: " + juce::String((LPCTSTR)lpMsgBuf)); + + LocalFree(lpMsgBuf); + jassertfalse; + } +#endif + + ~WriteProcess() { + close(); + } + +private: +#if JUCE_WINDOWS + HANDLE hReadPipe = INVALID_HANDLE_VALUE; + HANDLE hWritePipe = INVALID_HANDLE_VALUE; + PROCESS_INFORMATION pi; +#else + FILE* process = nullptr; +#endif +}; diff --git a/Source/visualiser/VisualiserComponent.cpp b/Source/visualiser/VisualiserComponent.cpp index 041eb4f..a3fa2c1 100644 --- a/Source/visualiser/VisualiserComponent.cpp +++ b/Source/visualiser/VisualiserComponent.cpp @@ -195,29 +195,13 @@ void VisualiserComponent::setRecording(bool recording) { juce::String resolution = std::to_string(renderTexture.width) + "x" + std::to_string(renderTexture.height); juce::String cmd = "\"" + ffmpegFile.getFullPathName() + "\" -r " + juce::String(FRAME_RATE) + " -f rawvideo -pix_fmt rgba -s " + resolution + " -i - -threads 0 -preset fast -y -pix_fmt yuv420p -crf " + juce::String(21) + " -vf vflip \"" + tempFile.getFile().getFullPathName() + "\""; -#if JUCE_WINDOWS - cmd = "cmd /c \"" + cmd + "\""; -#endif - // open pipe to ffmpeg's stdin in binary write mode -#if JUCE_WINDOWS - ffmpeg = _popen(cmd.toStdString().c_str(), "wb"); -#else - ffmpeg = popen(cmd.toStdString().c_str(), "w"); -#endif - if (ffmpeg == nullptr) { - DBG("popen failed: " + juce::String(std::strerror(errno))); - } + ffmpegProcess.start(cmd); framePixels.resize(renderTexture.width * renderTexture.height * 4); setPaused(false); stopwatch.start(); - } else if (ffmpeg != nullptr) { -#if JUCE_WINDOWS - _pclose(ffmpeg); -#else - pclose(ffmpeg); -#endif - ffmpeg = nullptr; + } else if (ffmpegProcess.isRunning()) { + ffmpegProcess.close(); } setBlockOnAudioThread(recording); numFrames = 0; @@ -378,7 +362,7 @@ void VisualiserComponent::renderOpenGL() { // draw frame to ffmpeg glBindTexture(GL_TEXTURE_2D, renderTexture.id); glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, framePixels.data()); - fwrite(framePixels.data(), 4 * renderTexture.width * renderTexture.height, 1, ffmpeg); + ffmpegProcess.write(framePixels.data(), 4 * renderTexture.width * renderTexture.height); } renderingSemaphore.release(); diff --git a/Source/visualiser/VisualiserComponent.h b/Source/visualiser/VisualiserComponent.h index a81aebb..2fba376 100644 --- a/Source/visualiser/VisualiserComponent.h +++ b/Source/visualiser/VisualiserComponent.h @@ -9,6 +9,7 @@ #include "../components/StopwatchComponent.h" #include "../img/qoixx.hpp" #include "../components/DownloaderComponent.h" +#include "../concurrency/WriteProcess.h" #define FILE_RENDER_DUMMY 0 #define FILE_RENDER_PNG 1 @@ -81,7 +82,7 @@ private: SvgButton record{"Record", BinaryData::record_svg, juce::Colours::red, juce::Colours::red.withAlpha(0.01f)}; long numFrames = 0; std::vector framePixels; - FILE* ffmpeg = nullptr; + WriteProcess ffmpegProcess; juce::File ffmpegFile; juce::String ffmpegURL = juce::String("https://github.com/eugeneware/ffmpeg-static/releases/download/b6.0/") + #if JUCE_WINDOWS diff --git a/osci-render.jucer b/osci-render.jucer index d13a044..d066ddc 100644 --- a/osci-render.jucer +++ b/osci-render.jucer @@ -196,6 +196,7 @@ file="Source/concurrency/BufferConsumer.h"/> + +