I tried encoding a video from a bk2 file, and while the video file has the expected total duration and what seems to be a correct framerate, the total number of frames is smaller than the number of frames in the bk2 file. Take
User movie #638529018325843149 for example. The bk2 contains 90741 frames (which, given the duration of 1517.80 s, implies a framerate of 59.7845 fps). The encoded video, however, contains 90655 frames for the same total duration, leading to a framerate of 59.7275 fps. Is this expected?
I'm using BizHawk 2.9.1 on Linux, Gambatte core, FFmpeg lossless Matroska encoding. Reproduction instructions and more analysis follow.
- Download User movie #638529018325843149.
- Run BizHawk as ./EmuHawkMono.sh --movie=kwirk-headingout-90741.bk2 kwirk.gb.
- Emulation→Pause and File→Movie→Play from Beginning.
- File→Movie→On Movie End...→✓Pause.
- File→AVI/WAV→Config and Record AVI/WAV....
- Click OK at "Most of these options will cause crashes on Linux."
- Select FFmpeg writer and leave Sync to Audio checked. Click OK.
- Select Matroska Lossless. Command should be -c:a pcm_s16le -c:v libx264rgb -qp 0 -pix_fmt rgb24 -f matroska. Click OK.
- Choose an output filename (I used tmp.mkv) and click Save.
- Uncheck Config→Speed/Skip→Clock Throttle, then uncheck Emulation→Pause. Let the movie play until it auto-pauses at frame 90741.
- File→AVI/WAV→Stop AVI/WAV
These are the stats from Header.txt in the bk2 file:
CycleCount 3183059634
ClockRate 2097152
3183059634 / 2097152 yields a total duration of 1517.80 s, which matches what's on the userfile page.
90741 frames divided by that duration gives a framerate of
59.7845 fps.
The encoded video file has the expected duration of 1517.80 s, but counting frames reveals that the video stream has just
90655 frames, and a framerate of
59.7275 fps (some ffprobe output omitted):
$ ffprobe -show_streams -count_frames tmp.mkv
Input #0, matroska,webm, from 'tmp.mkv':
Metadata:
ENCODER : Lavf59.27.100
Duration: 00:25:17.81, start: 0.000000, bitrate: 1514 kb/s
Stream #0:0: Video: h264 (High 4:4:4 Predictive), gbrp(pc, gbr/unknown/unknown, progressive), 160x144 [SAR 1:1 DAR 10:9], 59.73 fps, 59.73 tbr, 1k tbn
Metadata:
ENCODER : Lavc59.37.100 libx264rgb
DURATION : 00:25:17.810000000
Stream #0:1: Audio: pcm_s16le, 44100 Hz, 2 channels, s16, 1411 kb/s
Metadata:
ENCODER : Lavc59.37.100 pcm_s16le
DURATION : 00:25:17.801000000
[STREAM]
index=0
r_frame_rate=23891/400
avg_frame_rate=23891/400
nb_read_frames=90655
[/STREAM]
(Note, however, that the
audio stream has the expected 90741 frames):
[STREAM]
index=1
r_frame_rate=0/0
avg_frame_rate=0/0
nb_read_frames=90741
[/STREAM]
I am not sure where the 59.7845 fps (derived from the cycle counts in Header.txt) originally stems from, but the 59.7275 fps looks to be attributable directly to Gambatte.cs, as well as being attested in online documentation about Game Boy hardware.
https://github.com/TASEmulators/BizHawk/blob/3c1248547f5c8116152a041a43d8e806419dc0fe/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs#L267-L275
Language: csharp
/// <summary>
/// the nominal length of one frame
/// </summary>
private const uint TICKSINFRAME = 35112;
/// <summary>
/// number of ticks per second
/// </summary>
private const uint TICKSPERSECOND = 2097152;
2097152 / 35112 = 59.72750057..., which as a ratio also matches
https://mgba-emu.github.io/gbdoc/#hardware's 4194304 cycles per second divided by 70224 cycles per frame.
In summary, with lossless video encoding, I expected there to be exactly one video frame for each frame in the bk2 file. Instead, the encoded video file has fewer frames for the same total duration (which implies a somewhat lower framerate and that some frames must have been discarded). I am not sure if this is expected behavior or not, and I would appreciate some guidance before doing a lot of experiments with different cores, etc. Any hints?
The reason I noticed this at all is that I had a Lua script to write out the values of some status variables on each frame, with the intention of adding a tracker UI in postprocessing by matching up the status variable with the video, frame by frame. But because the video did not represent every frame in the input file, the tracker very slowly drifted out of sync with the game.