diff options
-rw-r--r-- | Makefile | 13 | ||||
-rw-r--r-- | README | 21 | ||||
-rw-r--r-- | compile_flags.txt | 6 | ||||
-rw-r--r-- | main.c | 34 | ||||
-rw-r--r-- | render.c | 371 |
5 files changed, 279 insertions, 166 deletions
@@ -1,8 +1,17 @@ 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 -lole32 -lmfuuid -lmfreadwrite -lstrmiids + +ifeq ($(OS), Windows_NT) + override CFLAGS+=-I"$(VCPKG_ROOT)/installed/x86-windows-static/include" + override LDFLAGS+=-L"$(VCPKG_ROOT)/installed/x86-windows-static/lib" +endif + +override CFLAGS+=-m32 -I3p -std=c23 +override LDFLAGS+=-lavcodec -lavformat -lavutil -lswscale -llibmfx \ + -llibx264 -lswresample -laom -lSvtAv1Enc -lwsock32 -lws2_32 -lbcrypt \ + -lsecur32 -lx265-static -lmfuuid -lstrmiids -luser32 + OBJS = main.o api.o 3p/sst/x86.o hook.o render.o @@ -4,9 +4,22 @@ rendertools orange box engine demo renderer usage: - rt [-w <width>] [-h <height>] [-g <game>] [-r <fps>] [-s <qvs>] [-q <quality>] OR [-b <bitrate>] - path/to/video.mp4 path/to/demo1.dem... + rt [-w <width>] [-h <height>] [-g <game>] [-e <encoder>] [-r <fps>] + [-q <crf>] OR [-b <bitrate>] path/to/video.mp4 path/to/demo1.dem... -bitrate is in kbps, quality 1-100 -cannot specify quality and bitrate at the same time +bitrate is in kbps + +encoder: default is libx264 +options: + - h264_nvenc + - hevc_nvenc + - libx264 + - libx265 + - h264_amf + - h265_amf + - av1_amf + - libsvtav1 + +Right now -q only works with libx264. + demo path is relative to the gamedir (e.g. hl2) diff --git a/compile_flags.txt b/compile_flags.txt index 118e065..69c62ea 100644 --- a/compile_flags.txt +++ b/compile_flags.txt @@ -1,9 +1,7 @@ --target -i686-pc-windows-msvc --include -stdbool.h +-m32 -I3p -Wpedantic -Wextra -std=c23 -Wno-gnu-zero-variadic-macro-arguments +-IC:/Users/wozniak/scoop/persist/vcpkg/installed/x86-windows-static/include @@ -32,11 +32,11 @@ struct { int fps; int quality; int bitrate; - int qvs; const char *out; const char **demo; int demo_count; - bool combine; + const char *encoder; + const char *preset; } args = {0}; void (*orig_cbuf_addtext)(char *); @@ -84,12 +84,7 @@ typedef int (*LauncherMain_t)(void *instance, void *prev_inst, char *cmdline, int cmd_show); void usage() { - const char *usage = - "usage:\n" - " rt [-w <width>] [-h <height>] [-g <game>] [-r <fps>] [-s <qvs>]" - "[-1] [-q <quality>] OR [-b <bitrate>]\n" - " path/to/video.mp4 path/to/demo1.dem..."; - puts(usage); + printf("invalid command line args! please read the readme\n"); } int main(int argc, const char **argv) { @@ -116,14 +111,7 @@ int main(int argc, const char **argv) { case 'q': arg = OPTARG(argc, argv); args.quality = strtol(arg, &strend, 10); - if (strend == arg || args.quality < 1 || args.quality > 100) - die("quality must be a number 1-100"); - break; - case 's': - arg = OPTARG(argc, argv); - args.qvs = strtol(arg, &strend, 10); - if (strend == arg || args.qvs < 1 || args.qvs > 100) - die("qvs must be a number 1-100"); + if (strend == arg) die("crf must be a number"); break; case 'b': arg = OPTARG(argc, argv); @@ -132,16 +120,22 @@ int main(int argc, const char **argv) { break; case 'g': args.game = OPTARG(argc, argv); - case '1': - args.combine = true; + break; + case 'p': + args.preset = OPTARG(argc, argv); + break; + case 'e': + args.encoder = OPTARG(argc, argv); + break; } + if (!args.encoder) args.encoder = "libx264"; 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; - if (!args.qvs) args.qvs = 100; + if (!args.quality) args.quality = 25; + if (!args.preset) args.preset = "fast"; if (argc < 2) { usage(); @@ -15,21 +15,31 @@ */ #include "api.h" +#include "libavutil/channel_layout.h" +#include "libavutil/mathematics.h" #include "log.h" #include "hook.h" #include "render.h" #include "os.h" -#include <combaseapi.h> -#include <mfapi.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> +#include <libavformat/avio.h> +#include <libavformat/avformat.h> +#include <libavcodec/avcodec.h> +#include <libavcodec/codec_id.h> +#include <libavutil/error.h> +#include <libavutil/opt.h> +#include <libavutil/log.h> +#include <libavutil/rational.h> +#include <libavutil/imgutils.h> +#include <libavcodec/codec.h> +#include <libavcodec/packet.h> +#include <libavutil/frame.h> +#include <libavutil/avutil.h> +#include <libavutil/samplefmt.h> +#include <libavcodec/codec_par.h> +#include <libavutil/log.h> +#include <libswscale/swscale.h> +#include <libswresample/swresample.h> extern struct { int width; @@ -38,67 +48,81 @@ extern struct { int fps; int quality; int bitrate; - int qvs; const char *out; const char **demo; int demo_count; - bool combine; + const char *encoder; + const char *preset; } args; struct renderctx { - GUID enc_format; - GUID in_fmt; - IMFSinkWriter *sink_writer; - ulong stream_index; - ulong audio_index; - int cur_demo; + AVFormatContext *format; + AVStream *vstream; + const AVCodec *vcodec; + AVCodecContext *vcodec_ctx; + AVFrame *yuv_frame; + struct SwsContext *sws; + + AVStream *astream; + const AVCodec *acodec; + AVCodecContext *acodec_ctx; + AVFrame *aframe; + AVFrame *aframe_tmp; + struct SwrContext *swr; + u64 nextpts; }; 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 encode(AVCodecContext *codec, AVFrame *frame, AVStream *stream) { + int ret; + AVPacket *pkt = av_packet_alloc(); + + if ((ret = avcodec_send_frame(codec, frame)) < 0) + bail("error sending a frame for encoding"); + + while (ret >= 0) { + ret = avcodec_receive_packet(codec, pkt); + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) + break; + else if (ret < 0) + bail("encoding error!"); + + pkt->stream_index = stream->index; + + av_packet_rescale_ts(pkt, codec->time_base, stream->time_base); + + if (av_interleaved_write_frame(ctx.format, pkt) < 0) + bail("failed to write frame to file!"); + av_packet_unref(pkt); + } + av_packet_free(&pkt); + return true; } bool do_frame(struct videomode *this, struct movieinfo *info) { is_rendering = true; - const int szpx = 3; - - static struct { u8 bgr[3]; } *raw = 0; - if (!raw) raw = malloc(args.width * args.height * szpx); + static struct { u8 bgr[3]; } *pixels = 0; + if (!pixels) + pixels = malloc(args.width * args.height * sizeof(*pixels)); - 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!! if (steampipe) this->vt->steampipe_read_screen_pixels(this, 0, 0, args.width, - args.height, raw, IMAGE_FORMAT_BGR888); + args.height, pixels, IMAGE_FORMAT_BGR888); else - this->vt->read_screen_pixels(this, 0, 0, args.width, args.height, raw, + this->vt->read_screen_pixels(this, 0, 0, args.width, args.height, pixels, 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); - } - HR(imf_buffer->lpVtbl->Unlock(imf_buffer)); - 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)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)); + int stride = args.width * sizeof(*pixels); + sws_scale(ctx.sws, (const u8 **)&pixels, &stride, 0, args.height, + ctx.yuv_frame->data, ctx.yuv_frame->linesize); + + ctx.yuv_frame->pts = info->curframe; + ctx.yuv_frame->time_base = ctx.vcodec_ctx->time_base; + ctx.yuv_frame->duration = 1; + + encode(ctx.vcodec_ctx, ctx.yuv_frame, ctx.vstream); return true; } @@ -106,13 +130,34 @@ bool do_frame(struct videomode *this, struct movieinfo *info) { 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!"); + if (!do_frame(this, info)) warn("do_frame failed!"); } bool do_audio_frame(void) { - // TODO: audio + if (!is_rendering) return true; + + static int frame_idx = 0; #define CLIP16(n) (i16)(n < -32768 ? -32768 : (n > 32767 ? 32767 : n)) + + for (int i = 0; i < *snd->snd_linear_count; i += 2) { + for (int c = 0; c < 2; ++c) { + ((i16 *)ctx.aframe_tmp->data[c])[frame_idx] = + CLIP16(((*snd->snd_p)[i + c] * *snd->snd_vol) >> 8); + } + + if (++frame_idx == ctx.aframe->nb_samples) { + if (swr_convert(ctx.swr, ctx.aframe->data, ctx.aframe->nb_samples, + (const u8 *const *)ctx.aframe_tmp->data, + ctx.aframe_tmp->nb_samples) < 0) + bail("failed to resample audio frame"); + + ctx.aframe->pts = ctx.nextpts; + ctx.nextpts += ctx.aframe->nb_samples; + encode(ctx.acodec_ctx, ctx.aframe, ctx.astream); + frame_idx = 0; + } + } + #undef CLIP16 return true; } @@ -127,30 +172,40 @@ void hook_snd_recordbuffer(void) { } 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()); + if (!encode(ctx.vcodec_ctx, 0, ctx.vstream)) + warn("video flush failed"); + if (!encode(ctx.acodec_ctx, 0, ctx.astream)) + warn("audio flush failed!"); + + if (av_write_trailer(ctx.format)) + warn("couldn't write trailer!"); + + avio_flush(ctx.format->pb); + avio_close(ctx.format->pb); + + av_frame_free(&ctx.yuv_frame); + avcodec_free_context(&ctx.vcodec_ctx); + av_frame_free(&ctx.aframe); + avcodec_free_context(&ctx.acodec_ctx); + avformat_free_context(ctx.format); + + sws_freeContext(ctx.sws); + 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; - // do we have any more demos to play? if (args.demo_count--) { demoplayer->vt->start_playback(demoplayer, *++args.demo, false); return; } - info("finished!"); - - if (!do_stop()) - die("oopsie!"); - + do_stop(); exit(0); } @@ -182,79 +237,123 @@ bool render_init(void) { sprintf(framerate_cmd, "host_framerate %d;", args.fps); cbuf_addtext(framerate_cmd); - if (CoInitializeEx(NULL, COINIT_MULTITHREADED) == RPC_E_CHANGED_MODE) - warn("changed COM concurrency mode!"); - - HR(MFStartup(MF_VERSION, 0)); - - // init sinkwriter - { - IMFMediaType *mt_out = NULL; - IMFMediaType *mt_in = NULL; - - u16 path[MAX_PATH] = {0}; - mbstowcs(path, args.out, strlen(args.out)); - - info("rendering to %S", path); - - 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, 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)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_RGB24)); - HR(mt_in->lpVtbl->SetUINT32(mt_in, &MF_MT_INTERLACE_MODE, MFVideoInterlace_Progressive)); - 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)); - } - - // video encoding parameters - { - ICodecAPI *codec; - HR(ctx.sink_writer->lpVtbl->GetServiceForStream(ctx.sink_writer, 0, &GUID_NULL, &IID_ICodecAPI, (void*)&codec)); - - 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 = !!args.bitrate ? args.bitrate : args.quality - }; - HR(codec->lpVtbl->SetValue(codec, &CODECAPI_AVEncCommonQuality, &_quality)); - - 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 * args.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); - } - - HR(ctx.sink_writer->lpVtbl->BeginWriting(ctx.sink_writer)); + info("started recording to %s (%dx%d @ %d)", args.out, args.width, + args.height, args.fps); + + avformat_alloc_output_context2(&ctx.format, 0, 0, args.out); + if (!ctx.format) + die("couldn't create output context (does the filename have an ext?)"); + + // video codec + ctx.vcodec = avcodec_find_encoder_by_name(args.encoder); + ctx.vcodec_ctx = avcodec_alloc_context3(ctx.vcodec); + ctx.vcodec_ctx->width = args.width; + ctx.vcodec_ctx->height = args.height; + ctx.vcodec_ctx->pix_fmt = AV_PIX_FMT_YUV420P; + ctx.vcodec_ctx->time_base = av_make_q(1, args.fps); + ctx.vcodec_ctx->framerate = av_make_q(args.fps, 1); + ctx.vcodec_ctx->gop_size = args.fps * 2; + ctx.vcodec_ctx->bit_rate = args.bitrate * 1000; + + if (!args.bitrate) + av_opt_set_int(ctx.vcodec_ctx->priv_data, "crf", args.quality, 0); + if (args.preset) + av_opt_set(ctx.vcodec_ctx->priv_data, "preset", args.preset, 0); + + if (avcodec_open2(ctx.vcodec_ctx, ctx.vcodec, NULL) < 0) + die("failed to open video encoder"); + + // video stream + ctx.vstream = avformat_new_stream(ctx.format, NULL); + if (!ctx.vstream) die("failed to create vide stream"); + avcodec_parameters_from_context(ctx.vstream->codecpar, ctx.vcodec_ctx); + ctx.vstream->time_base = ctx.vcodec_ctx->time_base; + ctx.vstream->avg_frame_rate = ctx.vcodec_ctx->framerate; + + // video frame + ctx.yuv_frame = av_frame_alloc(); + ctx.yuv_frame->width = args.width; + ctx.yuv_frame->height = args.height; + ctx.yuv_frame->color_range = AVCOL_RANGE_JPEG; + ctx.yuv_frame->time_base = ctx.vcodec_ctx->time_base; + ctx.yuv_frame->format = ctx.vcodec_ctx->pix_fmt; + + ctx.sws = sws_getContext(args.width, args.height, AV_PIX_FMT_BGR24, + args.width, args.height, ctx.yuv_frame->format, + SWS_FAST_BILINEAR | SWS_FULL_CHR_H_INT | SWS_ACCURATE_RND, 0, 0, 0); + + if (av_frame_get_buffer(ctx.yuv_frame, 0) < 0) + bail("failed to alloc frame buffer"); + + // audio codec + ctx.acodec = avcodec_find_encoder(AV_CODEC_ID_AAC); + if (!ctx.acodec) bail("failed to find aac codec"); + ctx.acodec_ctx = avcodec_alloc_context3(ctx.acodec); + ctx.acodec_ctx->bit_rate = 128000; + ctx.acodec_ctx->sample_fmt = AV_SAMPLE_FMT_FLTP; + ctx.acodec_ctx->sample_rate = 44100; + ctx.acodec_ctx->profile = FF_PROFILE_AAC_MAIN; + ctx.acodec_ctx->time_base = av_make_q(1, 44100); + + if (av_channel_layout_copy(&ctx.acodec_ctx->ch_layout, + &(AVChannelLayout)AV_CHANNEL_LAYOUT_STEREO)) + bail("failed to copy channel layout"); + + if (avcodec_open2(ctx.acodec_ctx, ctx.acodec, NULL) < 0) + die("failed to open audio encoder"); + + // audio resampler + ctx.swr = swr_alloc(); + av_opt_set_int(ctx.swr, "in_channel_count", 2, 0); + av_opt_set_int(ctx.swr, "in_sample_rate", 44100, 0); + av_opt_set_sample_fmt(ctx.swr, "in_sample_fmt", AV_SAMPLE_FMT_S16P, 0); + av_opt_set_chlayout(ctx.swr, "in_chlayout", &ctx.acodec_ctx->ch_layout, 0); + av_opt_set_int(ctx.swr, "out_channel_count", 2, 0); + av_opt_set_int(ctx.swr, "out_sample_rate", ctx.acodec_ctx->sample_rate, 0); + av_opt_set_sample_fmt(ctx.swr, "out_sample_fmt", ctx.acodec_ctx->sample_fmt, 0); + av_opt_set_chlayout(ctx.swr, "out_chlayout", &ctx.acodec_ctx->ch_layout, 0); + + if (swr_init(ctx.swr) < 0) + bail("failed to init resampler"); + + ctx.nextpts = 0; + + // audio frame + ctx.aframe = av_frame_alloc(); + if (!ctx.aframe) bail("failed to alloc audio frame"); + + ctx.aframe->nb_samples = ctx.acodec_ctx->frame_size; + ctx.aframe->format = ctx.acodec_ctx->sample_fmt; + if (av_channel_layout_copy(&ctx.aframe->ch_layout, &ctx.acodec_ctx->ch_layout)) + bail("failed to copy channel layout"); + if (av_frame_get_buffer(ctx.aframe, 0)) + bail("couldn't alloc audio frame buffer"); + + // audio tmp frame (pre resampling) + ctx.aframe_tmp = av_frame_alloc(); + if (!ctx.aframe_tmp) bail("failed to alloc tmp audio frame"); + + ctx.aframe_tmp->nb_samples = ctx.acodec_ctx->frame_size; + ctx.aframe_tmp->format = ctx.acodec_ctx->sample_fmt; + if (av_channel_layout_copy(&ctx.aframe_tmp->ch_layout, &ctx.acodec_ctx->ch_layout)) + bail("failed to copy channel layout"); + if (av_frame_get_buffer(ctx.aframe_tmp, 0)) + bail("couldn't alloc audio frame buffer"); + + // audio stream + ctx.astream = avformat_new_stream(ctx.format, NULL); + if (!ctx.astream) die("failed to create audio stream"); + avcodec_parameters_from_context(ctx.astream->codecpar, ctx.acodec_ctx); + ctx.astream->time_base = ctx.acodec_ctx->time_base; + + if (avcodec_open2(ctx.acodec_ctx, ctx.acodec, NULL) < 0) + bail("failed to open audio encoder"); + + if (avio_open(&ctx.format->pb, args.out, AVIO_FLAG_WRITE) < 0) + bail("failed to open file"); + + if (avformat_write_header(ctx.format, NULL) < 0) + bail("error when writing to output file"); return true; } |