From 9e244740479f65443caeaeec17a889bfd438d8ad Mon Sep 17 00:00:00 2001 From: Themaister Date: Thu, 11 Aug 2011 05:25:31 +0200 Subject: [PATCH] Can record hi-res and filtered output. --- config.def.h | 6 ++++++ general.h | 3 +++ record/ffemu.c | 41 ++++++++++++++++++++++++++++++----------- record/ffemu.h | 30 +++--------------------------- settings.c | 6 ++++++ ssnes.c | 48 +++++++++++++++++++++++++++++++++++++++++++----- ssnes.cfg | 6 ++++++ 7 files changed, 97 insertions(+), 43 deletions(-) diff --git a/config.def.h b/config.def.h index 22a872c014..190e41bded 100644 --- a/config.def.h +++ b/config.def.h @@ -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) //////////////// diff --git a/general.h b/general.h index 4754249e91..4e4040aa6f 100644 --- a/general.h +++ b/general.h @@ -91,6 +91,9 @@ struct settings bool force_16bit; bool disable_composition; + bool hires_record; + bool post_filter_record; + char external_driver[256]; } video; diff --git a/record/ffemu.c b/record/ffemu.c index 83049059c6..b5d0057e9f 100644 --- a/record/ffemu.c +++ b/record/ffemu.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -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); diff --git a/record/ffemu.h b/record/ffemu.h index 19fcfe142b..172384dc45 100644 --- a/record/ffemu.h +++ b/record/ffemu.h @@ -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 diff --git a/settings.c b/settings.c index d56e3f031a..96f9d0287d 100644 --- a/settings.c +++ b/settings.c @@ -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"); diff --git a/ssnes.c b/ssnes.c index a97dcc1848..005721cedf 100644 --- a/ssnes.c +++ b/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) { diff --git a/ssnes.cfg b/ssnes.cfg index 595f1d5368..92608f23dc 100644 --- a/ssnes.cfg +++ b/ssnes.cfg @@ -293,3 +293,9 @@ # Directory to dump screenshots to. # screenshot_directory = + +# Records video assuming video is hi-res. +# video_hires_record = false + +# Records video after CPU video filter. +# video_post_filter_record = false