Skip to content

Commit 5d2a893

Browse files
committed
feat: WIP; add windows ffmpeg / video backend implementation
1 parent f43086f commit 5d2a893

File tree

1 file changed

+133
-0
lines changed

1 file changed

+133
-0
lines changed
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
2+
#include "video_renderer.hpp"
3+
4+
#define WIN32_LEAN_AND_MEAN
5+
#define NOMINMAX
6+
7+
#include <windows.h>
8+
9+
10+
struct Decoder {
11+
HANDLE hProcess;
12+
HANDLE hPipeWrite;
13+
};
14+
15+
// inspired by: https://github.com/tsoding/musializer/blob/762a729ff69ba1f984b0f2604e0eac08af46327c/src/ffmpeg_windows.c
16+
VideoRendererBackend::VideoRendererBackend(const std::filesystem::path& destination_path)
17+
: m_destination_path{ destination_path },
18+
m_decoder{ nullptr } { }
19+
20+
VideoRendererBackend::~VideoRendererBackend() = default;
21+
22+
23+
std::optional<std::string> VideoRendererBackend::setup(u32 fps, shapes::UPoint size) {
24+
25+
HANDLE pipe_read;
26+
HANDLE pipe_write;
27+
28+
SECURITY_ATTRIBUTES saAttr = { 0 };
29+
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
30+
saAttr.bInheritHandle = TRUE;
31+
32+
if (!CreatePipe(&pipe_read, &pipe_write, &saAttr, 0)) {
33+
return fmt::format("FFMPEG: Could not create pipe. System Error Code: {}", GetLastError());
34+
}
35+
36+
if (!SetHandleInformation(pipe_write, HANDLE_FLAG_INHERIT, 0)) {
37+
return fmt::format(
38+
"FFMPEG: Could not mark write pipe as non-inheritable. System Error Code: {}", GetLastError()
39+
);
40+
}
41+
42+
// https://docs.microsoft.com/en-us/windows/win32/procthread/creating-a-child-process-with-redirected-input-and-output
43+
44+
STARTUPINFO siStartInfo;
45+
ZeroMemory(&siStartInfo, sizeof(siStartInfo));
46+
siStartInfo.cb = sizeof(STARTUPINFO);
47+
// NOTE: theoretically setting NULL to std handles should not be a problem
48+
// https://docs.microsoft.com/en-us/windows/console/getstdhandle?redirectedfrom=MSDN#attachdetach-behavior
49+
siStartInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);
50+
if (siStartInfo.hStdError == INVALID_HANDLE_VALUE) {
51+
return fmt::format(
52+
"FFMPEG: Could get standard error handle for the child. System Error Code: {}", GetLastError()
53+
);
54+
}
55+
siStartInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
56+
if (siStartInfo.hStdOutput == INVALID_HANDLE_VALUE) {
57+
return fmt::format(
58+
"FFMPEG: Could get standard output handle for the child. System Error Code: {}", GetLastError()
59+
);
60+
}
61+
siStartInfo.hStdInput = pipe_read;
62+
siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
63+
64+
PROCESS_INFORMATION piProcInfo;
65+
ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION));
66+
67+
//TODO: do this in better c++ fashion and deduplicate this
68+
char cmd_buffer[1024 * 2];
69+
snprintf(
70+
cmd_buffer, sizeof(cmd_buffer),
71+
"ffmpeg.exe -loglevel verbose -y -f rawvideo -pix_fmt rgba -s %dx%d -r %d -i - -i \"%s\" -c:v libx264 -vb "
72+
"2500k -c:a aac -ab 200k -pix_fmt yuv420p output.mp4",
73+
(int) width, (int) height, (int) fps, sound_file_path
74+
);
75+
76+
if (!CreateProcess(NULL, cmd_buffer, NULL, NULL, TRUE, 0, NULL, NULL, &siStartInfo, &piProcInfo)) {
77+
CloseHandle(pipe_write);
78+
CloseHandle(pipe_read);
79+
80+
return fmt::format("FFMPEG: Could not create child process. System Error Code: {}", GetLastError());
81+
}
82+
83+
CloseHandle(pipe_read);
84+
CloseHandle(piProcInfo.hThread);
85+
86+
m_decoder = std::make_unique<Decoder>(piProcInfo.hProcess, pipe_write);
87+
return std::nullopt;
88+
}
89+
90+
bool VideoRendererBackend::add_frame(SDL_Surface* surface) {
91+
DWORD written{};
92+
93+
if (not WriteFile(
94+
m_decoder->hPipeWrite, surface->pixels, static_cast<size_t>(surface->h) * surface->pitch, &written, NULL
95+
)) {
96+
spdlog::error("FFMPEG: failed to write into ffmpeg pipe. System Error Code: {}", GetLastError());
97+
return false;
98+
}
99+
return true;
100+
}
101+
102+
bool VideoRendererBackend::finish(bool cancel) {
103+
104+
FlushFileBuffers(m_decoder->hPipeWrite);
105+
CloseHandle(m_decoder->hPipeWrite);
106+
107+
if (cancel) {
108+
TerminateProcess(m_decoder->hProcess, 1);
109+
}
110+
111+
if (WaitForSingleObject(m_decoder->hProcess, INFINITE) == WAIT_FAILED) {
112+
spdlog::error("FFMPEG: could not wait on child process. System Error Code: {}", GetLastError());
113+
CloseHandle(m_decoder->hProcess);
114+
return false;
115+
}
116+
117+
DWORD exit_status{};
118+
if (GetExitCodeProcess(m_decoder->hProcess, &exit_status) == 0) {
119+
spdlog::error("FFMPEG: could not get process exit code. System Error Code: {}", GetLastError());
120+
CloseHandle(m_decoder->hProcess);
121+
return false;
122+
}
123+
124+
if (exit_status != 0) {
125+
spdlog::error("FFMPEG: command exited with exit code {}", exit_status);
126+
CloseHandle(m_decoder->hProcess);
127+
return false;
128+
}
129+
130+
CloseHandle(m_decoder->hProcess);
131+
132+
return true;
133+
}

0 commit comments

Comments
 (0)