|
|
@@ -0,0 +1,148 @@
|
|
|
+#include <assert.h>
|
|
|
+#include <stdio.h>
|
|
|
+#include <stdint.h>
|
|
|
+#define WIN32_LEAN_AND_MEAN
|
|
|
+#include <windows.h>
|
|
|
+
|
|
|
+typedef struct {
|
|
|
+ HANDLE hProcess;
|
|
|
+ // HANDLE hPipeRead;
|
|
|
+ HANDLE hPipeWrite;
|
|
|
+} FFMPEG;
|
|
|
+
|
|
|
+static LPSTR GetLastErrorAsString(void)
|
|
|
+{
|
|
|
+ // https://stackoverflow.com/questions/1387064/how-to-get-the-error-message-from-the-error-code-returned-by-getlasterror
|
|
|
+
|
|
|
+ DWORD errorMessageId = GetLastError();
|
|
|
+ assert(errorMessageId != 0);
|
|
|
+
|
|
|
+ LPSTR messageBuffer = NULL;
|
|
|
+
|
|
|
+ DWORD size =
|
|
|
+ FormatMessage(
|
|
|
+ FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, // DWORD dwFlags,
|
|
|
+ NULL, // LPCVOID lpSource,
|
|
|
+ errorMessageId, // DWORD dwMessageId,
|
|
|
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // DWORD dwLanguageId,
|
|
|
+ (LPSTR) &messageBuffer, // LPTSTR lpBuffer,
|
|
|
+ 0, // DWORD nSize,
|
|
|
+ NULL // va_list *Arguments
|
|
|
+ );
|
|
|
+
|
|
|
+ return messageBuffer;
|
|
|
+}
|
|
|
+
|
|
|
+FFMPEG *ffmpeg_start_rendering(size_t width, size_t height, size_t fps)
|
|
|
+{
|
|
|
+ HANDLE pipe_read;
|
|
|
+ HANDLE pipe_write;
|
|
|
+
|
|
|
+ SECURITY_ATTRIBUTES saAttr = {0};
|
|
|
+ saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
|
|
|
+ saAttr.bInheritHandle = TRUE;
|
|
|
+
|
|
|
+ if (!CreatePipe(&pipe_read, &pipe_write, &saAttr, 0)) {
|
|
|
+ fprintf(stderr, "ERROR: Could not create pipe: %s\n", GetLastErrorAsString());
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!SetHandleInformation(pipe_write, HANDLE_FLAG_INHERIT, 0)) {
|
|
|
+ fprintf(stderr, "ERROR: Could not SetHandleInformation: %s\n", GetLastErrorAsString());
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+
|
|
|
+ // https://docs.microsoft.com/en-us/windows/win32/procthread/creating-a-child-process-with-redirected-input-and-output
|
|
|
+
|
|
|
+ STARTUPINFO siStartInfo;
|
|
|
+ ZeroMemory(&siStartInfo, sizeof(siStartInfo));
|
|
|
+ siStartInfo.cb = sizeof(STARTUPINFO);
|
|
|
+ // NOTE: theoretically setting NULL to std handles should not be a problem
|
|
|
+ // https://docs.microsoft.com/en-us/windows/console/getstdhandle?redirectedfrom=MSDN#attachdetach-behavior
|
|
|
+ siStartInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);
|
|
|
+ // TODO(#32): check for errors in GetStdHandle
|
|
|
+ siStartInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
|
+ siStartInfo.hStdInput = pipe_read;
|
|
|
+ siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
|
|
|
+
|
|
|
+ PROCESS_INFORMATION piProcInfo;
|
|
|
+ ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION));
|
|
|
+
|
|
|
+ char cmd_buffer[1024*2];
|
|
|
+ snprintf(cmd_buffer, sizeof(cmd_buffer), "ffmpeg.exe -loglevel verbose -y -f rawvideo -pix_fmt rgba -s %dx%d -r %d -t 10 -i - -c:v libx264 -vb 2500k -c:a aac -ab 200k -pix_fmt yuv420p output.mp4", width, height, fps);
|
|
|
+
|
|
|
+ BOOL bSuccess =
|
|
|
+ CreateProcess(
|
|
|
+ NULL,
|
|
|
+ cmd_buffer,
|
|
|
+ NULL,
|
|
|
+ NULL,
|
|
|
+ TRUE,
|
|
|
+ 0,
|
|
|
+ NULL,
|
|
|
+ NULL,
|
|
|
+ &siStartInfo,
|
|
|
+ &piProcInfo
|
|
|
+ );
|
|
|
+
|
|
|
+ if (!bSuccess) {
|
|
|
+ fprintf(stderr, "ERROR: Could not create child process: %s\n", GetLastErrorAsString());
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+
|
|
|
+ CloseHandle(pipe_read);
|
|
|
+ CloseHandle(piProcInfo.hThread);
|
|
|
+
|
|
|
+ // TODO: use Windows specific allocation stuff?
|
|
|
+ FFMPEG *ffmpeg = malloc(sizeof(FFMPEG));
|
|
|
+ assert(ffmpeg != NULL && "Buy MORE RAM lol!!");
|
|
|
+ ffmpeg->hProcess = piProcInfo.hProcess;
|
|
|
+ // ffmpeg->hPipeRead = pipe_read;
|
|
|
+ ffmpeg->hPipeWrite = pipe_write;
|
|
|
+ return ffmpeg;
|
|
|
+}
|
|
|
+
|
|
|
+void ffmpeg_send_frame(FFMPEG *ffmpeg, void *data, size_t width, size_t height)
|
|
|
+{
|
|
|
+ WriteFile(ffmpeg->hPipeWrite, data, sizeof(uint32_t)*width*height, NULL, NULL);
|
|
|
+}
|
|
|
+
|
|
|
+void ffmpeg_send_frame_flipped(FFMPEG *ffmpeg, void *data, size_t width, size_t height)
|
|
|
+{
|
|
|
+ for (size_t y = height; y > 0; --y) {
|
|
|
+ WriteFile(ffmpeg->hPipeWrite, (uint32_t*)data + (y - 1)*width, sizeof(uint32_t)*width, NULL, NULL);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void ffmpeg_end_rendering(FFMPEG *ffmpeg)
|
|
|
+{
|
|
|
+ FlushFileBuffers(ffmpeg->hPipeWrite);
|
|
|
+ // FlushFileBuffers(ffmpeg->hPipeRead);
|
|
|
+
|
|
|
+ CloseHandle(ffmpeg->hPipeWrite);
|
|
|
+ // CloseHandle(ffmpeg->hPipeRead);
|
|
|
+
|
|
|
+ DWORD result = WaitForSingleObject(
|
|
|
+ ffmpeg->hProcess, // HANDLE hHandle,
|
|
|
+ INFINITE // DWORD dwMilliseconds
|
|
|
+ );
|
|
|
+
|
|
|
+ if (result == WAIT_FAILED) {
|
|
|
+ fprintf(stderr, "ERROR: could not wait on child process: %s\n", GetLastErrorAsString());
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ DWORD exit_status;
|
|
|
+ if (GetExitCodeProcess(ffmpeg->hProcess, &exit_status) == 0) {
|
|
|
+ fprintf(stderr, "ERROR: could not get process exit code: %lu\n", GetLastError());
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (exit_status != 0) {
|
|
|
+ fprintf(stderr, "ERROR: command exited with exit code %lu\n", exit_status);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ CloseHandle(ffmpeg->hProcess);
|
|
|
+}
|
|
|
+
|