Add config file support to FFmpeg recording.
This commit is contained in:
parent
f6e2c190ad
commit
8f9b68ad90
4
Makefile
4
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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
259
record/ffemu.c
259
record/ffemu.c
|
@ -25,6 +25,8 @@ extern "C" {
|
|||
#include <libavutil/opt.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavutil/avconfig.h>
|
||||
#include <libavutil/pixdesc.h>
|
||||
#include <libswscale/swscale.h>
|
||||
#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 <assert.h>
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
28
retroarch.c
28
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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
|
Loading…
Reference in New Issue