From 8f9b68ad90a0214d949b044413df33bfe5a3bcf7 Mon Sep 17 00:00:00 2001 From: Themaister Date: Fri, 23 Nov 2012 22:46:21 +0100 Subject: [PATCH] Add config file support to FFmpeg recording. --- Makefile | 4 +- conf/config_file.c | 80 +++++++++----- conf/config_file.h | 11 ++ config.def.h | 6 -- docs/retroarch.1 | 4 + general.h | 3 +- qb/config.libs.sh | 5 +- record/ffemu.c | 259 +++++++++++++++++++++++++++++++++------------ record/ffemu.h | 4 + retroarch.c | 28 ++--- retroarch.cfg | 6 -- settings.c | 4 - 12 files changed, 282 insertions(+), 132 deletions(-) diff --git a/Makefile b/Makefile index a1af4094a6..3a1b4a893d 100644 --- a/Makefile +++ b/Makefile @@ -266,8 +266,8 @@ endif ifeq ($(HAVE_FFMPEG), 1) OBJ += record/ffemu.o - LIBS += $(AVCODEC_LIBS) $(AVFORMAT_LIBS) $(AVUTIL_LIBS) - DEFINES += $(AVCODEC_CFLAGS) $(AVFORMAT_CFLAGS) $(AVUTIL_CFLAGS) + LIBS += $(AVCODEC_LIBS) $(AVFORMAT_LIBS) $(AVUTIL_LIBS) $(SWSCALE_LIBS) + DEFINES += $(AVCODEC_CFLAGS) $(AVFORMAT_CFLAGS) $(AVUTIL_CFLAGS) $(SWSCALE_CFLAGS) endif ifeq ($(HAVE_DYNAMIC), 1) diff --git a/conf/config_file.c b/conf/config_file.c index eaa8b5ebb1..8a644f1d86 100644 --- a/conf/config_file.c +++ b/conf/config_file.c @@ -44,12 +44,12 @@ #define MAX_INCLUDE_DEPTH 16 -struct entry_list +struct config_entry_list { bool readonly; // If we got this from an #include, do not allow write. char *key; char *value; - struct entry_list *next; + struct config_entry_list *next; }; struct include_list @@ -61,8 +61,8 @@ struct include_list struct config_file { char *path; - struct entry_list *entries; - struct entry_list *tail; + struct config_entry_list *entries; + struct config_entry_list *tail; unsigned include_depth; struct include_list *includes; @@ -132,7 +132,7 @@ static char *extract_value(char *line, bool is_value) } } -static void set_list_readonly(struct entry_list *list) +static void set_list_readonly(struct config_entry_list *list) { while (list) { @@ -146,7 +146,7 @@ static void add_child_list(config_file_t *parent, config_file_t *child) { if (parent->entries) { - struct entry_list *head = parent->entries; + struct config_entry_list *head = parent->entries; while (head->next) head = head->next; @@ -164,7 +164,7 @@ static void add_child_list(config_file_t *parent, config_file_t *child) // Rebase tail. if (parent->entries) { - struct entry_list *head = parent->entries; + struct config_entry_list *head = parent->entries; while (head->next) head = head->next; parent->tail = head; @@ -227,7 +227,7 @@ static void add_sub_conf(config_file_t *conf, char *line) free(path); } -static bool parse_line(config_file_t *conf, struct entry_list *list, char *line) +static bool parse_line(config_file_t *conf, struct config_entry_list *list, char *line) { // Remove everything after comment. char *comment = strchr(line, '#'); @@ -324,7 +324,7 @@ static config_file_t *config_file_new_internal(const char *path, unsigned depth) while (!feof(file)) { - struct entry_list *list = (struct entry_list*)calloc(1, sizeof(*list)); + struct config_entry_list *list = (struct config_entry_list*)calloc(1, sizeof(*list)); char *line = getaline(file); if (line) @@ -364,12 +364,12 @@ void config_file_free(config_file_t *conf) if (!conf) return; - struct entry_list *tmp = conf->entries; + struct config_entry_list *tmp = conf->entries; while (tmp) { free(tmp->key); free(tmp->value); - struct entry_list *hold = tmp; + struct config_entry_list *hold = tmp; tmp = tmp->next; free(hold); } @@ -389,7 +389,7 @@ void config_file_free(config_file_t *conf) bool config_get_double(config_file_t *conf, const char *key, double *in) { - struct entry_list *list = conf->entries; + struct config_entry_list *list = conf->entries; while (list) { @@ -405,7 +405,7 @@ bool config_get_double(config_file_t *conf, const char *key, double *in) bool config_get_float(config_file_t *conf, const char *key, float *in) { - struct entry_list *list = conf->entries; + struct config_entry_list *list = conf->entries; while (list) { @@ -426,7 +426,7 @@ bool config_get_float(config_file_t *conf, const char *key, float *in) bool config_get_int(config_file_t *conf, const char *key, int *in) { - struct entry_list *list = conf->entries; + struct config_entry_list *list = conf->entries; while (list) { @@ -449,7 +449,7 @@ bool config_get_int(config_file_t *conf, const char *key, int *in) bool config_get_uint64(config_file_t *conf, const char *key, uint64_t *in) { - struct entry_list *list = conf->entries; + struct config_entry_list *list = conf->entries; while (list) { @@ -473,7 +473,7 @@ bool config_get_uint64(config_file_t *conf, const char *key, uint64_t *in) bool config_get_uint(config_file_t *conf, const char *key, unsigned *in) { - struct entry_list *list = conf->entries; + struct config_entry_list *list = conf->entries; while (list != NULL) { @@ -490,7 +490,7 @@ bool config_get_uint(config_file_t *conf, const char *key, unsigned *in) bool config_get_hex(config_file_t *conf, const char *key, unsigned *in) { - struct entry_list *list = conf->entries; + struct config_entry_list *list = conf->entries; while (list) { @@ -513,7 +513,7 @@ bool config_get_hex(config_file_t *conf, const char *key, unsigned *in) bool config_get_char(config_file_t *conf, const char *key, char *in) { - struct entry_list *list = conf->entries; + struct config_entry_list *list = conf->entries; while (list) { @@ -531,7 +531,7 @@ bool config_get_char(config_file_t *conf, const char *key, char *in) bool config_get_string(config_file_t *conf, const char *key, char **str) { - struct entry_list *list = conf->entries; + struct config_entry_list *list = conf->entries; while (list) { @@ -547,7 +547,7 @@ bool config_get_string(config_file_t *conf, const char *key, char **str) bool config_get_array(config_file_t *conf, const char *key, char *buf, size_t size) { - struct entry_list *list = conf->entries; + struct config_entry_list *list = conf->entries; while (list) { @@ -563,7 +563,7 @@ bool config_get_path(config_file_t *conf, const char *key, char *buf, size_t siz #if defined(_WIN32) || defined(RARCH_CONSOLE) return config_get_array(conf, key, buf, size); #else - struct entry_list *list = conf->entries; + struct config_entry_list *list = conf->entries; while (list) { @@ -595,7 +595,7 @@ bool config_get_path(config_file_t *conf, const char *key, char *buf, size_t siz bool config_get_bool(config_file_t *conf, const char *key, bool *in) { - struct entry_list *list = conf->entries; + struct config_entry_list *list = conf->entries; while (list) { @@ -621,8 +621,8 @@ bool config_get_bool(config_file_t *conf, const char *key, bool *in) void config_set_string(config_file_t *conf, const char *key, const char *val) { - struct entry_list *list = conf->entries; - struct entry_list *last = list; + struct config_entry_list *list = conf->entries; + struct config_entry_list *last = list; while (list) { if (!list->readonly && (strcmp(key, list->key) == 0)) @@ -636,7 +636,7 @@ void config_set_string(config_file_t *conf, const char *key, const char *val) list = list->next; } - struct entry_list *elem = (struct entry_list*)calloc(1, sizeof(*elem)); + struct config_entry_list *elem = (struct config_entry_list*)calloc(1, sizeof(*elem)); elem->key = strdup(key); elem->value = strdup(val); @@ -724,7 +724,7 @@ void config_file_dump(config_file_t *conf, FILE *file) includes = includes->next; } - struct entry_list *list = conf->entries; + struct config_entry_list *list = conf->entries; while (list) { if (!list->readonly) @@ -742,7 +742,7 @@ void config_file_dump_all(config_file_t *conf, FILE *file) includes = includes->next; } - struct entry_list *list = conf->entries; + struct config_entry_list *list = conf->entries; while (list) { fprintf(file, "%s = \"%s\" %s\n", list->key, list->value, list->readonly ? "(included)" : ""); @@ -752,7 +752,7 @@ void config_file_dump_all(config_file_t *conf, FILE *file) bool config_entry_exists(config_file_t *conf, const char *entry) { - struct entry_list *list = conf->entries; + struct config_entry_list *list = conf->entries; while (list) { @@ -764,3 +764,27 @@ bool config_entry_exists(config_file_t *conf, const char *entry) return false; } +bool config_get_entry_list_head(config_file_t *conf, struct config_file_entry *entry) +{ + const struct config_entry_list *head = conf->entries; + if (!head) + return false; + + entry->key = head->key; + entry->value = head->value; + entry->next = head->next; + return true; +} + +bool config_get_entry_list_next(struct config_file_entry *entry) +{ + const struct config_entry_list *next = entry->next; + if (!next) + return false; + + entry->key = next->key; + entry->value = next->value; + entry->next = next->next; + return true; +} + diff --git a/conf/config_file.h b/conf/config_file.h index 819821a2df..1fa7d5c959 100644 --- a/conf/config_file.h +++ b/conf/config_file.h @@ -53,6 +53,17 @@ bool config_append_file(config_file_t *conf, const char *path); bool config_entry_exists(config_file_t *conf, const char *entry); +struct config_entry_list; +struct config_file_entry +{ + const char *key; + const char *value; + const struct config_entry_list *next; // Used internally. Opaque here. +}; + +bool config_get_entry_list_head(config_file_t *conf, struct config_file_entry *entry); +bool config_get_entry_list_next(struct config_file_entry *entry); + // Extracts a double from config file. bool config_get_double(config_file_t *conf, const char *entry, double *in); // Extracts a float from config file. diff --git a/config.def.h b/config.def.h index e8d60fb1b9..bb06eb117e 100644 --- a/config.def.h +++ b/config.def.h @@ -214,12 +214,6 @@ static const float fbo_scale_x = 2.0; static const float fbo_scale_y = 2.0; static const bool second_pass_smooth = true; -// Record video assuming game runs hi-res. -static const bool hires_record = false; - -// Enables lossless RGB H.264 recording if possible (if not, FFV1 is used). -static const bool h264_record = true; - // Record post-filtered (CPU filter) video rather than raw game output. static const bool post_filter_record = false; diff --git a/docs/retroarch.1 b/docs/retroarch.1 index d535b4cfaa..13d526807b 100644 --- a/docs/retroarch.1 +++ b/docs/retroarch.1 @@ -140,6 +140,10 @@ Connects a DualAnalog controller into port PORT. Possible values are 1 to 8. Activates video recording of gameplay into PATH. Using .mkv extension is recommended. Codecs used are (FFV1 or H264 RGB lossless (x264))/FLAC, suitable for processing the material further. +.TP +\fB--recordconfig PATH\fR +Sets path to a config file for use during FFmpeg recording. + .TP \fB--size WIDTHxHEIGHT\fR Allows specifying the exact output width and height of FFmpeg recording. This option will override any configuration settings. diff --git a/general.h b/general.h index 5665dfc786..0c4f5908aa 100644 --- a/general.h +++ b/general.h @@ -147,8 +147,6 @@ struct settings bool disable_composition; - bool hires_record; - bool h264_record; bool post_filter_record; bool gpu_record; bool gpu_screenshot; @@ -434,6 +432,7 @@ struct global #ifdef HAVE_FFMPEG ffemu_t *rec; char record_path[PATH_MAX]; + char record_config[PATH_MAX]; bool recording; unsigned record_width; unsigned record_height; diff --git a/qb/config.libs.sh b/qb/config.libs.sh index bdd3d298fc..0f603509e2 100644 --- a/qb/config.libs.sh +++ b/qb/config.libs.sh @@ -120,7 +120,8 @@ if [ "$HAVE_THREADS" != 'no' ]; then check_pkgconf AVCODEC libavcodec check_pkgconf AVFORMAT libavformat check_pkgconf AVUTIL libavutil - ( [ "$HAVE_FFMPEG" = 'auto' ] && ( [ "$HAVE_AVCODEC" = 'no' ] || [ "$HAVE_AVFORMAT" = 'no' ] || [ "$HAVE_AVUTIL" = 'no' ] ) && HAVE_FFMPEG='no' ) || HAVE_FFMPEG='yes' + check_pkgconf SWSCALE libswscale + ( [ "$HAVE_FFMPEG" = 'auto' ] && ( [ "$HAVE_AVCODEC" = 'no' ] || [ "$HAVE_AVFORMAT" = 'no' ] || [ "$HAVE_AVUTIL" = 'no' ] || [ "$HAVE_SWSCALE" = 'no' ] ) && HAVE_FFMPEG='no' ) || HAVE_FFMPEG='yes' fi if [ "$HAVE_FFMPEG" = 'yes' ]; then @@ -191,6 +192,6 @@ check_pkgconf PYTHON python3 add_define_make OS "$OS" # Creates config.mk and config.h. -VARS="ALSA OSS OSS_BSD OSS_LIB AL RSOUND ROAR JACK COREAUDIO PULSE SDL OPENGL GLES VG EGL KMS GBM DRM DYLIB GETOPT_LONG THREADS CG XML SDL_IMAGE LIBPNG DYNAMIC FFMPEG AVCODEC AVFORMAT AVUTIL CONFIGFILE FREETYPE XVIDEO X11 XEXT XF86VM XINERAMA NETPLAY NETWORK_CMD STDIN_CMD COMMAND SOCKET_LEGACY FBO STRL PYTHON FFMPEG_ALLOC_CONTEXT3 FFMPEG_AVCODEC_OPEN2 FFMPEG_AVIO_OPEN FFMPEG_AVFORMAT_WRITE_HEADER FFMPEG_AVFORMAT_NEW_STREAM FFMPEG_AVCODEC_ENCODE_AUDIO2 FFMPEG_AVCODEC_ENCODE_VIDEO2 SINC BSV_MOVIE VIDEOCORE" +VARS="ALSA OSS OSS_BSD OSS_LIB AL RSOUND ROAR JACK COREAUDIO PULSE SDL OPENGL GLES VG EGL KMS GBM DRM DYLIB GETOPT_LONG THREADS CG XML SDL_IMAGE LIBPNG DYNAMIC FFMPEG AVCODEC AVFORMAT AVUTIL SWSCALE CONFIGFILE FREETYPE XVIDEO X11 XEXT XF86VM XINERAMA NETPLAY NETWORK_CMD STDIN_CMD COMMAND SOCKET_LEGACY FBO STRL PYTHON FFMPEG_ALLOC_CONTEXT3 FFMPEG_AVCODEC_OPEN2 FFMPEG_AVIO_OPEN FFMPEG_AVFORMAT_WRITE_HEADER FFMPEG_AVFORMAT_NEW_STREAM FFMPEG_AVCODEC_ENCODE_AUDIO2 FFMPEG_AVCODEC_ENCODE_VIDEO2 SINC BSV_MOVIE VIDEOCORE" create_config_make config.mk $VARS create_config_header config.h $VARS diff --git a/record/ffemu.c b/record/ffemu.c index 62bdc2be3f..52a3a882e6 100644 --- a/record/ffemu.c +++ b/record/ffemu.c @@ -25,6 +25,8 @@ extern "C" { #include #include #include +#include +#include #ifdef __cplusplus } #endif @@ -37,6 +39,7 @@ extern "C" { #include "../thread.h" #include "../general.h" #include "../gfx/scaler/scaler.h" +#include "../conf/config_file.h" #include "ffemu.h" #include @@ -60,12 +63,19 @@ struct ff_video_info uint8_t *outbuf; size_t outbuf_size; + // Output pixel format. enum PixelFormat pix_fmt; + // Input pixel format. Only used by sws. + enum PixelFormat in_pix_fmt; + + // Input pixel size. size_t pix_size; AVFormatContext *format; struct scaler_ctx scaler; + struct SwsContext *sws; + bool use_sws; }; struct ff_audio_info @@ -89,11 +99,25 @@ struct ff_muxer_info AVStream *vstream; }; +struct ff_config_param +{ + config_file_t *conf; + char vcodec[64]; + char acodec[64]; + char format[64]; + enum PixelFormat out_pix_fmt; + unsigned threads; + + AVDictionary *video_opts; + AVDictionary *audio_opts; +}; + struct ffemu { struct ff_video_info video; struct ff_audio_info audio; struct ff_muxer_info muxer; + struct ff_config_param config; struct ffemu_params params; @@ -109,9 +133,9 @@ struct ffemu volatile bool can_sleep; }; -static bool ffemu_init_audio(struct ff_audio_info *audio, struct ffemu_params *param) +static bool ffemu_init_audio(struct ff_config_param *params, struct ff_audio_info *audio, struct ffemu_params *param) { - AVCodec *codec = avcodec_find_encoder_by_name("flac"); + AVCodec *codec = avcodec_find_encoder_by_name(*params->acodec ? params->acodec : "flac"); if (!codec) return false; @@ -131,7 +155,7 @@ static bool ffemu_init_audio(struct ff_audio_info *audio, struct ffemu_params *p audio->codec->sample_fmt = AV_SAMPLE_FMT_S16; #ifdef HAVE_FFMPEG_AVCODEC_OPEN2 - if (avcodec_open2(audio->codec, codec, NULL) != 0) + if (avcodec_open2(audio->codec, codec, params->audio_opts ? ¶ms->audio_opts : NULL) != 0) #else if (avcodec_open(audio->codec, codec) != 0) #endif @@ -155,25 +179,24 @@ static bool ffemu_init_audio(struct ff_audio_info *audio, struct ffemu_params *p return true; } -static bool ffemu_init_video(struct ff_video_info *video, const struct ffemu_params *param) +static bool ffemu_init_video(struct ff_config_param *params, struct ff_video_info *video, const struct ffemu_params *param) { AVCodec *codec = NULL; - if (g_settings.video.h264_record) + if (*params->vcodec) + codec = avcodec_find_encoder_by_name(params->vcodec); + else { codec = avcodec_find_encoder_by_name("libx264rgb"); // Older versions of FFmpeg have RGB encoding in libx264. if (!codec) codec = avcodec_find_encoder_by_name("libx264"); - video->pix_fmt = PIX_FMT_BGR24; + video->pix_fmt = PIX_FMT_BGR24; video->scaler.out_fmt = SCALER_FMT_BGR24; - } - else - { - codec = avcodec_find_encoder_by_name("ffv1"); - video->pix_fmt = PIX_FMT_RGB32; - video->scaler.out_fmt = SCALER_FMT_ARGB8888; + + // By default, we want lossless video. + av_dict_set(¶ms->video_opts, "qp", "0", 0); } if (!codec) @@ -181,21 +204,49 @@ static bool ffemu_init_video(struct ff_video_info *video, const struct ffemu_par video->encoder = codec; + // Don't use swscaler unless format is not something "in-house" scaler supports. + // libswscale doesn't scale RGB -> RGB correctly (goes via YUV first), and it's non-trivial to fix + // upstream as it's heavily geared towards YUV. + // If we're dealing with strange formats or YUV, just use libswscale. + if (params->out_pix_fmt != PIX_FMT_NONE) + { + video->pix_fmt = params->out_pix_fmt; + if (video->pix_fmt != PIX_FMT_BGR24 && video->pix_fmt != PIX_FMT_RGB32) + video->use_sws = true; + + switch (video->pix_fmt) + { + case PIX_FMT_BGR24: + video->scaler.out_fmt = SCALER_FMT_BGR24; + break; + + case PIX_FMT_RGB32: + video->scaler.out_fmt = SCALER_FMT_ARGB8888; + break; + + default: + break; + } + } + switch (param->pix_fmt) { case FFEMU_PIX_RGB565: video->scaler.in_fmt = SCALER_FMT_RGB565; - video->pix_size = 2; + video->in_pix_fmt = PIX_FMT_RGB565; + video->pix_size = 2; break; case FFEMU_PIX_BGR24: video->scaler.in_fmt = SCALER_FMT_BGR24; - video->pix_size = 3; + video->in_pix_fmt = PIX_FMT_BGR24; + video->pix_size = 3; break; case FFEMU_PIX_ARGB8888: video->scaler.in_fmt = SCALER_FMT_ARGB8888; - video->pix_size = 4; + video->in_pix_fmt = PIX_FMT_RGB32; + video->pix_size = 4; break; default: @@ -215,30 +266,15 @@ static bool ffemu_init_video(struct ff_video_info *video, const struct ffemu_par video->codec->sample_aspect_ratio = av_d2q(param->aspect_ratio * param->out_height / param->out_width, 255); video->codec->pix_fmt = video->pix_fmt; -#ifdef HAVE_FFMPEG_AVCODEC_OPEN2 - AVDictionary *opts = NULL; -#endif - - if (g_settings.video.h264_record) - { - video->codec->thread_count = 3; - av_dict_set(&opts, "qp", "0", 0); - } - else - video->codec->thread_count = 2; + video->codec->thread_count = params->threads; #ifdef HAVE_FFMPEG_AVCODEC_OPEN2 - if (avcodec_open2(video->codec, codec, &opts) != 0) + if (avcodec_open2(video->codec, codec, params->video_opts ? ¶ms->video_opts : NULL) != 0) #else if (avcodec_open(video->codec, codec) != 0) #endif return false; -#ifdef HAVE_FFMPEG_AVCODEC_OPEN2 - if (opts) - av_dict_free(&opts); -#endif - // Allocate a big buffer :p ffmpeg API doesn't seem to give us some clues how big this buffer should be. video->outbuf_size = 1 << 23; video->outbuf = (uint8_t*)av_malloc(video->outbuf_size); @@ -251,11 +287,67 @@ static bool ffemu_init_video(struct ff_video_info *video, const struct ffemu_par return true; } +static bool ffemu_init_config(struct ff_config_param *params, const char *config) +{ + params->out_pix_fmt = PIX_FMT_NONE; + if (!config) + return true; + + params->conf = config_file_new(config); + if (!params->conf) + { + RARCH_ERR("Failed to load FFmpeg config \"%s\".\n", config); + return false; + } + + config_get_array(params->conf, "vcodec", params->vcodec, sizeof(params->vcodec)); + config_get_array(params->conf, "acodec", params->acodec, sizeof(params->acodec)); + config_get_array(params->conf, "format", params->format, sizeof(params->format)); + + if (!config_get_uint(params->conf, "threads", ¶ms->threads)) + params->threads = 1; + + char pix_fmt[64] = {0}; + if (config_get_array(params->conf, "pix_fmt", pix_fmt, sizeof(pix_fmt))) + { + params->out_pix_fmt = av_get_pix_fmt(pix_fmt); + if (params->out_pix_fmt == PIX_FMT_NONE) + { + RARCH_ERR("Cannot find pix_fmt \"%s\".\n", pix_fmt); + return false; + } + } + + struct config_file_entry entry; + if (!config_get_entry_list_head(params->conf, &entry)) + return true; + + do + { + if (strstr(entry.key, "video_") == entry.key) + { + const char *key = entry.key + strlen("video_"); + av_dict_set(¶ms->video_opts, key, entry.value, 0); + } + else if (strstr(entry.key, "audio_") == entry.key) + { + const char *key = entry.key + strlen("audio_"); + av_dict_set(¶ms->audio_opts, key, entry.value, 0); + } + } while (config_get_entry_list_next(&entry)); + + return true; +} + static bool ffemu_init_muxer(ffemu_t *handle) { AVFormatContext *ctx = avformat_alloc_context(); av_strlcpy(ctx->filename, handle->params.filename, sizeof(ctx->filename)); - ctx->oformat = av_guess_format(NULL, ctx->filename, NULL); + + if (*handle->config.format) + ctx->oformat = av_guess_format(handle->config.format, NULL, NULL); + else + ctx->oformat = av_guess_format(NULL, ctx->filename, NULL); if (!ctx->oformat) return false; @@ -308,8 +400,7 @@ static bool ffemu_init_muxer(ffemu_t *handle) #ifdef AVFMT_TS_NONSTRICT // Avoids a warning at end about non-monotonically increasing DTS values. // It seems to be harmless to disable this. - if (g_settings.video.h264_record) - ctx->oformat->flags |= AVFMT_TS_NONSTRICT; + ctx->oformat->flags |= AVFMT_TS_NONSTRICT; #endif av_dict_set(&ctx->metadata, "title", "RetroArch video dump", 0); @@ -401,10 +492,13 @@ ffemu_t *ffemu_new(const struct ffemu_params *params) handle->params = *params; - if (!ffemu_init_video(&handle->video, &handle->params)) + if (!ffemu_init_config(&handle->config, params->config)) goto error; - if (!ffemu_init_audio(&handle->audio, &handle->params)) + if (!ffemu_init_video(&handle->config, &handle->video, &handle->params)) + goto error; + + if (!ffemu_init_audio(&handle->config, &handle->audio, &handle->params)) goto error; if (!ffemu_init_muxer(handle)) @@ -422,36 +516,46 @@ error: void ffemu_free(ffemu_t *handle) { - if (handle) + if (!handle) + return; + + deinit_thread(handle); + deinit_thread_buf(handle); + + if (handle->audio.codec) { - deinit_thread(handle); - deinit_thread_buf(handle); - - if (handle->audio.codec) - { - avcodec_close(handle->audio.codec); - av_free(handle->audio.codec); - } - - if (handle->audio.buffer) - av_free(handle->audio.buffer); - - if (handle->video.codec) - { - avcodec_close(handle->video.codec); - av_free(handle->video.codec); - } - - if (handle->video.conv_frame) - av_free(handle->video.conv_frame); - - if (handle->video.conv_frame_buf) - av_free(handle->video.conv_frame_buf); - - scaler_ctx_gen_reset(&handle->video.scaler); - - free(handle); + avcodec_close(handle->audio.codec); + av_free(handle->audio.codec); } + + if (handle->audio.buffer) + av_free(handle->audio.buffer); + + if (handle->video.codec) + { + avcodec_close(handle->video.codec); + av_free(handle->video.codec); + } + + if (handle->video.conv_frame) + av_free(handle->video.conv_frame); + + if (handle->video.conv_frame_buf) + av_free(handle->video.conv_frame_buf); + + scaler_ctx_gen_reset(&handle->video.scaler); + + if (handle->video.sws) + sws_freeContext(handle->video.sws); + + if (handle->config.conf) + config_file_free(handle->config.conf); + if (handle->config.video_opts) + av_dict_free(&handle->config.video_opts); + if (handle->config.audio_opts) + av_dict_free(&handle->config.audio_opts); + + free(handle); } bool ffemu_push_video(ffemu_t *handle, const struct ffemu_video_data *data) @@ -594,9 +698,22 @@ static bool encode_video(ffemu_t *handle, AVPacket *pkt, AVFrame *frame) return true; } -static bool ffemu_push_video_thread(ffemu_t *handle, const struct ffemu_video_data *data) +static void ffemu_scale_input(ffemu_t *handle, const struct ffemu_video_data *data) { - if (!data->is_dupe) + // Attempt to preserve more information if we scale down. + bool shrunk = handle->params.out_width < data->width || handle->params.out_height < data->height; + + if (handle->video.use_sws) + { + handle->video.sws = sws_getCachedContext(handle->video.sws, data->width, data->height, handle->video.in_pix_fmt, + handle->params.out_width, handle->params.out_height, handle->video.pix_fmt, + shrunk ? SWS_BILINEAR : SWS_POINT, NULL, NULL, NULL); + + int linesize = data->pitch; + sws_scale(handle->video.sws, (const uint8_t* const*)&data->data, &linesize, 0, + data->height, handle->video.conv_frame->data, handle->video.conv_frame->linesize); + } + else { if ((int)data->width != handle->video.scaler.in_width || (int)data->height != handle->video.scaler.in_height) { @@ -604,8 +721,6 @@ static bool ffemu_push_video_thread(ffemu_t *handle, const struct ffemu_video_da handle->video.scaler.in_height = data->height; handle->video.scaler.in_stride = data->pitch; - // Attempt to preserve more information if we scale down. - bool shrunk = handle->params.out_width < data->width || handle->params.out_height < data->height; handle->video.scaler.scaler_type = shrunk ? SCALER_TYPE_BILINEAR : SCALER_TYPE_POINT; handle->video.scaler.out_width = handle->params.out_width; @@ -617,6 +732,12 @@ static bool ffemu_push_video_thread(ffemu_t *handle, const struct ffemu_video_da scaler_ctx_scale(&handle->video.scaler, handle->video.conv_frame->data[0], data->data); } +} + +static bool ffemu_push_video_thread(ffemu_t *handle, const struct ffemu_video_data *data) +{ + if (!data->is_dupe) + ffemu_scale_input(handle, data); handle->video.conv_frame->pts = handle->video.frame_cnt; diff --git a/record/ffemu.h b/record/ffemu.h index aff204c373..8f9fdad813 100644 --- a/record/ffemu.h +++ b/record/ffemu.h @@ -53,10 +53,14 @@ struct ffemu_params // Audio channels. unsigned channels; + // Input pixel format. enum ffemu_pix_format pix_fmt; // Filename to dump to. const char *filename; + + // Path to config. Optional. + const char *config; }; struct ffemu_video_data diff --git a/retroarch.c b/retroarch.c index fbc6215dfd..4eb8b6d8f3 100644 --- a/retroarch.c +++ b/retroarch.c @@ -659,6 +659,7 @@ static void print_help(void) #ifdef HAVE_FFMPEG puts("\t-r/--record: Path to record video file.\n\t\tUsing .mkv extension is recommended."); + puts("\t--recordconfig: Path to settings used during recording."); puts("\t--size: Overrides output video size when recording with FFmpeg (format: WIDTHxHEIGHT)."); #endif puts("\t-v/--verbose: Verbose logging."); @@ -789,6 +790,7 @@ static void parse_input(int argc, char *argv[]) { "fullscreen", 0, NULL, 'f' }, #ifdef HAVE_FFMPEG { "record", 1, NULL, 'r' }, + { "recordconfig", 1, &val, 'R' }, { "size", 1, &val, 's' }, #endif { "verbose", 0, NULL, 'v' }, @@ -1117,6 +1119,10 @@ static void parse_input(int argc, char *argv[]) } break; } + + case 'R': + strlcpy(g_extern.record_config, optarg, sizeof(g_extern.record_config)); + break; #endif case 'f': print_features(); @@ -1280,6 +1286,7 @@ static void init_recording(void) params.fps = fps; params.samplerate = samplerate; params.pix_fmt = g_extern.system.pix_fmt == RETRO_PIXEL_FORMAT_XRGB8888 ? FFEMU_PIX_ARGB8888 : FFEMU_PIX_RGB565; + params.config = *g_extern.record_config ? g_extern.record_config : NULL; if (g_settings.video.gpu_record && driver.video->read_viewport) { @@ -1326,11 +1333,6 @@ static void init_recording(void) params.out_width = g_extern.record_width; params.out_height = g_extern.record_height; } - else if (g_settings.video.hires_record) - { - params.out_width *= 2; - params.out_height *= 2; - } if (g_settings.video.force_aspect && (g_settings.video.aspect_ratio > 0.0f)) params.aspect_ratio = g_settings.video.aspect_ratio; @@ -1369,15 +1371,15 @@ static void init_recording(void) static void deinit_recording(void) { - if (g_extern.recording) - { - ffemu_finalize(g_extern.rec); - ffemu_free(g_extern.rec); - g_extern.rec = NULL; + if (!g_extern.recording) + return; - free(g_extern.record_gpu_buffer); - g_extern.record_gpu_buffer = NULL; - } + ffemu_finalize(g_extern.rec); + ffemu_free(g_extern.rec); + g_extern.rec = NULL; + + free(g_extern.record_gpu_buffer); + g_extern.record_gpu_buffer = NULL; } #endif diff --git a/retroarch.cfg b/retroarch.cfg index 7c471fffaf..5880f94dd8 100644 --- a/retroarch.cfg +++ b/retroarch.cfg @@ -393,12 +393,6 @@ # Directory to dump screenshots to. # screenshot_directory = -# Records video assuming video is hi-res. -# video_hires_record = false - -# Enables lossless RGB H.264 recording if possible (if not, FFV1 is used). -# video_h264_record = true - # Records video after CPU video filter. # video_post_filter_record = false diff --git a/settings.c b/settings.c index 16f21fea82..e0709ab33c 100644 --- a/settings.c +++ b/settings.c @@ -190,8 +190,6 @@ void config_set_defaults(void) #endif g_settings.video.refresh_rate = refresh_rate; - g_settings.video.hires_record = hires_record; - g_settings.video.h264_record = h264_record; g_settings.video.post_filter_record = post_filter_record; g_settings.video.gpu_record = gpu_record; g_settings.video.gpu_screenshot = gpu_screenshot; @@ -470,8 +468,6 @@ bool config_load_file(const char *path) } #endif - CONFIG_GET_BOOL(video.hires_record, "video_hires_record"); - CONFIG_GET_BOOL(video.h264_record, "video_h264_record"); CONFIG_GET_BOOL(video.post_filter_record, "video_post_filter_record"); CONFIG_GET_BOOL(video.gpu_record, "video_gpu_record"); CONFIG_GET_BOOL(video.gpu_screenshot, "video_gpu_screenshot");