diff --git a/dynamic.c b/dynamic.c index 408b0292a0..ea65640873 100644 --- a/dynamic.c +++ b/dynamic.c @@ -306,6 +306,11 @@ static bool environment_cb(unsigned cmd, void *data) SSNES_LOG("GET_OVERSCAN: %u\n", (unsigned)!g_settings.video.crop_overscan); break; + case SNES_ENVIRONMENT_SET_TIMING: + g_extern.system.timing = *(const struct snes_system_timing*)data; + g_extern.system.timing_set = true; + break; + default: return false; } diff --git a/general.h b/general.h index 746ff98de8..44adfcec9b 100644 --- a/general.h +++ b/general.h @@ -213,6 +213,8 @@ struct global struct snes_geometry geom; unsigned pitch; // If 0, has classic libsnes semantics. char fullpath[MAXPATHLEN]; + struct snes_system_timing timing; + bool timing_set; } system; struct diff --git a/libsnes.hpp b/libsnes.hpp index 79ea845b1c..c88592ce07 100755 --- a/libsnes.hpp +++ b/libsnes.hpp @@ -71,6 +71,8 @@ extern "C" { #define SNES_ENVIRONMENT_SET_GEOMETRY 1 // const struct snes_geometry * -- Window geometry information for the system/game. #define SNES_ENVIRONMENT_SET_PITCH 2 // const unsigned * -- Pitch of game image. #define SNES_ENVIRONMENT_GET_OVERSCAN 3 // bool * -- Boolean value whether or not the implementation should use overscan. +#define SNES_ENVIRONMENT_SET_TIMING 4 // const struct snes_system_timing * -- Set exact timings of the system. + // Used primarily for video recording. struct snes_geometry { @@ -80,6 +82,12 @@ struct snes_geometry unsigned max_height; // Maximum possible height of system. }; +struct snes_system_timing +{ + double fps; + double sample_rate; +}; + typedef bool (*snes_environment_t)(unsigned cmd, void *data); // Must be called before calling snes_init(). diff --git a/record/ffemu.c b/record/ffemu.c index 864ff28eed..9d5c8f728b 100644 --- a/record/ffemu.c +++ b/record/ffemu.c @@ -98,7 +98,7 @@ static bool init_audio(struct audio_info *audio, struct ffemu_params *param) #endif audio->codec->sample_rate = param->samplerate; - audio->codec->time_base = (AVRational) { 1, param->samplerate }; + audio->codec->time_base = av_d2q(1.0 / param->samplerate, 1000000); audio->codec->channels = param->channels; audio->codec->sample_fmt = AV_SAMPLE_FMT_S16; @@ -162,7 +162,7 @@ static bool init_video(struct video_info *video, const struct ffemu_params *para video->codec->width = param->out_width; video->codec->height = param->out_height; - video->codec->time_base = (AVRational) { param->fps.den, param->fps.num }; + video->codec->time_base = av_d2q(1.0 / param->fps, 1000000); // Arbitrary big number. video->codec->sample_aspect_ratio = av_d2q(param->aspect_ratio * param->out_height / param->out_width, 255); video->codec->pix_fmt = video->pix_fmt; @@ -399,7 +399,7 @@ void ffemu_free(ffemu_t *handle) } } -int ffemu_push_video(ffemu_t *handle, const struct ffemu_video_data *data) +bool ffemu_push_video(ffemu_t *handle, const struct ffemu_video_data *data) { for (;;) { @@ -408,7 +408,7 @@ int ffemu_push_video(ffemu_t *handle, const struct ffemu_video_data *data) slock_unlock(handle->lock); if (!handle->alive) - return -1; + return false; if (avail >= sizeof(*data)) break; @@ -441,10 +441,10 @@ int ffemu_push_video(ffemu_t *handle, const struct ffemu_video_data *data) slock_unlock(handle->lock); scond_signal(handle->cond); - return 0; + return true; } -int ffemu_push_audio(ffemu_t *handle, const struct ffemu_audio_data *data) +bool ffemu_push_audio(ffemu_t *handle, const struct ffemu_audio_data *data) { for (;;) { @@ -453,7 +453,7 @@ int ffemu_push_audio(ffemu_t *handle, const struct ffemu_audio_data *data) slock_unlock(handle->lock); if (!handle->alive) - return -1; + return false; if (avail >= data->frames * handle->params.channels * sizeof(int16_t)) break; @@ -476,10 +476,10 @@ int ffemu_push_audio(ffemu_t *handle, const struct ffemu_audio_data *data) slock_unlock(handle->lock); scond_signal(handle->cond); - return 0; + return true; } -static int ffemu_push_video_thread(ffemu_t *handle, const struct ffemu_video_data *data) +static bool 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, handle->video.fmt, handle->params.out_width, handle->params.out_height, handle->video.pix_fmt, SWS_POINT, @@ -496,7 +496,7 @@ static int ffemu_push_video_thread(ffemu_t *handle, const struct ffemu_video_dat handle->video.outbuf_size, handle->video.conv_frame); if (outsize < 0) - return -1; + return false; AVPacket pkt; av_init_packet(&pkt); @@ -513,15 +513,15 @@ static int ffemu_push_video_thread(ffemu_t *handle, const struct ffemu_video_dat if (pkt.size > 0) { if (av_interleaved_write_frame(handle->muxer.ctx, &pkt) < 0) - return -1; + return false; } handle->video.frame_cnt++; - return 0; + return true; } -static int ffemu_push_audio_thread(ffemu_t *handle, const struct ffemu_audio_data *data) +static bool ffemu_push_audio_thread(ffemu_t *handle, const struct ffemu_audio_data *data) { size_t written_frames = 0; while (written_frames < data->frames) @@ -545,7 +545,7 @@ static int ffemu_push_audio_thread(ffemu_t *handle, const struct ffemu_audio_dat int out_size = avcodec_encode_audio(handle->audio.codec, handle->audio.outbuf, handle->audio.outbuf_size, handle->audio.buffer); if (out_size < 0) - return -1; + return false; pkt.size = out_size; @@ -558,14 +558,14 @@ static int ffemu_push_audio_thread(ffemu_t *handle, const struct ffemu_audio_dat if (pkt.size > 0) { if (av_interleaved_write_frame(handle->muxer.ctx, &pkt) < 0) - return -1; + return false; } } } - return 0; + return true; } -int ffemu_finalize(ffemu_t *handle) +bool ffemu_finalize(ffemu_t *handle) { deinit_thread(handle); @@ -642,7 +642,7 @@ int ffemu_finalize(ffemu_t *handle) // Write final data. av_write_trailer(handle->muxer.ctx); - return 0; + return true; } static void ffemu_thread(void *data) diff --git a/record/ffemu.h b/record/ffemu.h index 172384dc45..7afae44ba4 100644 --- a/record/ffemu.h +++ b/record/ffemu.h @@ -8,15 +8,14 @@ extern "C" { #endif -struct ffemu_rational -{ - unsigned num; - unsigned den; -}; - // Parameters passed to ffemu_new() struct ffemu_params { + // FPS of input video. + double fps; + // Sample rate of input audio. + double samplerate; + // Desired output resolution. unsigned out_width; unsigned out_height; @@ -24,38 +23,17 @@ struct ffemu_params // Total size of framebuffer used in input. unsigned fb_width; unsigned fb_height; + + // Aspect ratio of input video. Parameters are passed to the muxer, + // the video itself is not scaled. float aspect_ratio; - // FPS of video input. - struct ffemu_rational fps; - - // Relative video quality. 0 is lossless (if available), 10 is very low quality. - // A value over 10 is codec defined if it will give even worse quality. - unsigned videoq; - - // Define some video codec dependent option. (E.g. h264 profiles) - uint64_t video_opt; - - // Audio sample rate. - unsigned samplerate; - // 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; - - // Relative audio quality. 0 is lossless (if available), 10 is very low quality. - // A value over 10 is codec defined if it will give even worse quality. - // Some codecs might ignore this (lossless codecs such as FLAC). - unsigned audioq; - - // Define some audio codec dependent option. - uint64_t audio_opt; - // Filename to dump to. const char *filename; }; @@ -79,10 +57,9 @@ typedef struct ffemu ffemu_t; ffemu_t *ffemu_new(const struct ffemu_params *params); void ffemu_free(ffemu_t* handle); -int ffemu_push_video(ffemu_t *handle, const struct ffemu_video_data *data); -int ffemu_push_audio(ffemu_t *handle, const struct ffemu_audio_data *data); -int ffemu_finalize(ffemu_t *handle); - +bool ffemu_push_video(ffemu_t *handle, const struct ffemu_video_data *data); +bool ffemu_push_audio(ffemu_t *handle, const struct ffemu_audio_data *data); +bool ffemu_finalize(ffemu_t *handle); #ifdef __cplusplus } diff --git a/ssnes.c b/ssnes.c index 653de2b5d7..f350274861 100644 --- a/ssnes.c +++ b/ssnes.c @@ -958,20 +958,27 @@ static void init_recording(void) if (!g_extern.recording) return; - // Not perfectly 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}; + // Canonical values. + double fps = psnes_get_region() == SNES_REGION_NTSC ? 60.00 : 50.00; + double samplerate = 32000.0; + if (g_extern.system.timing_set) + { + fps = g_extern.system.timing.fps; + samplerate = g_extern.system.timing.sample_rate; + SSNES_LOG("Custom timing given: FPS: %.4lf, Sample rate: %.4lf\n", fps, samplerate); + } + struct ffemu_params params = { .out_width = g_extern.system.geom.base_width, .out_height = g_extern.system.geom.base_height, .fb_width = g_extern.system.geom.max_width, .fb_height = g_extern.system.geom.max_height, .channels = 2, - .samplerate = 32000, .filename = g_extern.record_path, - .fps = psnes_get_region() == SNES_REGION_NTSC ? ntsc_fps : pal_fps, + + .fps = fps, + .samplerate = samplerate, + .rgb32 = false, };