Start adding GPU recording to FFmpeg.

This commit is contained in:
Themaister 2012-08-25 22:38:49 +02:00
parent e479a50203
commit 304615510a
6 changed files with 178 additions and 56 deletions

View File

@ -200,9 +200,12 @@ 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 SNES output.
// Record post-filtered (CPU filter) video rather than raw game output.
static const bool post_filter_record = false;
// Record post-shaded GPU output instead of raw game footage if available.
static const bool gpu_record = false;
// OSD-messages
static const bool font_enable = true;

View File

@ -127,6 +127,7 @@ struct settings
bool hires_record;
bool h264_record;
bool post_filter_record;
bool gpu_record;
bool allow_rotate;
char external_driver[PATH_MAX];
@ -447,6 +448,10 @@ struct global
bool recording;
unsigned record_width;
unsigned record_height;
uint8_t *record_gpu_buffer;
size_t record_gpu_width;
size_t record_gpu_height;
#endif
struct

View File

@ -170,16 +170,29 @@ static bool ffemu_init_video(struct ff_video_info *video, const struct ffemu_par
video->encoder = codec;
#if AV_HAVE_BIGENDIAN
video->fmt = PIX_FMT_RGB555BE;
#else
video->fmt = PIX_FMT_RGB555LE;
#endif
video->pix_size = sizeof(uint16_t);
if (param->rgb32)
switch (param->pix_fmt)
{
video->fmt = PIX_FMT_RGB32;
video->pix_size = sizeof(uint32_t);
case FFEMU_PIX_XRGB1555:
#if AV_HAVE_BIGENDIAN
video->fmt = PIX_FMT_RGB555BE;
#else
video->fmt = PIX_FMT_RGB555LE;
#endif
video->pix_size = 2;
break;
case FFEMU_PIX_BGR24:
video->fmt = PIX_FMT_BGR24;
video->pix_size = 3;
break;
case FFEMU_PIX_ARGB8888:
video->fmt = PIX_FMT_RGB32;
video->pix_size = 4;
break;
default:
return false;
}
video->pix_fmt = g_settings.video.h264_record ? PIX_FMT_BGR24 : PIX_FMT_RGB32;
@ -474,7 +487,7 @@ bool ffemu_push_video(ffemu_t *handle, const struct ffemu_video_data *data)
fifo_write(handle->attr_fifo, &attr_data, sizeof(attr_data));
unsigned offset = 0;
int offset = 0;
for (unsigned y = 0; y < attr_data.height; y++, offset += data->pitch)
fifo_write(handle->video_fifo, (const uint8_t*)data->data + offset, attr_data.pitch);

View File

@ -23,6 +23,13 @@
extern "C" {
#endif
enum ffemu_pix_format
{
FFEMU_PIX_XRGB1555 = 0,
FFEMU_PIX_BGR24,
FFEMU_PIX_ARGB8888
};
// Parameters passed to ffemu_new()
struct ffemu_params
{
@ -46,8 +53,7 @@ struct ffemu_params
// Audio channels.
unsigned channels;
// If input is ARGB or XRGB1555.
bool rgb32;
enum ffemu_pix_format pix_fmt;
// Filename to dump to.
const char *filename;
@ -58,7 +64,7 @@ struct ffemu_video_data
const void *data;
unsigned width;
unsigned height;
unsigned pitch;
int pitch;
bool is_dupe;
};

View File

@ -188,6 +188,72 @@ static void readjust_audio_input_rate(void)
// g_extern.audio_data.src_ratio, g_extern.audio_data.orig_src_ratio);
}
#ifdef HAVE_FFMPEG
static void recording_dump_frame(const void *data, unsigned width, unsigned height, size_t pitch)
{
struct ffemu_video_data ffemu_data = {0};
if (g_extern.record_gpu_buffer)
{
unsigned gpu_w = 0, gpu_h = 0;
video_viewport_size_func(&gpu_w, &gpu_h);
if (!gpu_w || !gpu_h)
{
RARCH_WARN("Viewport size calculation failed! Will continue using raw data. This will probably not work right ...\n");
free(g_extern.record_gpu_buffer);
g_extern.record_gpu_buffer = NULL;
recording_dump_frame(data, width, height, pitch);
return;
}
// User has resized. We're kinda fucked now, but we just have to go through with it.
// Resize in ffmpeg if we have to ...
if (gpu_w != g_extern.record_gpu_width || gpu_h != g_extern.record_gpu_height)
{
RARCH_WARN("Resize has taken place. Image quality in recording will now lower dramatically as output is fixed resolution.\n");
uint8_t *new_buffer = (uint8_t*)realloc(g_extern.record_gpu_buffer, gpu_w * gpu_h * 3);
if (!new_buffer)
{
RARCH_ERR("Failed to realloce GPU record buffer. Will revert back to raw data. This will probably not work right ...\n");
free(g_extern.record_gpu_buffer);
g_extern.record_gpu_buffer = NULL;
recording_dump_frame(data, width, height, pitch);
return;
}
g_extern.record_gpu_buffer = new_buffer;
g_extern.record_gpu_width = gpu_w;
g_extern.record_gpu_height = gpu_h;
}
// Big bottleneck. Also adds one frame "delay" to video output as we haven't rendered the current frame yet.
// It will probably improve performance a lot though, who knows.
video_read_viewport_func(g_extern.record_gpu_buffer);
ffemu_data.pitch = g_extern.record_gpu_width * 3;
ffemu_data.width = g_extern.record_gpu_width;
ffemu_data.height = g_extern.record_gpu_height;
ffemu_data.data = g_extern.record_gpu_buffer + (ffemu_data.height - 1) * ffemu_data.pitch;
ffemu_data.pitch = -ffemu_data.pitch;
}
else
{
ffemu_data.data = data;
ffemu_data.pitch = pitch;
ffemu_data.width = width;
ffemu_data.height = height;
ffemu_data.is_dupe = !data;
}
ffemu_push_video(g_extern.rec, &ffemu_data);
}
#endif
static void video_frame(const void *data, unsigned width, unsigned height, size_t pitch)
{
#ifndef RARCH_CONSOLE
@ -198,16 +264,8 @@ static void video_frame(const void *data, unsigned width, unsigned height, size_
// Slightly messy code,
// but we really need to do processing before blocking on VSync for best possible scheduling.
#ifdef HAVE_FFMPEG
if (g_extern.recording && (!g_extern.filter.active || !g_settings.video.post_filter_record || !data))
{
struct ffemu_video_data ffemu_data = {0};
ffemu_data.data = data;
ffemu_data.pitch = pitch;
ffemu_data.width = width;
ffemu_data.height = height;
ffemu_data.is_dupe = !data;
ffemu_push_video(g_extern.rec, &ffemu_data);
}
if (g_extern.recording && (!g_extern.filter.active || !g_settings.video.post_filter_record || !data || g_extern.record_gpu_buffer))
recording_dump_frame(data, width, height, pitch);
#endif
const char *msg = msg_queue_pull(g_extern.msg_queue);
@ -223,14 +281,7 @@ static void video_frame(const void *data, unsigned width, unsigned height, size_
#ifdef HAVE_FFMPEG
if (g_extern.recording && g_settings.video.post_filter_record)
{
struct ffemu_video_data ffemu_data = {0};
ffemu_data.data = g_extern.filter.buffer;
ffemu_data.pitch = g_extern.filter.pitch;
ffemu_data.width = owidth;
ffemu_data.height = oheight;
ffemu_push_video(g_extern.rec, &ffemu_data);
}
recording_dump_frame(g_extern.filter.buffer, owidth, oheight, g_extern.filter.pitch);
#endif
if (!video_frame_func(g_extern.filter.buffer, owidth, oheight, g_extern.filter.pitch, msg))
@ -1196,47 +1247,86 @@ static void init_recording(void)
params.filename = g_extern.record_path;
params.fps = fps;
params.samplerate = samplerate;
params.rgb32 = g_extern.system.rgb32;
params.pix_fmt = g_extern.system.rgb32 ? FFEMU_PIX_ARGB8888 : FFEMU_PIX_XRGB1555;
if (g_extern.record_width || g_extern.record_height)
if (g_settings.video.gpu_record && driver.video->read_viewport)
{
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;
}
unsigned width = 0, height = 0;
video_viewport_size_func(&width, &height);
if (g_settings.video.force_aspect && (g_settings.video.aspect_ratio > 0.0f))
params.aspect_ratio = g_settings.video.aspect_ratio;
if (!width || !height)
{
RARCH_ERR("Failed to get viewport information from video driver. "
"Cannot start recording ...\n");
g_extern.recording = false;
return;
}
params.out_width = width;
params.out_height = height;
params.fb_width = next_pow2(width);
params.out_height = next_pow2(height);
params.aspect_ratio = (float)width / height;
params.pix_fmt = FFEMU_PIX_BGR24;
g_extern.record_gpu_width = width;
g_extern.record_gpu_height = height;
RARCH_LOG("Detected viewport of %u x %u\n",
width, height);
g_extern.record_gpu_buffer = (uint8_t*)malloc(width * height * 3);
if (!g_extern.record_gpu_buffer)
{
RARCH_ERR("Failed to allocate GPU record buffer.\n");
g_extern.recording = false;
return;
}
}
else
params.aspect_ratio = (float)params.out_width / params.out_height;
if (g_settings.video.post_filter_record && g_extern.filter.active)
{
g_extern.filter.psize(&params.out_width, &params.out_height);
params.rgb32 = true;
if (g_extern.record_width || g_extern.record_height)
{
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;
}
unsigned max_width = params.fb_width;
unsigned max_height = params.fb_height;
g_extern.filter.psize(&max_width, &max_height);
params.fb_width = next_pow2(max_width);
params.fb_height = next_pow2(max_height);
if (g_settings.video.force_aspect && (g_settings.video.aspect_ratio > 0.0f))
params.aspect_ratio = g_settings.video.aspect_ratio;
else
params.aspect_ratio = (float)params.out_width / params.out_height;
if (g_settings.video.post_filter_record && g_extern.filter.active)
{
g_extern.filter.psize(&params.out_width, &params.out_height);
params.pix_fmt = FFEMU_PIX_ARGB8888;
unsigned max_width = params.fb_width;
unsigned max_height = params.fb_height;
g_extern.filter.psize(&max_width, &max_height);
params.fb_width = next_pow2(max_width);
params.fb_height = next_pow2(max_height);
}
}
RARCH_LOG("Recording with FFmpeg to %s @ %ux%u. (FB size: %ux%u 32-bit: %s)\n",
RARCH_LOG("Recording with FFmpeg to %s @ %ux%u. (FB size: %ux%u pix_fmt: %u)\n",
g_extern.record_path,
params.out_width, params.out_height,
params.fb_width, params.fb_height,
params.rgb32 ? "yes" : "no");
(unsigned)params.pix_fmt);
g_extern.rec = ffemu_new(&params);
if (!g_extern.rec)
{
RARCH_ERR("Failed to start FFmpeg recording.\n");
g_extern.recording = false;
free(g_extern.record_gpu_buffer);
g_extern.record_gpu_buffer = NULL;
}
}
@ -1246,6 +1336,9 @@ static void deinit_recording(void)
{
ffemu_finalize(g_extern.rec);
ffemu_free(g_extern.rec);
free(g_extern.record_gpu_buffer);
g_extern.record_gpu_buffer = NULL;
}
}
#endif

View File

@ -178,6 +178,7 @@ void config_set_defaults(void)
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.audio.enable = audio_enable;
g_settings.audio.out_rate = out_rate;
@ -390,6 +391,7 @@ bool config_load_file(const char *path)
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");
#ifdef HAVE_DYLIB
CONFIG_GET_STRING(video.filter_path, "video_filter");