ffmpeg_windows.c 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. #include <assert.h>
  2. #include <stdio.h>
  3. #include <stdint.h>
  4. #define WIN32_LEAN_AND_MEAN
  5. #include <windows.h>
  6. typedef struct {
  7. HANDLE hProcess;
  8. // HANDLE hPipeRead;
  9. HANDLE hPipeWrite;
  10. } FFMPEG;
  11. static LPSTR GetLastErrorAsString(void)
  12. {
  13. // https://stackoverflow.com/questions/1387064/how-to-get-the-error-message-from-the-error-code-returned-by-getlasterror
  14. DWORD errorMessageId = GetLastError();
  15. assert(errorMessageId != 0);
  16. LPSTR messageBuffer = NULL;
  17. DWORD size =
  18. FormatMessage(
  19. FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, // DWORD dwFlags,
  20. NULL, // LPCVOID lpSource,
  21. errorMessageId, // DWORD dwMessageId,
  22. MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // DWORD dwLanguageId,
  23. (LPSTR) &messageBuffer, // LPTSTR lpBuffer,
  24. 0, // DWORD nSize,
  25. NULL // va_list *Arguments
  26. );
  27. return messageBuffer;
  28. }
  29. FFMPEG *ffmpeg_start_rendering(size_t width, size_t height, size_t fps)
  30. {
  31. HANDLE pipe_read;
  32. HANDLE pipe_write;
  33. SECURITY_ATTRIBUTES saAttr = {0};
  34. saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
  35. saAttr.bInheritHandle = TRUE;
  36. if (!CreatePipe(&pipe_read, &pipe_write, &saAttr, 0)) {
  37. fprintf(stderr, "ERROR: Could not create pipe: %s\n", GetLastErrorAsString());
  38. return NULL;
  39. }
  40. if (!SetHandleInformation(pipe_write, HANDLE_FLAG_INHERIT, 0)) {
  41. fprintf(stderr, "ERROR: Could not SetHandleInformation: %s\n", GetLastErrorAsString());
  42. return NULL;
  43. }
  44. // https://docs.microsoft.com/en-us/windows/win32/procthread/creating-a-child-process-with-redirected-input-and-output
  45. STARTUPINFO siStartInfo;
  46. ZeroMemory(&siStartInfo, sizeof(siStartInfo));
  47. siStartInfo.cb = sizeof(STARTUPINFO);
  48. // NOTE: theoretically setting NULL to std handles should not be a problem
  49. // https://docs.microsoft.com/en-us/windows/console/getstdhandle?redirectedfrom=MSDN#attachdetach-behavior
  50. siStartInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);
  51. // TODO(#32): check for errors in GetStdHandle
  52. siStartInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
  53. siStartInfo.hStdInput = pipe_read;
  54. siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
  55. PROCESS_INFORMATION piProcInfo;
  56. ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION));
  57. char cmd_buffer[1024*2];
  58. snprintf(cmd_buffer, sizeof(cmd_buffer), "ffmpeg.exe -loglevel verbose -y -f rawvideo -pix_fmt rgba -s %dx%d -r %d -i - -c:v libx264 -vb 2500k -c:a aac -ab 200k -pix_fmt yuv420p output.mp4", width, height, fps);
  59. BOOL bSuccess =
  60. CreateProcess(
  61. NULL,
  62. cmd_buffer,
  63. NULL,
  64. NULL,
  65. TRUE,
  66. 0,
  67. NULL,
  68. NULL,
  69. &siStartInfo,
  70. &piProcInfo
  71. );
  72. if (!bSuccess) {
  73. fprintf(stderr, "ERROR: Could not create child process: %s\n", GetLastErrorAsString());
  74. return NULL;
  75. }
  76. CloseHandle(pipe_read);
  77. CloseHandle(piProcInfo.hThread);
  78. // TODO: use Windows specific allocation stuff?
  79. FFMPEG *ffmpeg = malloc(sizeof(FFMPEG));
  80. assert(ffmpeg != NULL && "Buy MORE RAM lol!!");
  81. ffmpeg->hProcess = piProcInfo.hProcess;
  82. // ffmpeg->hPipeRead = pipe_read;
  83. ffmpeg->hPipeWrite = pipe_write;
  84. return ffmpeg;
  85. }
  86. void ffmpeg_send_frame(FFMPEG *ffmpeg, void *data, size_t width, size_t height)
  87. {
  88. WriteFile(ffmpeg->hPipeWrite, data, sizeof(uint32_t)*width*height, NULL, NULL);
  89. }
  90. void ffmpeg_send_frame_flipped(FFMPEG *ffmpeg, void *data, size_t width, size_t height)
  91. {
  92. for (size_t y = height; y > 0; --y) {
  93. WriteFile(ffmpeg->hPipeWrite, (uint32_t*)data + (y - 1)*width, sizeof(uint32_t)*width, NULL, NULL);
  94. }
  95. }
  96. void ffmpeg_end_rendering(FFMPEG *ffmpeg)
  97. {
  98. FlushFileBuffers(ffmpeg->hPipeWrite);
  99. // FlushFileBuffers(ffmpeg->hPipeRead);
  100. CloseHandle(ffmpeg->hPipeWrite);
  101. // CloseHandle(ffmpeg->hPipeRead);
  102. DWORD result = WaitForSingleObject(
  103. ffmpeg->hProcess, // HANDLE hHandle,
  104. INFINITE // DWORD dwMilliseconds
  105. );
  106. if (result == WAIT_FAILED) {
  107. fprintf(stderr, "ERROR: could not wait on child process: %s\n", GetLastErrorAsString());
  108. return;
  109. }
  110. DWORD exit_status;
  111. if (GetExitCodeProcess(ffmpeg->hProcess, &exit_status) == 0) {
  112. fprintf(stderr, "ERROR: could not get process exit code: %lu\n", GetLastError());
  113. return;
  114. }
  115. if (exit_status != 0) {
  116. fprintf(stderr, "ERROR: command exited with exit code %lu\n", exit_status);
  117. return;
  118. }
  119. CloseHandle(ffmpeg->hProcess);
  120. }