Can record hi-res and filtered output.

This commit is contained in:
Themaister 2011-08-11 05:25:31 +02:00
parent 7c17ede1ef
commit 9e24474047
7 changed files with 97 additions and 43 deletions

View file

@ -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)
////////////////

View file

@ -91,6 +91,9 @@ struct settings
bool force_16bit;
bool disable_composition;
bool hires_record;
bool post_filter_record;
char external_driver[256];
} video;

View file

@ -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);

View file

@ -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

View file

@ -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
View file

@ -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(&params.out_width, &params.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(&params);
if (!g_extern.rec)
{

View file

@ -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