mirror of
https://github.com/libretro/RetroArch
synced 2025-02-08 15:40:14 +00:00
Can record hi-res and filtered output.
This commit is contained in:
parent
7c17ede1ef
commit
9e24474047
@ -157,6 +157,12 @@ 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;
|
||||
|
||||
// Record post-filtered (CPU filter) video rather than raw SNES output.
|
||||
static const bool post_filter_record = false;
|
||||
|
||||
#define SNES_ASPECT_RATIO (4.0/3)
|
||||
|
||||
////////////////
|
||||
|
@ -91,6 +91,9 @@ struct settings
|
||||
bool force_16bit;
|
||||
bool disable_composition;
|
||||
|
||||
bool hires_record;
|
||||
bool post_filter_record;
|
||||
|
||||
char external_driver[256];
|
||||
} video;
|
||||
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include <libavutil/opt.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libswscale/swscale.h>
|
||||
#include <libavutil/avconfig.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
@ -25,6 +26,9 @@ struct video_info
|
||||
uint8_t *outbuf;
|
||||
size_t outbuf_size;
|
||||
|
||||
int fmt;
|
||||
int pix_size;
|
||||
|
||||
AVFormatContext *format;
|
||||
|
||||
struct SwsContext *sws_ctx;
|
||||
@ -41,8 +45,6 @@ struct audio_info
|
||||
|
||||
void *outbuf;
|
||||
size_t outbuf_size;
|
||||
|
||||
int pix_fmt;
|
||||
} audio;
|
||||
|
||||
struct muxer_info
|
||||
@ -91,7 +93,7 @@ static bool init_audio(struct audio_info *audio, struct ffemu_params *param)
|
||||
if (!audio->buffer)
|
||||
return false;
|
||||
|
||||
audio->outbuf_size = 200000;
|
||||
audio->outbuf_size = 2000000;
|
||||
audio->outbuf = av_malloc(audio->outbuf_size);
|
||||
if (!audio->outbuf)
|
||||
return false;
|
||||
@ -105,6 +107,18 @@ static bool init_video(struct video_info *video, struct ffemu_params *param)
|
||||
if (!codec)
|
||||
return false;
|
||||
|
||||
#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)
|
||||
{
|
||||
video->fmt = PIX_FMT_RGB32;
|
||||
video->pix_size = sizeof(uint32_t);
|
||||
}
|
||||
|
||||
video->codec = avcodec_alloc_context();
|
||||
video->codec->width = param->out_width;
|
||||
video->codec->height = param->out_height;
|
||||
@ -116,7 +130,7 @@ static bool init_video(struct video_info *video, struct ffemu_params *param)
|
||||
return false;
|
||||
|
||||
// Allocate a big buffer :p ffmpeg API doesn't seem to give us some clues how big this buffer should be.
|
||||
video->outbuf_size = 2000000;
|
||||
video->outbuf_size = 1 << 23;
|
||||
video->outbuf = av_malloc(video->outbuf_size);
|
||||
|
||||
// Just to make sure we can handle the biggest frames. Seemed to crash with just 256 * 224.
|
||||
@ -177,7 +191,8 @@ static bool init_thread(ffemu_t *handle)
|
||||
assert(handle->cond = SDL_CreateCond());
|
||||
assert(handle->audio_fifo = fifo_new(32000 * sizeof(int16_t) * handle->params.channels * MAX_FRAMES / 60));
|
||||
assert(handle->attr_fifo = fifo_new(sizeof(struct ffemu_video_data) * MAX_FRAMES));
|
||||
assert(handle->video_fifo = fifo_new(handle->params.fb_width * handle->params.fb_height * sizeof(int16_t) * MAX_FRAMES));
|
||||
assert(handle->video_fifo = fifo_new(handle->params.fb_width * handle->params.fb_height *
|
||||
handle->video.pix_size * MAX_FRAMES));
|
||||
|
||||
handle->alive = true;
|
||||
handle->can_sleep = true;
|
||||
@ -365,18 +380,20 @@ int ffemu_push_audio(ffemu_t *handle, const struct ffemu_audio_data *data)
|
||||
|
||||
static int ffemu_push_video_thread(ffemu_t *handle, const struct ffemu_video_data *data)
|
||||
{
|
||||
handle->video.sws_ctx = sws_getCachedContext(handle->video.sws_ctx, data->width, data->height, PIX_FMT_RGB555LE,
|
||||
handle->video.sws_ctx = sws_getCachedContext(handle->video.sws_ctx, data->width, data->height, handle->video.fmt,
|
||||
handle->params.out_width, handle->params.out_height, PIX_FMT_RGB32, SWS_POINT,
|
||||
NULL, NULL, NULL);
|
||||
|
||||
int linesize = data->pitch;
|
||||
|
||||
sws_scale(handle->video.sws_ctx, (const uint8_t* const*)&data->data, &linesize, 0, handle->params.out_width, handle->video.conv_frame->data, handle->video.conv_frame->linesize);
|
||||
sws_scale(handle->video.sws_ctx, (const uint8_t* const*)&data->data, &linesize, 0,
|
||||
handle->video.codec->height, handle->video.conv_frame->data, handle->video.conv_frame->linesize);
|
||||
|
||||
handle->video.conv_frame->pts = handle->video.frame_cnt;
|
||||
handle->video.conv_frame->display_picture_number = handle->video.frame_cnt;
|
||||
|
||||
int outsize = avcodec_encode_video(handle->video.codec, handle->video.outbuf, handle->video.outbuf_size, handle->video.conv_frame);
|
||||
int outsize = avcodec_encode_video(handle->video.codec, handle->video.outbuf,
|
||||
handle->video.outbuf_size, handle->video.conv_frame);
|
||||
|
||||
if (outsize < 0)
|
||||
return -1;
|
||||
@ -387,7 +404,8 @@ static int ffemu_push_video_thread(ffemu_t *handle, const struct ffemu_video_dat
|
||||
pkt.data = handle->video.outbuf;
|
||||
pkt.size = outsize;
|
||||
|
||||
pkt.pts = av_rescale_q(handle->video.codec->coded_frame->pts, handle->video.codec->time_base, handle->muxer.vstream->time_base);
|
||||
pkt.pts = av_rescale_q(handle->video.codec->coded_frame->pts, handle->video.codec->time_base,
|
||||
handle->muxer.vstream->time_base);
|
||||
|
||||
if (handle->video.codec->coded_frame->key_frame)
|
||||
pkt.flags |= AV_PKT_FLAG_KEY;
|
||||
@ -454,7 +472,7 @@ int ffemu_finalize(ffemu_t *handle)
|
||||
deinit_thread(handle);
|
||||
|
||||
// Push out frames still stuck in queue.
|
||||
uint16_t *video_buf = av_malloc(handle->params.fb_width * handle->params.fb_height * sizeof(uint16_t));
|
||||
void *video_buf = av_malloc(2 * handle->params.fb_width * handle->params.fb_height * handle->video.pix_size);
|
||||
if (video_buf)
|
||||
{
|
||||
struct ffemu_video_data attr_buf;
|
||||
@ -524,7 +542,8 @@ static int SDLCALL ffemu_thread(void *data)
|
||||
{
|
||||
ffemu_t *ff = data;
|
||||
|
||||
uint16_t *video_buf = av_malloc(ff->params.fb_width * ff->params.fb_height * sizeof(uint16_t));
|
||||
// For some reason, FFmpeg has a tendency to crash if we don't overallocate a bit. :s
|
||||
void *video_buf = av_malloc(2 * ff->params.fb_width * ff->params.fb_height * ff->video.pix_size);
|
||||
assert(video_buf);
|
||||
|
||||
size_t audio_buf_size = 128 * ff->params.channels * sizeof(int16_t);
|
||||
|
@ -8,29 +8,6 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Available video codecs
|
||||
typedef enum ffemu_video_codec
|
||||
{
|
||||
FFEMU_VIDEO_FFV1,
|
||||
} ffemu_video_codec;
|
||||
|
||||
// Available audio codecs
|
||||
typedef enum ffemu_audio_codec
|
||||
{
|
||||
FFEMU_AUDIO_VORBIS,
|
||||
} ffemu_audio_codec;
|
||||
|
||||
// Available pixel formats
|
||||
typedef enum ffemu_pixel_format
|
||||
{
|
||||
FFEMU_FMT_XBGR1555,
|
||||
} ffemu_pixel_format;
|
||||
|
||||
typedef enum ffemu_rescaler
|
||||
{
|
||||
FFEMU_RESCALER_POINT
|
||||
} ffemu_rescaler;
|
||||
|
||||
struct ffemu_rational
|
||||
{
|
||||
unsigned num;
|
||||
@ -40,9 +17,6 @@ struct ffemu_rational
|
||||
// Parameters passed to ffemu_new()
|
||||
struct ffemu_params
|
||||
{
|
||||
// Video codec to use. If not recording video, select FFEMU_VIDEO_NONE.
|
||||
ffemu_video_codec vcodec;
|
||||
|
||||
// Desired output resolution.
|
||||
unsigned out_width;
|
||||
unsigned out_height;
|
||||
@ -68,6 +42,9 @@ struct ffemu_params
|
||||
// Audio channels.
|
||||
unsigned channels;
|
||||
|
||||
// If input is ARGB or XRGB1555.
|
||||
bool rgb32;
|
||||
|
||||
// Audio bits. Sample format is always signed PCM in native byte order.
|
||||
//unsigned bits;
|
||||
|
||||
@ -107,7 +84,6 @@ int ffemu_push_audio(ffemu_t *handle, const struct ffemu_audio_data *data);
|
||||
int ffemu_finalize(ffemu_t *handle);
|
||||
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
@ -142,6 +142,9 @@ static void set_defaults(void)
|
||||
g_settings.video.second_pass_smooth = second_pass_smooth;
|
||||
#endif
|
||||
|
||||
g_settings.video.hires_record = hires_record;
|
||||
g_settings.video.post_filter_record = post_filter_record;
|
||||
|
||||
g_settings.audio.enable = audio_enable;
|
||||
g_settings.audio.out_rate = out_rate;
|
||||
g_settings.audio.in_rate = in_rate;
|
||||
@ -325,6 +328,9 @@ static void parse_config_file(void)
|
||||
CONFIG_GET_DOUBLE(video.msg_pos_y, "video_message_pos_y");
|
||||
#endif
|
||||
|
||||
CONFIG_GET_BOOL(video.hires_record, "video_hires_record");
|
||||
CONFIG_GET_BOOL(video.post_filter_record, "video_post_filter_record");
|
||||
|
||||
#ifdef HAVE_DYLIB
|
||||
CONFIG_GET_STRING(video.filter_path, "video_filter");
|
||||
CONFIG_GET_STRING(video.external_driver, "video_external_driver");
|
||||
|
48
ssnes.c
48
ssnes.c
@ -142,14 +142,16 @@ static void video_frame(const uint16_t *data, unsigned width, unsigned height)
|
||||
if (g_extern.do_screenshot)
|
||||
take_screenshot(data, width, height);
|
||||
|
||||
// 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)
|
||||
if (g_extern.recording && (!g_extern.filter.active || !g_settings.video.post_filter_record))
|
||||
{
|
||||
struct ffemu_video_data ffemu_data = {
|
||||
.data = data,
|
||||
.pitch = height == 448 || height == 478 ? 1024 : 2048,
|
||||
.width = width,
|
||||
.height = height
|
||||
.height = height,
|
||||
};
|
||||
ffemu_push_video(g_extern.rec, &ffemu_data);
|
||||
}
|
||||
@ -165,6 +167,20 @@ static void video_frame(const uint16_t *data, unsigned width, unsigned height)
|
||||
g_extern.filter.psize(&owidth, &oheight);
|
||||
g_extern.filter.prender(g_extern.filter.colormap, g_extern.filter.buffer,
|
||||
g_extern.filter.pitch, data, (height == 448 || height == 478) ? 1024 : 2048, width, height);
|
||||
|
||||
#ifdef HAVE_FFMPEG
|
||||
if (g_extern.recording && g_settings.video.post_filter_record)
|
||||
{
|
||||
struct ffemu_video_data ffemu_data = {
|
||||
.data = g_extern.filter.buffer,
|
||||
.pitch = g_extern.filter.pitch,
|
||||
.width = owidth,
|
||||
.height = oheight,
|
||||
};
|
||||
ffemu_push_video(g_extern.rec, &ffemu_data);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!driver.video->frame(driver.video_data, g_extern.filter.buffer, owidth, oheight, g_extern.filter.pitch, msg))
|
||||
g_extern.video_active = false;
|
||||
}
|
||||
@ -792,9 +808,11 @@ static inline void save_files(void)
|
||||
#ifdef HAVE_FFMPEG
|
||||
static void init_recording(void)
|
||||
{
|
||||
// Hardcode these options at the moment. Should be specificed in the config file later on.
|
||||
if (g_extern.recording)
|
||||
{
|
||||
// Not perfectly SNES accurate, but this can be adjusted later during processing
|
||||
// and playback losslessly, and we please the containers more by
|
||||
// using "sane" values.
|
||||
struct ffemu_rational ntsc_fps = {60000, 1000};
|
||||
struct ffemu_rational pal_fps = {50000, 1000};
|
||||
struct ffemu_params params = {
|
||||
@ -806,9 +824,29 @@ static void init_recording(void)
|
||||
.samplerate = 32000,
|
||||
.filename = g_extern.record_path,
|
||||
.fps = psnes_get_region() == SNES_REGION_NTSC ? ntsc_fps : pal_fps,
|
||||
.aspect_ratio = 4.0/3
|
||||
.aspect_ratio = 4.0 / 3,
|
||||
.rgb32 = false,
|
||||
};
|
||||
SSNES_LOG("Recording with FFmpeg to %s.\n", g_extern.record_path);
|
||||
|
||||
if (g_settings.video.hires_record)
|
||||
{
|
||||
params.out_width = 512;
|
||||
params.out_height = 448;
|
||||
}
|
||||
|
||||
if (g_settings.video.post_filter_record && g_extern.filter.active)
|
||||
{
|
||||
g_extern.filter.psize(¶ms.out_width, ¶ms.out_height);
|
||||
params.rgb32 = true;
|
||||
|
||||
unsigned max_width = 512;
|
||||
unsigned max_height = 512;
|
||||
g_extern.filter.psize(&max_width, &max_height);
|
||||
params.fb_width = g_extern.filter.pitch >> 2;
|
||||
params.fb_height = next_pow2(max_height);
|
||||
}
|
||||
|
||||
SSNES_LOG("Recording with FFmpeg to %s @ %ux%u. (FB size: %ux%u 32-bit: %s)\n", g_extern.record_path, params.out_width, params.out_height, params.fb_width, params.fb_height, params.rgb32 ? "yes" : "no");
|
||||
g_extern.rec = ffemu_new(¶ms);
|
||||
if (!g_extern.rec)
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user