diff options
-rw-r--r-- | 3p/ms/getopt.c | 51 | ||||
-rw-r--r-- | 3p/ms/getopt.h | 36 | ||||
-rw-r--r-- | Makefile | 6 | ||||
-rw-r--r-- | README | 9 | ||||
-rw-r--r-- | api.c | 32 | ||||
-rw-r--r-- | api.h | 96 | ||||
-rw-r--r-- | main.c | 81 | ||||
-rw-r--r-- | os.h | 2 | ||||
-rw-r--r-- | render.c | 199 | ||||
-rw-r--r-- | render.h | 37 |
10 files changed, 509 insertions, 40 deletions
diff --git a/3p/ms/getopt.c b/3p/ms/getopt.c new file mode 100644 index 0000000..307baf2 --- /dev/null +++ b/3p/ms/getopt.c @@ -0,0 +1,51 @@ +/* ***************************************************************** +* +* Copyright 2016 Microsoft +* +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +******************************************************************/ + +#include "getopt.h" +#include <windows.h> + +char* optarg = NULL; +int optind = 1; + +int getopt(int argc, char *const argv[], const char *optstring) +{ + if ((optind >= argc) || (argv[optind][0] != '-') || (argv[optind][0] == 0)) + { + return -1; + } + + int opt = argv[optind][1]; + const char *p = strchr(optstring, opt); + + if (p == NULL) + { + return '?'; + } + if (p[1] == ':') + { + optind++; + if (optind >= argc) + { + return '?'; + } + optarg = argv[optind]; + optind++; + } + return opt; +} diff --git a/3p/ms/getopt.h b/3p/ms/getopt.h new file mode 100644 index 0000000..33de8ad --- /dev/null +++ b/3p/ms/getopt.h @@ -0,0 +1,36 @@ +/* ***************************************************************** +* +* Copyright 2016 Microsoft +* +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +******************************************************************/ + +#ifndef GETOPT_H__ +#define GETOPT_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +extern char *optarg; +extern int optind; + +int getopt(int argc, char *const argv[], const char *optstring); + +#ifdef __cplusplus +} +#endif + +#endif @@ -1,8 +1,10 @@ CC = clang -OBJS = main.o api.o 3p/sst/x86.o hook.o WARNINGS=-Wall -Wpedantic -Wextra -Wno-gnu-zero-variadic-macro-arguments \ -D_CRT_SECURE_NO_WARNINGS -CFLAGS:=$(CFLAGS) -m32 -flto -I3p -std=c23 +override CFLAGS+=-m32 -flto -I3p -std=c23 +override LDFLAGS+=-lmf -lmfplat -lmfuuid -lmfreadwrite -lstrmiids + +OBJS = main.o api.o 3p/sst/x86.o 3p/ms/getopt.o hook.o render.o all: rt.exe rt.exe: $(OBJS) @@ -4,4 +4,11 @@ rendertools orange box engine demo renderer usage: - rt -w <width> -h <height> -g <game> -r <fps> path/to/demo.dem + rt -w <width> -h <height> -g <game> -r <fps> [-1] [-q <quality>] [-b <bitrate>] + path/to/output.mp4 path/to/demo1.dem [path/to/demo2.dem...] + +-1 flag combines multiple renders into 1 file + +bitrate is in kbps, quality 1-100 +cannot specify quality and bitrate at the same time +demo path is relative to the gamedir (e.g. hl2) @@ -74,7 +74,9 @@ bool api_init(void) { // find videomode // CEngineAPI::SetEngineWindow calls videomode->SetGameWindow after // detaching the current window with another vcall. Look for the second one. - // This, like the demoplayer, is a pointer. Mind the double dereference. + // This, like the demoplayer, is a pointer. We need to keep the double + // pointer because when we get this, it is null. It is set by the engine + // later. instr = (const u8 *)engineapi->vt->set_engine_window; int mov_ecx_counter = 0; for (const u8 *p = instr; p - instr < 64;) { @@ -89,6 +91,34 @@ bool api_init(void) { p += l; } debug("videomode = %p", (void *)videomode); + + // find cl_movieinfo + { + // step 1: find CL_IsRecordingMovie() in + // CEngineTools::StartMovieRecording + instr = (const u8 *)enginetools->vt->start_movie_recording; + void *cl_isrecordingmovie = NULL; + for (const u8 *p = instr; p - instr < 64;) { + // this is the first call in the function + if (*p == X86_CALL) { + cl_isrecordingmovie = (void *)(p + 5 + *(i32 *)(p + 1)); + } + int l = x86_len(p); + if (l == -1) + bail("invalid instruction looking for CL_IsRecordingMovie"); + p += l; + } + if (!cl_isrecordingmovie) + bail("couldn't find cbuf_addtext CL_IsRecordingMovie"); + + // step 2: get the pointer to cl_movieinfo + // should be in the first instruction + instr = (const u8 *)cl_isrecordingmovie; + if (instr[0] != X86_ALUMI8 || instr[1] != X86_MODRM(0, 7, 5)) + bail("couldn't get movieinfo"); + movieinfo = *(struct movieinfo **)(instr + 2); + debug("movieinfo = %p", (void *)movieinfo); + } return true; } @@ -23,20 +23,21 @@ #define VENGINE_TOOL_INTERFACE_VERSION "VENGINETOOL003" #include "intdef.h" +#include "os.h" typedef void * (*createinterface_func)(const char *name, int *ret); struct engserver { struct { usize _pad[36]; - void (*__thiscall server_command)(struct engserver *this, const char *str); + void (*VIRTUAL server_command)(struct engserver *this, const char *str); } *vt; }; struct engclient { struct { usize _pad[75]; - void (*__thiscall is_playing_demo)(struct engclient *this); + void (*VIRTUAL is_playing_demo)(struct engclient *this); } *vt; }; @@ -49,50 +50,85 @@ struct engineapi { struct enginetools { struct { - usize _pad[64]; - void *is_recording_movie; + usize _pad[65]; + void *start_movie_recording; } *vt; }; struct movieinfo { - char name[256]; - int curframe; - int type; - int jpeg_quality; + char name[256]; + int curframe; + int type; + int jpeg_quality; +}; + +// Almost no chance I need all of these, but why not. +enum image_format { + IMAGE_FORMAT_UNKNOWN = -1, + IMAGE_FORMAT_RGBA8888 = 0, + IMAGE_FORMAT_ABGR8888, + IMAGE_FORMAT_RGB888, + IMAGE_FORMAT_BGR888, + IMAGE_FORMAT_RGB565, + IMAGE_FORMAT_I8, + IMAGE_FORMAT_IA88, + IMAGE_FORMAT_P8, + IMAGE_FORMAT_A8, + IMAGE_FORMAT_RGB888_BLUESCREEN, + IMAGE_FORMAT_BGR888_BLUESCREEN, + IMAGE_FORMAT_ARGB8888, + IMAGE_FORMAT_BGRA8888, + IMAGE_FORMAT_DXT1, + IMAGE_FORMAT_DXT3, + IMAGE_FORMAT_DXT5, + IMAGE_FORMAT_BGRX8888, + IMAGE_FORMAT_BGR565, + IMAGE_FORMAT_BGRX5551, + IMAGE_FORMAT_BGRA4444, + IMAGE_FORMAT_DXT1_ONEBITALPHA, + IMAGE_FORMAT_BGRA5551, + IMAGE_FORMAT_UV88, + IMAGE_FORMAT_UVWQ8888, + IMAGE_FORMAT_RGBA16161616F, + IMAGE_FORMAT_RGBA16161616, + IMAGE_FORMAT_UVLX8888, + IMAGE_FORMAT_R32F, // Single-channel 32-bit floating point + IMAGE_FORMAT_RGB323232F, + IMAGE_FORMAT_RGBA32323232F, + // vendor crap + IMAGE_FORMAT_NV_DST16, + IMAGE_FORMAT_NV_DST24, + IMAGE_FORMAT_NV_INTZ, + IMAGE_FORMAT_NV_RAWZ, + IMAGE_FORMAT_ATI_DST16, + IMAGE_FORMAT_ATI_DST24, + IMAGE_FORMAT_NV_NULL, + IMAGE_FORMAT_ATI2N, + IMAGE_FORMAT_ATI1N, + NUM_IMAGE_FORMATS }; struct videomode { struct { usize _pad[22]; - void (*__thiscall write_movie_frame)(struct videomode *this, + void (*VIRTUAL write_movie_frame)(struct videomode *this, struct movieinfo *info); + usize _pad2[4]; + void (*VIRTUAL read_screen_pixels)(struct videomode *this, + int x, int y, int w, int h, void *buf, enum image_format fmt); } *vt; }; struct demoplayer { struct { - void *_destructor; - void * (*__thiscall get_demo_file)(struct demoplayer *this); - int (*__thiscall get_playback_tick)(struct demoplayer *this); - int (*__thiscall get_total_ticks)(struct demoplayer *this); - void *_whatisthisihavenoidea; // TODO - bool (*__thiscall start_playback)(struct demoplayer *this, + usize _pad[5]; + bool (*VIRTUAL start_playback)(struct demoplayer *this, const char *filename, bool as_time_demo); - bool (*__thiscall is_playing_back)(struct demoplayer *this); - bool (*__thiscall is_playback_paused)(struct demoplayer *this); - bool (*__thiscall is_playing_time_demo)(struct demoplayer *this); - bool (*__thiscall is_skipping)(struct demoplayer *this); - bool (*__thiscall can_skip_backwards)(struct demoplayer *this); - void (*__thiscall set_playback_time_scale)(struct demoplayer *this, - float timescale); - float (*__thiscall get_playback_time_scale)(struct demoplayer *this); - void (*__thiscall pause_playback)(struct demoplayer *this, - float seconds); - void (*__thiscall skip_to_tick)(struct demoplayer *this, int tick, - bool relative, bool pause); - void (*__thiscall resume_playback)(struct demoplayer *this); - void (*__thiscall stop_playback)(struct demoplayer *this); - void (*__thiscall interpolate_viewpoint)(struct demoplayer *this); + usize _pad2[11]; + void (*VIRTUAL stop_playback)(struct demoplayer *this); + // TODO: Disable interp when in a portal bubble. This will be very + // difficult! + void (*VIRTUAL interpolate_viewpoint)(struct demoplayer *this); } *vt; }; @@ -17,21 +17,42 @@ #include "api.h" #include "hook.h" #include "log.h" +#include "render.h" #include "os.h" +#include "ms/getopt.h" + #include <stddef.h> #define WIN32_LEAN_AND_MEAN #include <Windows.h> +struct { + int width; + int height; + char *game; + int fps; + int quality; + int bitrate; + char *out; + char **demo; + bool combine; +} args = {0}; + void (*orig_cbuf_addtext)(char *); void hook_cbuf_addtext(char *str) { orig_cbuf_addtext(str); // this is the last thing that happens when the game is opened if (!strcmp(str, "exec modsettings.cfg mod\n")) { + bool use_bitrate = !!args.bitrate; + if (!render_init(args.width, args.height, args.fps, use_bitrate, + use_bitrate ? args.bitrate : args.quality, args.out)) + die("couldn't init render"); + // play the demo + demoplayer->vt->start_playback(demoplayer, args.demo[0], false); } } -char *cmdline; +char cmdline[128]; char WINAPI *hook_GetCommandLineA(void) { return cmdline; } @@ -59,19 +80,67 @@ void WINAPI *hook_LoadLibraryExA(const char *filename, void *hfile, int flags) { typedef int (*LauncherMain_t)(void *instance, void *prev_inst, char *cmdline, int cmd_show); -int main(void/* int argc, char **argv */) { +int main(int argc, char **argv) { SetDllDirectoryA("bin/"); + int c; + char *strend; + while ((c = getopt(argc, argv, "w:h:g:r:q:b:")) != -1) { + switch (c) { + case 'w': + args.width = strtol(optarg, &strend, 10); + if (strend == optarg) die("width must be a number!"); + break; + case 'h': + args.height = strtol(optarg, &strend, 10); + if (strend == optarg) die("height must be a number"); + break; + case 'r': + args.fps = strtol(optarg, &strend, 10); + if (strend == optarg) die("must pass a number to -w!"); + break; + case 'q': + args.quality = strtol(optarg, &strend, 10); + if (strend == optarg) die("must pass a number to -w!"); + break; + case 'b': + args.bitrate = strtol(optarg, &strend, 10); + if (strend == optarg) die("must pass a number to -w!"); + break; + case 'g': + args.game = optarg; + case '1': + args.combine = true; + case '?': + break; + } + } + + if (!args.width) args.width = 1280; + if (!args.height) args.height = 720; + if (!args.game) args.game = "hl2"; + if (!args.fps) args.fps = 30; + if (!args.quality) args.quality = 75; - // TODO: make this changeable by the user - cmdline = "hl2.exe -console -w 1280 -h 720 -window -high -dxlevel 95"; + if (argc - optind < 2) { + printf( + "usage:\n" + " rt -w <width> -h <height> -g <game> -r <fps> [-q <quality>] [-b <bitrate>]\n" + " path/to/video.mp4 path/to/demo1.dem...\n"); + exit(1); + } + args.out = argv[optind++]; + args.demo = argv + optind; + + sprintf(cmdline, "hl2.exe -game %s -w %d -h %d -window -console", + args.game, args.width, args.height); + + info("cmdline = %s", cmdline); hook_init(); orig_LoadLibraryExA = (typeof(orig_LoadLibraryExA))hook_dllapi("kernel32", "LoadLibraryExA", (void *)hook_LoadLibraryExA); hook_dllapi("kernel32", "GetCommandLineA", (void *)hook_GetCommandLineA); - info("GetCommandLineA() = %s", GetCommandLineA()); - void *launcher_dll = os_dlopen("launcher"); LauncherMain_t launcher_main = (LauncherMain_t)os_dlsym(launcher_dll, "LauncherMain"); @@ -33,6 +33,8 @@ inline bool os_mprot(void *mem, int len, int mode) { return !!VirtualProtect(mem, len, mode, &old); } +#define VIRTUAL __thiscall + #endif // vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/render.c b/render.c new file mode 100644 index 0000000..495a918 --- /dev/null +++ b/render.c @@ -0,0 +1,199 @@ +/* + * Copyright © 2024 Matthew Wozniak <me@woz.blue> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#include "api.h" +#include "log.h" +#include "render.h" +#include "os.h" + +#include <codecapi.h> +#include <mfapi.h> +#include <mfobjects.h> +#include <mfreadwrite.h> +#include <stdlib.h> +#include <string.h> +#include <strmif.h> +#include <winerror.h> + +static struct renderctx ctx = { 0 }; +static bool is_rendering = false; + +#define HR(expr) { \ + HRESULT hr = expr; \ + if (!SUCCEEDED(hr)) \ + bail(#expr " failed!!! (code %lu)", hr); \ +} + +bool do_frame(struct videomode *this, struct movieinfo *info) { + is_rendering = true; + IMFSample *sample = NULL; + IMFMediaBuffer *imf_buffer = NULL; + int szpx = 4; + u32 *data = NULL; + + HR(MFCreateMemoryBuffer(ctx.width * ctx.height * szpx, &imf_buffer)); + HR(imf_buffer->lpVtbl->Lock(imf_buffer, (u8 **)&data, NULL, NULL)); + // THIS IS SLOW!! + u32 *raw = malloc(ctx.width * ctx.height * szpx); + this->vt->read_screen_pixels(this, 0, 0, ctx.width, ctx.height, raw, IMAGE_FORMAT_BGRA8888); + for (int y = ctx.height - 1; y > 0; y--) { + memcpy(data + ((ctx.height - y) * ctx.width), raw + y * ctx.width, + szpx * ctx.width); + /* for (int x = 0; x < ctx.width; x++) { + data[(ctx.height - y) * ctx.width + x] = raw[y * ctx.width + x]; + } */ + } + free(raw); + HR(imf_buffer->lpVtbl->Unlock(imf_buffer)); + HR(imf_buffer->lpVtbl->SetCurrentLength(imf_buffer, ctx.width * ctx.height * szpx)); + + HR(MFCreateSample(&sample)); + HR(sample->lpVtbl->AddBuffer(sample, imf_buffer)); + u64 sample_duration = (u64)((1.0 / (double)ctx.fps) * 10000000.0); + HR(sample->lpVtbl->SetSampleDuration(sample, sample_duration)); + u64 sample_time = sample_duration * info->curframe; + HR(sample->lpVtbl->SetSampleTime(sample, sample_time)); + HR(ctx.sink_writer->lpVtbl->WriteSample(ctx.sink_writer, ctx.stream_index, sample)); + + sample->lpVtbl->Release(sample); + imf_buffer->lpVtbl->Release(imf_buffer); + return true; +} + +typeof((*videomode)->vt->write_movie_frame) orig_write_movie_frame; +void VIRTUAL hook_write_movie_frame(struct videomode *this, + struct movieinfo *info) { + if (!do_frame(this, info)) + die("oopsie!"); +} + + +bool do_stop() { + HR(ctx.sink_writer->lpVtbl->Flush(ctx.sink_writer, ctx.stream_index)); + HR(ctx.sink_writer->lpVtbl->Finalize(ctx.sink_writer)); + HR(MFShutdown()); + return true; +} + +static void (VIRTUAL *orig_stop_playback)(struct demoplayer *); +void VIRTUAL hook_stop_playback(struct demoplayer *this) { + orig_stop_playback(this); + + if (!is_rendering) return; + is_rendering = false; + if (!do_stop()) + die("oopsie!"); + // TODO: IPC mode so we don't have to restart the game for every demo + exit(0); +} + +bool render_init(int width, int height, int framerate, bool use_bitrate, + int quality, const char *output_file) { + bool r = + os_mprot((*videomode)->vt, 28 * sizeof(void *), PAGE_EXECUTE_READWRITE); + if (!r) bail("couldn't mprotect videomode vtable"); + orig_write_movie_frame = (*videomode)->vt->write_movie_frame; + (*videomode)->vt->write_movie_frame = hook_write_movie_frame; + + r = os_mprot(demoplayer->vt, 18 * sizeof(void *), PAGE_EXECUTE_READWRITE); + if (!r) bail("couldn't mprotect demoplayer vtable"); + orig_stop_playback = demoplayer->vt->stop_playback; + demoplayer->vt->stop_playback = hook_stop_playback; + + // make the game thing we are recording a movie (we are!) + memcpy(movieinfo->name, "a", 2); + movieinfo->type = 0; + + char framerate_cmd[32] = { 0 }; + sprintf(framerate_cmd, "host_framerate %d;", framerate); + cbuf_addtext(framerate_cmd); + + ctx.fps = framerate; + ctx.width = width; + ctx.height = height; + ctx.bit_rate = use_bitrate ? quality : 99999999; + + MFStartup(MF_VERSION, 0); + + // init sinkwriter + { + IMFMediaType *mt_out = NULL; + IMFMediaType *mt_in = NULL; + + u16 path[MAX_PATH] = {0}; + info("strlen = %d", strlen(output_file)); + mbstowcs(path, output_file, strlen(output_file)); + + info("rendering to %S", path); + + HR(MFCreateSinkWriterFromURL(path, NULL, NULL, &ctx.sink_writer)); + HR(MFCreateMediaType(&mt_out)); + HR(mt_out->lpVtbl->SetGUID(mt_out, &MF_MT_MAJOR_TYPE, &MFMediaType_Video)); + HR(mt_out->lpVtbl->SetGUID(mt_out, &MF_MT_SUBTYPE, &MFVideoFormat_H264)); + HR(mt_out->lpVtbl->SetUINT32(mt_out, &MF_MT_AVG_BITRATE, ctx.bit_rate)); + HR(mt_out->lpVtbl->SetUINT32(mt_out, &MF_MT_MPEG2_PROFILE , eAVEncH264VProfile_Main)); + HR(mt_out->lpVtbl->SetUINT32(mt_out, &MF_MT_INTERLACE_MODE, MFVideoInterlace_Progressive)); + HR(mt_out->lpVtbl->SetUINT64(mt_out, &MF_MT_FRAME_SIZE, ((u64)ctx.width << 32) | ctx.height)); + HR(mt_out->lpVtbl->SetUINT64(mt_out, &MF_MT_FRAME_RATE, ((u64)ctx.fps << 32) | 1)); + HR(mt_out->lpVtbl->SetUINT64(mt_out, &MF_MT_PIXEL_ASPECT_RATIO, (((u64)1 << 32) | 1))); + HR(ctx.sink_writer->lpVtbl->AddStream(ctx.sink_writer, mt_out, &ctx.stream_index)); + + HR(MFCreateMediaType(&mt_in)); + HR(mt_in->lpVtbl->SetGUID(mt_in, &MF_MT_MAJOR_TYPE, &MFMediaType_Video)); + HR(mt_in->lpVtbl->SetGUID(mt_in, &MF_MT_SUBTYPE, &MFVideoFormat_ARGB32)); + HR(mt_in->lpVtbl->SetUINT32(mt_in, &MF_MT_INTERLACE_MODE, MFVideoInterlace_Progressive)); + HR(mt_in->lpVtbl->SetUINT64(mt_in, &MF_MT_FRAME_SIZE, ((u64)ctx.width << 32) | ctx.height)); + HR(mt_in->lpVtbl->SetUINT64(mt_in, &MF_MT_FRAME_RATE, ((u64)ctx.fps << 32) | 1)); + HR(mt_in->lpVtbl->SetUINT64(mt_in, &MF_MT_PIXEL_ASPECT_RATIO, (((u64)1 << 32) | 1))); + HR(ctx.sink_writer->lpVtbl->SetInputMediaType(ctx.sink_writer, ctx.stream_index, mt_in, NULL)); + + HR(ctx.sink_writer->lpVtbl->BeginWriting(ctx.sink_writer)); + HR(ctx.sink_writer->lpVtbl->AddRef(ctx.sink_writer)); + } + + // video encoding parameters + { + ICodecAPI *codec; + HR(ctx.sink_writer->lpVtbl->GetServiceForStream(ctx.sink_writer, 0, &GUID_NULL, &IID_ICodecAPI, (void*)&codec)); + + // use a quality VBR + int ratecontrol = use_bitrate ? eAVEncCommonRateControlMode_UnconstrainedVBR : eAVEncCommonRateControlMode_Quality; + VARIANT _ratecontrol = { .vt = VT_UI4, .ulVal = ratecontrol }; + HR(codec->lpVtbl->SetValue(codec, &CODECAPI_AVEncCommonRateControlMode, &_ratecontrol)); + + // set the quality + VARIANT _quality = { .vt = VT_UI4, .ulVal = quality }; + HR(codec->lpVtbl->SetValue(codec, &CODECAPI_AVEncCommonQuality, &_quality)); + + // even at 100 it is still very quick, really no reason to change it + VARIANT qvs = { .vt = VT_UI4, .ulVal = 100 }; + HR(codec->lpVtbl->SetValue(codec, &CODECAPI_AVEncCommonQualityVsSpeed, &qvs)); + + // set the gop size to 2 seconds + VARIANT gop = { .vt = VT_UI4, .ulVal = 2 * ctx.fps }; + HR(codec->lpVtbl->SetValue(codec, &CODECAPI_AVEncMPVGOPSize, &gop)); + + // enable 2 b-frames + VARIANT bframes = { .vt = VT_UI4, .ulVal = 2 }; + HR(codec->lpVtbl->SetValue(codec, &CODECAPI_AVEncMPVDefaultBPictureCount, &bframes)); + + codec->lpVtbl->Release(codec); + } + + return true; +} + +// vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/render.h b/render.h new file mode 100644 index 0000000..bec043b --- /dev/null +++ b/render.h @@ -0,0 +1,37 @@ +/* + * Copyright © 2024 Matthew Wozniak <me@woz.blue> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#include "intdef.h" + +#include <mfapi.h> +#include <mfidl.h> +#include <mfreadwrite.h> + +bool render_init(int width, int height, int framerate, bool use_bitrate, + int quality, const char *output_file); + +struct renderctx { + int width; + int height; + int fps; + int bit_rate; + GUID enc_format; + GUID in_fmt; + IMFSinkWriter *sink_writer; + ulong stream_index; +}; + +// vi: sw=4 ts=4 noet tw=80 cc=80 |