diff options
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | README | 4 | ||||
-rw-r--r-- | api.c | 140 | ||||
-rw-r--r-- | api.h | 55 | ||||
-rw-r--r-- | log.h | 3 | ||||
-rw-r--r-- | main.c | 15 | ||||
-rw-r--r-- | render.c | 147 | ||||
-rw-r--r-- | render.h | 20 |
8 files changed, 281 insertions, 105 deletions
@@ -2,7 +2,7 @@ CC = clang WARNINGS=-Wall -Wpedantic -Wextra -Wno-gnu-zero-variadic-macro-arguments \ -D_CRT_SECURE_NO_WARNINGS override CFLAGS+=-m32 -flto -I3p -std=c23 -override LDFLAGS+=-lmf -lmfplat -lmfuuid -lmfreadwrite -lstrmiids +override LDFLAGS+=-lmf -lmfplat -lole32 -lmfuuid -lmfreadwrite -lstrmiids OBJS = main.o api.o 3p/sst/x86.o hook.o render.o @@ -4,11 +4,9 @@ rendertools orange box engine demo renderer usage: - rt [-w <width>] [-h <height>] [-g <game>] [-r <fps>] [-s <qvs>] [-1] [-q <quality>] OR [-b <bitrate>] + rt [-w <width>] [-h <height>] [-g <game>] [-r <fps>] [-s <qvs>] [-q <quality>] OR [-b <bitrate>] path/to/video.mp4 path/to/demo1.dem... --1 flag combines multiple renders into 1 file (UNIMPLEMENTED) - 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) @@ -21,14 +21,28 @@ #include "log.h" #include "sst/x86.h" -struct engserver *engserver = NULL; -struct engclient *engclient = NULL; -struct engineapi *engineapi = NULL; -struct enginetools *enginetools = NULL; -struct demoplayer *demoplayer = NULL; -struct videomode **videomode = NULL; -struct movieinfo *movieinfo = NULL; -void (*cbuf_addtext)(char *) = NULL; +struct engserver *engserver = 0; +struct engclient *engclient = 0; +struct engineapi *engineapi = 0; +struct enginetools *enginetools = 0; +struct cvar *cvar = 0; +struct demoplayer *demoplayer = 0; +struct videomode **videomode = 0; +struct movieinfo *movieinfo = 0; +struct audio_device **audio_device = 0; +struct soundstate *snd = 0; +void (*snd_recordbuffer)(void) = 0; +void (*cbuf_addtext)(char *) = 0; + +// This macro is Copyright 2024 Michael Smith under the same license as above. +#define NEXT_INSN(p, tgt) do { \ + int _len = x86_len(p); \ + if (_len == -1) { \ + bail("unknown or invalid instruction looking for %s", tgt); \ + return false; \ + } \ + (p) += _len; \ +} while (0) // TODO: should this be split up? @@ -36,15 +50,22 @@ bool api_init(void) { void *engine_dll = os_dlhandle("engine"); createinterface_func engine_factory = (createinterface_func)os_dlsym(engine_dll, "CreateInterface"); + + void *vstdlib_dll = os_dlhandle("vstdlib"); + createinterface_func vstdlib_factory = + (createinterface_func)os_dlsym(vstdlib_dll, "CreateInterface"); + if (!engine_factory) bail("couldn't get engine factory"); - engserver = engine_factory(VENGINE_SERVER_INTERFACE_VERSION, NULL); + engserver = engine_factory(VENGINE_SERVER_INTERFACE_VERSION, 0); if (!engserver) bail("couldn't get IVEngineServer from engine"); - engclient = engine_factory(VENGINE_CLIENT_INTERFACE_VERSION, NULL); + engclient = engine_factory(VENGINE_CLIENT_INTERFACE_VERSION, 0); if (!engclient) bail("couldn't get IVEngineClient from engine"); - engineapi = engine_factory(VENGINE_LAUNCHER_INTERFACE_VERSION, NULL); + engineapi = engine_factory(VENGINE_LAUNCHER_INTERFACE_VERSION, 0); if (!engineapi) bail("couldn't get IEngineAPI from engine"); - enginetools = engine_factory(VENGINE_TOOL_INTERFACE_VERSION, NULL); + enginetools = engine_factory(VENGINE_TOOL_INTERFACE_VERSION, 0); if (!engineapi) bail("couldn't get IEngineTools from engine"); + cvar = vstdlib_factory(VENGINE_CVAR_INTERFACE_VERSION, 0); + if (!cvar) bail("couldn't get ICVar from vstdlib"); // find cbuf_addtext const u8 *instr = (const u8 *)engserver->vt->server_command; @@ -55,10 +76,7 @@ bool api_init(void) { // jump is relative to after the instruction cbuf_addtext = (void (*)(char *))(p + 5 + *(i32 *)(p + 1)); } - int l = x86_len(p); - if (l == -1) - bail("invalid instruction looking for cbuf_addtext"); - p += l; + NEXT_INSN(p, "CBuf_AddText"); } if (!cbuf_addtext) bail("couldn't find cbuf_addtext"); @@ -85,10 +103,7 @@ bool api_init(void) { videomode = *(struct videomode ***)(p + 2); break; } - int l = x86_len(p); - if (l == -1) - bail("invalid instruction looking for videomode"); - p += l; + NEXT_INSN(p, "videomode"); } debug("videomode = %p", (void *)videomode); @@ -97,16 +112,13 @@ bool api_init(void) { // step 1: find CL_IsRecordingMovie() in // CEngineTools::StartMovieRecording instr = (const u8 *)enginetools->vt->start_movie_recording; - void *cl_isrecordingmovie = NULL; + void *cl_isrecordingmovie = 0; 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; + NEXT_INSN(p, "CL_IsRecordingMovie"); } if (!cl_isrecordingmovie) bail("couldn't find cbuf_addtext CL_IsRecordingMovie"); @@ -120,6 +132,84 @@ bool api_init(void) { debug("movieinfo = %p", (void *)movieinfo); } + // find SND_RecordBuffer... this is something! + { + // step 1: Find S_Init() in snd_restart + struct concommand *snd_restart = cvar->vt->find_command(cvar, "snd_restart"); + instr = (const u8 *)snd_restart->callback; + void *s_init = 0; + // S_Init is the 3rd call in Snd_Restart_f + int call_count = 0; + for (const u8 *p = instr; p - instr < 64;) { + if (*p == X86_CALL && ++call_count == 3) + s_init = (void *)(p + 5 + *(i32 *)(p + 1)); + NEXT_INSN(p, "S_Init"); + } + debug("S_Init = %p", (void *)s_init); + if (!s_init) bail("couldn't find S_Init"); + // step 2: find g_AudioDevice in S_Init + // g_AudioDevice is the first variable assignment after the check for + // the -nosound command line arg. look for that. + instr = (const u8 *)s_init; + bool nosound_push = false; + for (const u8 *p = instr; p - instr < 180;) { + if (p[0] == X86_PUSHIW + && !strcmp(*(const char **)(p + 1), "-nosound")) + nosound_push = true; + if (nosound_push && p[0] == X86_MOVIIEAX) { + audio_device = *(struct audio_device ***)(p + 1); + break; + } + NEXT_INSN(p, "g_AudioDevice"); + } + debug("g_AudioDevice = %p", (void *)audio_device); + if (!audio_device) bail("couldn't get ptr to g_AudioDevice"); + } + return true; +} + +bool api_find_snd_recordbuffer(void) { + // continuation, we must do this part later + // step 3: Find S_TransferStereo16 in CAudioDirectSound::TransferSamples + void *transferstereo16 = 0; + const u8 *instr = (*audio_device)->vt->transfer_samples; + debug("CAudioDirectSound::TransferSamples = %p", (void *)instr); + // S_TransferStereo16 is the 8th call in TransferSamples + int call_count = 0; + for (const u8 *p = instr; p - instr < 384 /* big func! */;) { + if (*p == X86_CALL && ++call_count == 8) + transferstereo16 = (void *)(p + 5 + *(i32 *)(p + 1)); + NEXT_INSN(p, "S_TransferStereo16"); + } + if (!transferstereo16) + bail("couldn't find transferstereo16"); + debug("S_TransferStereo16 = %p", transferstereo16); + // step 4: find SND_RecordBuffer in S_TransferStereo16 + // it should be the next call after an 'add ecx, ecx' + // We are also going to get Snd_WriteLinearBlastStereo16 to get some other + // variables we need. + instr = (const u8 *)transferstereo16; + bool found_double = false; + bool found_recbuf = false; + for (const u8 *p = instr; p - instr < 192;) { + if (p[0] == X86_ADDRMW && p[1] == X86_MODRM(3, 1, 1)) + found_double = true; + else if (p[0] == X86_CALL && found_double && !found_recbuf) { + found_recbuf = true; + snd_recordbuffer = (void (*)(void))(p + 5 + *(i32 *)(p + 1)); + } + // this will be the next call + else if (p[0] == X86_CALL && found_double && found_recbuf) { + // this looks outrageous! that is because it is. + snd = (struct soundstate *)(p + 5 + *(i32 *)(p + 1) + 3); + break; + } + NEXT_INSN(p, "SND_RecordBuffer"); + } + if (!snd_recordbuffer) + bail("couldn't find snd_recordbuffer"); + debug("SND_RecordBuffer = %p", (void *)snd_recordbuffer); + debug("Snd_WriteLinearBlastStereo16 + 3 = %p", (void *)snd); return true; } @@ -21,6 +21,7 @@ #define VENGINE_CLIENT_INTERFACE_VERSION "VEngineClient013" #define VENGINE_LAUNCHER_INTERFACE_VERSION "VENGINE_LAUNCHER_API_VERSION004" #define VENGINE_TOOL_INTERFACE_VERSION "VENGINETOOL003" +#define VENGINE_CVAR_INTERFACE_VERSION "VEngineCvar004" #include "intdef.h" #include "os.h" @@ -55,6 +56,34 @@ struct enginetools { } *vt; }; +struct cvar { + struct { + usize _pad[10]; + struct concommand * (*VIRTUAL find_command)(struct cvar *this, const char *name); + } *vt; +}; + +struct cmdbase { + void **vt; + struct cmdbase *next; + bool registered; + const char *name; + const char *helpstring; + int flags; +}; + +struct concommand { + struct cmdbase base; + void *callback; +}; + +struct audio_device { + struct { + usize _pad[22]; + void *transfer_samples; + } *vt; +}; + struct movieinfo { char name[256]; int curframe; @@ -132,6 +161,29 @@ struct demoplayer { } *vt; }; +/* EPIC HACK: + * Snd_WriteLinearBlastStereo16 is an inline asm function that starts with + * + * mov ebx,snd_p + * mov edi,snd_out + * mov ecx,snd_linear_count + * mov esi,snd_vol + * + * Luckily for us, these are all the things that we need!!!! + * So, we just use the instructions as a struct so we don't have to copy + * anything. It just works. + */ +struct soundstate { + u16 _mov_ebx; + int **snd_p; + u16 _mov_edi; + short **snd_out; + u16 _mov_ecx; + int *snd_linear_count; + u16 _mov_esi; + int *snd_vol; +} __attribute__((packed)); + #ifndef NO_EXTERNS extern struct engserver *engserver; extern struct engclient *engclient; @@ -141,10 +193,13 @@ extern struct demoplayer *demoplayer; extern struct videomode **videomode; extern struct movieinfo *movieinfo; extern void (*cbuf_addtext)(char *); +extern void (*snd_recordbuffer)(void); +extern struct soundstate *snd; #endif // initializes required engine apis. returns false on error. bool api_init(void); +bool api_find_snd_recordbuffer(void); #endif @@ -15,6 +15,7 @@ */ #include <stdio.h> +#include <sys/timeb.h> #define die(fmt, ...) {\ fprintf(stderr, "err: %s: " fmt "\n", __func__ __VA_OPT__(,) __VA_ARGS__); \ @@ -34,5 +35,3 @@ #define debug(fmt, ...) \ fprintf(stderr, "dbg: %s: " fmt "\n", __func__ __VA_OPT__(,) __VA_ARGS__) - -// vi: sw=4 ts=4 noet tw=80 cc=80 @@ -35,6 +35,7 @@ struct { int qvs; const char *out; const char **demo; + int demo_count; bool combine; } args = {0}; @@ -42,13 +43,14 @@ 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, args.qvs)) + if (!strcmp(str, "exec valve.rc\n")) { + // after sound has been init'd, we can finish this + if (!api_find_snd_recordbuffer()) + die("couldn't find SND_RecordBuffer"); + if (!render_init()) die("couldn't init render"); - // play the demo demoplayer->vt->start_playback(demoplayer, args.demo[0], false); + orig_cbuf_addtext("exec prerender"); } } @@ -69,7 +71,7 @@ void WINAPI *hook_LoadLibraryExA(const char *filename, void *hfile, int flags) { for (const char *p = filename; *p; p++) if (*p == '\\') basename = p + 1; - if (!strcmp(basename, "engine.dll")) { + if (!strcmp(basename, "gameui.dll")) { if (!api_init()) die("couldn't get apis"); orig_cbuf_addtext = (void (*)(char *)) hook_inline((void *)cbuf_addtext, (void *)hook_cbuf_addtext); @@ -146,6 +148,7 @@ int main(int argc, const char **argv) { } args.out = argv[0]; args.demo = argv + 1; + args.demo_count = argc - 2; sprintf(cmdline, "hl2.exe -game %s -w %d -h %d -window -console", args.game, args.width, args.height); @@ -16,18 +16,44 @@ #include "api.h" #include "log.h" +#include "hook.h" #include "render.h" #include "os.h" -#include <codecapi.h> +#include <combaseapi.h> #include <mfapi.h> -#include <mfobjects.h> +#include <mfidl.h> #include <mfreadwrite.h> +#include <codecapi.h> +#include <mftransform.h> #include <stdlib.h> #include <string.h> #include <strmif.h> #include <winerror.h> +extern struct { + int width; + int height; + const char *game; + int fps; + int quality; + int bitrate; + int qvs; + const char *out; + const char **demo; + int demo_count; + bool combine; +} args; + +struct renderctx { + GUID enc_format; + GUID in_fmt; + IMFSinkWriter *sink_writer; + ulong stream_index; + ulong audio_index; + int cur_demo; +}; + static struct renderctx ctx = { 0 }; static bool is_rendering = false; @@ -39,50 +65,63 @@ static bool is_rendering = false; 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; + const int szpx = 3; - HR(MFCreateMemoryBuffer(ctx.width * ctx.height * szpx, &imf_buffer)); + static struct { u8 bgr[3]; } *raw = 0; + if (!raw) raw = malloc(args.width * args.height * szpx); + + IMFMediaBuffer *imf_buffer = 0; + HR(MFCreateMemoryBuffer(args.width * args.height * szpx, &imf_buffer)); + struct { u8 bgr[3]; } *data = 0; 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]; - } */ + this->vt->read_screen_pixels(this, 0, 0, args.width, args.height, raw, IMAGE_FORMAT_BGR888); + for (int y = args.height - 1; y > 0; y--) { + memcpy(data + ((args.height - y) * args.width), raw + y * args.width, + szpx * args.width); } - free(raw); HR(imf_buffer->lpVtbl->Unlock(imf_buffer)); - HR(imf_buffer->lpVtbl->SetCurrentLength(imf_buffer, ctx.width * ctx.height * szpx)); + HR(imf_buffer->lpVtbl->SetCurrentLength(imf_buffer, args.width * args.height * szpx)); + IMFSample *sample = 0; HR(MFCreateSample(&sample)); HR(sample->lpVtbl->AddBuffer(sample, imf_buffer)); - u64 sample_duration = (u64)((1.0 / (double)ctx.fps) * 10000000.0); + u64 sample_duration = (u64)((1.0 / (double)args.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)); + HR(sample->lpVtbl->Release(sample)); + HR(imf_buffer->lpVtbl->Release(imf_buffer)); - 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)) + if (!do_frame(this, info)) die("oopsie!"); } +bool do_audio_frame(void) { + // TODO: audio +#define CLIP16(n) (i16)(n < -32768 ? -32768 : (n > 32767 ? 32767 : n)) +#undef CLIP16 + return true; +} + +void (*orig_snd_recordbuffer)(void) = 0; +void hook_snd_recordbuffer(void) { + if (!is_rendering) { + orig_snd_recordbuffer(); + return; + } + do_audio_frame(); +} bool do_stop() { - HR(ctx.sink_writer->lpVtbl->Flush(ctx.sink_writer, ctx.stream_index)); + //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; @@ -94,14 +133,20 @@ void VIRTUAL hook_stop_playback(struct demoplayer *this) { if (!is_rendering) return; is_rendering = false; + + // do we have any more demos to play? + if (args.demo_count--) { + demoplayer->vt->start_playback(demoplayer, *++args.demo, false); + return; + } + 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, int qvs) { +bool render_init(void) { bool r = os_mprot((*videomode)->vt, 28 * sizeof(void *), PAGE_EXECUTE_READWRITE); if (!r) bail("couldn't mprotect videomode vtable"); @@ -113,20 +158,19 @@ bool render_init(int width, int height, int framerate, bool use_bitrate, orig_stop_playback = demoplayer->vt->stop_playback; demoplayer->vt->stop_playback = hook_stop_playback; + // hook the audio frame + orig_snd_recordbuffer = (void (*)(void)) + hook_inline((void *)snd_recordbuffer, (void *)hook_snd_recordbuffer); + // 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); + sprintf(framerate_cmd, "host_framerate %d;", args.fps); cbuf_addtext(framerate_cmd); - ctx.fps = framerate; - ctx.width = width; - ctx.height = height; - ctx.bit_rate = use_bitrate ? quality : 99999999; - - MFStartup(MF_VERSION, 0); + HR(MFStartup(MF_VERSION, 0)); // init sinkwriter { @@ -134,34 +178,35 @@ bool render_init(int width, int height, int framerate, bool use_bitrate, IMFMediaType *mt_in = NULL; u16 path[MAX_PATH] = {0}; - info("strlen = %d", strlen(output_file)); - mbstowcs(path, output_file, strlen(output_file)); + mbstowcs(path, args.out, strlen(args.out)); info("rendering to %S", path); - HR(MFCreateSinkWriterFromURL(path, NULL, NULL, &ctx.sink_writer)); + IMFAttributes *sinkwriter_attr; + HR(MFCreateAttributes(&sinkwriter_attr, 1)); + HR(sinkwriter_attr->lpVtbl->SetUINT32(sinkwriter_attr, &MF_READWRITE_ENABLE_HARDWARE_TRANSFORMS, TRUE)); + HR(MFCreateSinkWriterFromURL(path, NULL, sinkwriter_attr, &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_AVG_BITRATE, args.bitrate ? args.bitrate : 9999999)); 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_FRAME_SIZE, ((u64)args.width << 32) | args.height)); + HR(mt_out->lpVtbl->SetUINT64(mt_out, &MF_MT_FRAME_RATE, ((u64)args.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->SetGUID(mt_in, &MF_MT_SUBTYPE, &MFVideoFormat_RGB24)); 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->SetUINT32(mt_in, &MF_MT_ALL_SAMPLES_INDEPENDENT, TRUE)); + HR(mt_in->lpVtbl->SetUINT64(mt_in, &MF_MT_FRAME_SIZE, ((u64)args.width << 32) | args.height)); + HR(mt_in->lpVtbl->SetUINT64(mt_in, &MF_MT_FRAME_RATE, ((u64)args.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 @@ -169,20 +214,22 @@ bool render_init(int width, int height, int framerate, bool use_bitrate, 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; + int ratecontrol = !!args.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 }; + VARIANT _quality = { + .vt = VT_UI4, + .ulVal = !!args.bitrate ? args.bitrate : args.quality + }; HR(codec->lpVtbl->SetValue(codec, &CODECAPI_AVEncCommonQuality, &_quality)); - VARIANT _qvs = { .vt = VT_UI4, .ulVal = qvs }; + VARIANT _qvs = { .vt = VT_UI4, .ulVal = args.qvs }; HR(codec->lpVtbl->SetValue(codec, &CODECAPI_AVEncCommonQualityVsSpeed, &_qvs)); // set the gop size to 2 seconds - VARIANT gop = { .vt = VT_UI4, .ulVal = 2 * ctx.fps }; + VARIANT gop = { .vt = VT_UI4, .ulVal = 2 * args.fps }; HR(codec->lpVtbl->SetValue(codec, &CODECAPI_AVEncMPVGOPSize, &gop)); // enable 2 b-frames @@ -192,6 +239,8 @@ bool render_init(int width, int height, int framerate, bool use_bitrate, codec->lpVtbl->Release(codec); } + HR(ctx.sink_writer->lpVtbl->BeginWriting(ctx.sink_writer)); + return true; } @@ -14,24 +14,6 @@ * 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, int qvs); - -struct renderctx { - int width; - int height; - int fps; - int bit_rate; - GUID enc_format; - GUID in_fmt; - IMFSinkWriter *sink_writer; - ulong stream_index; -}; +bool render_init(void); // vi: sw=4 ts=4 noet tw=80 cc=80 |