[SDL Mixer X] Seek and skip: review, clarifications, improvements, additional features
Posted: 19 Jan 2019, 19:16
I use the terms 'seeking' for 'absolute seeking' and 'skipping' for 'relative seeking'.
OK, so it seems that there is no documentation for SDL_Mixer 2 and that the doc to which SDL_Mixer 2 points is in fact the same SDL_Mixer 1.2 documentation : https://www.libsdl.org/projects/SDL_mixer/docs/SDL_mixer.html#SEC65
, but the code is indeed as you say: always absolute.
However, I think the Mikmod version does not use seconds but straight 'positions', which seem to be some kind of ticks (like MIDI). It does not seem to be pattern numbers as SDL_Mixer (1.2) documentation says, but rather a row number inside a pattern.
I did a quick investigation last night, and took the following notes. I was interested in behaviour when seeking out of bounds, which is unspecified (undocumented) by SDL Mixer and by most underlying libraries too. It may not be very important when doing pure seeking which has little reason to seek out of bounds, but it is useful to have a fixed specified behaviour when doing some skipping.
As you can see, it is completely inconsistent
(It is possible I overlooked or misunderstood a few points as I did it quickly, just by reading code and not by testing, but well, the general picture should be rather correct.)
That's why, I'd still like to add properly specified functions, possibly configurable, like
I don't have a definitive API in mind, but something like that.
Oh, that's your work in the official SDL_mixer too?
In fact, my understanding of my readings on this matter (very fresh readings, I am no expert at all) is that it is not so complicated.
From what I understood:
So, for regular MP3 files (including VBR) each frame has a fixed time duration, all frames have the same.
The only small difficulty is if we want to handle irregular Frankenstein files, which have different MPEG version, or different sample rates, in some frames.
But is not very difficult either, since now we already have to parse the full file when we load it in order to calculate its duration (in your
Then we can seek directly to the right frame in regular MP3 files, and almost directly for Frankenstein MP3 files (the difference between Frankenstein and regular files has also been very simply detected at the first parsing time, during file duration calculation). No need to decode again each frame, and the next, and so on, until we find the right one to perform a seek.
I have written code for that, but I have been doing something else since, so I need to review it again, and it probably requires some testing and cleaning before I show it. Just wait a few days
well, I hope 
About `Mix_MusicSetPosition`, in the fact it's always used for absolute position and nothing relative, and always in seconds unit. Even on the side of official SDL Mixer this function works for absolute seconds position. Maybe, in the past it had some crap, but on the moment when I have picked it up initially, it had the sense "absolute seconds position".
OK, so it seems that there is no documentation for SDL_Mixer 2 and that the doc to which SDL_Mixer 2 points is in fact the same SDL_Mixer 1.2 documentation : https://www.libsdl.org/projects/SDL_mixer/docs/SDL_mixer.html#SEC65
, but the code is indeed as you say: always absolute. However, I think the Mikmod version does not use seconds but straight 'positions', which seem to be some kind of ticks (like MIDI). It does not seem to be pattern numbers as SDL_Mixer (1.2) documentation says, but rather a row number inside a pattern.
I did a quick investigation last night, and took the following notes. I was interested in behaviour when seeking out of bounds, which is unspecified (undocumented) by SDL Mixer and by most underlying libraries too. It may not be very important when doing pure seeking which has little reason to seek out of bounds, but it is useful to have a fixed specified behaviour when doing some skipping.
As you can see, it is completely inconsistent
(It is possible I overlooked or misunderstood a few points as I did it quickly, just by reading code and not by testing, but well, the general picture should be rather correct.)
Code: Select all
| seek type | seek < 0 | seek > end | notes
-------------------------------------------------------------------------------------------
flac | absolute | unsigned^1 | no action | libFLAC behaviour undocumented
fluidsynth | n/a | n/a | n/a |
gme | absolute | bug^3 | to end + err? | libGME behaviour undocumented
mad | | unsigned^1 | to end + err | no dedicated lib function
midi_adl | absolute | no action | seek to 0 | never returns errors
midi_opn | absolute | no action | seek to 0 | never returns errors
mikmod | abs tick pos| unsigned^1 | to end | libmikmod behaviour undocumented^4, no error possible
modplug | absolute | unsigned^1 | to end | no doc at all for libmodplug
mpg123 | absolute^2 | seek to 0 | ? | libmpg123 behaviour undocumented
nativemidi | n/a | n/a | n/a |
ogg | absolute | no action+err | no action+err | libvorbis behaviour not clearly documented
opus | absolute | no action+err | no action+err | libopusfile behaviour not clearly documented
timidity | absolute | unsigned^5 | to end | no doc at all
wav | absolute | variable!^6 | no action+err |
^1: music_flac casts negative double to unsigned int without any check! -> UB
music_mad casts negative double to unsigned long without any check! -> UB
music_mikmod casts negative double to unsigned short without any check -> UB
libmodplug casts negative double to uint32_t without any check -> UB
music_timidity casts negative double to Uint32 without any check -> UB
^2: the underlying library also supports relative seeking.
^3: it seems that no check is performed before the argument is used in
various calculations and loop conditions, so it probably does... whatever.
^4: bad comment in music_mikmod.c, refering to time (seconds) instead of
pattern rows.
^5: timidity casts double to unsigned and then back to signed -> UB
^6: if the underlying SDL_RWops is a file (stdio or windows_file) seeking
below zero will result in: no action taken + error emitted. But if it is a
memory buffer, then the result is: seeking to 0!
That's why, I'd still like to add properly specified functions, possibly configurable, like
[Seek/Skip]Seconds(..., double seconds, enum seek_bounds mode), which operate on seconds for really all formats, and with enum seek_bounds being SB_SATURATE for seeking/skipping to 0 for when seeking/skipping below 0, and to the end (or just before?) when seeking/skipping beyond the end, SB_ERROR for doing nothing and returning an error when seeking/skipping out of bounds, SB_SATURATE_ERROR for performing the same action as SB_SATURATE and signalling an error as SB_ERROR too.I don't have a definitive API in mind, but something like that.
MAD MP3 hadn't seekability originally, I have implemented my own seeker which does two algorithms in dependence from seek the direction (when position is going before, drop position into begin and seek frames forward; but when it going after, just skip some frames)
Oh, that's your work in the official SDL_mixer too?
Yeah, MP3 is a mess to seek, but it's possible.
In fact, my understanding of my readings on this matter (very fresh readings, I am no expert at all) is that it is not so complicated.
From what I understood:
- MP3 frames have a fixed length: always the same number of samples;
- this number only depend on the MPEG version used;
- in a MP3 stream/file, all frames should have the same MPEG version and the same sample rate (otherwise it's a Frankenstein MP3 as a library called them);
- same sample rate + same number of samples / frame => all frames in a MP3 stream/file should have the same time duration;
- contrarily to what is written in
music_mad.ccomments, the bitrate doesn't matter (it just means a frame will occupy more or less bytes, but the duration it describes remains constant), so VBR MP3 can be handled the same as CBR.
So, for regular MP3 files (including VBR) each frame has a fixed time duration, all frames have the same.
The only small difficulty is if we want to handle irregular Frankenstein files, which have different MPEG version, or different sample rates, in some frames.
But is not very difficult either, since now we already have to parse the full file when we load it in order to calculate its duration (in your
calculate_total_time() function). We just have to store a table of frames timestamps at the same time, those time points are already calculated anyway. Then we also need to store the offsets to which they refer in the source stream/buffer, which is a bit more annoying (because of the way music_mad operates on a small buffer and not directly on the whole file buffer) but doable.Then we can seek directly to the right frame in regular MP3 files, and almost directly for Frankenstein MP3 files (the difference between Frankenstein and regular files has also been very simply detected at the first parsing time, during file duration calculation). No need to decode again each frame, and the next, and so on, until we find the right one to perform a seek.
I have written code for that, but I have been doing something else since, so I need to review it again, and it probably requires some testing and cleaning before I show it. Just wait a few days
well, I hope 
so I don't know if it is very useful... On the other hand, that's now the only program in those I tested (Windows: Winamp, SMPlayer, VLC, Linux : mplayer, cmus) which calculate the correct time duration of a Frankenstein MP3, all those existing players show a wrong duration!
but except Winamp, the others play it with the correct sound. Winamp and SDL_mixer don't: in my test, I have a 48 kHz piece followed by a a 44,1 kHz one, and the second plays too high-pitched because Winamp and SDL_mixer play the 44,1 kHz piece as if it was 48 kHz. For a player, it is good to be able to play all files correctly, even if they are not standard compliant; for a game library, it is not so needed since you are supposed to control your input assets in general (and convert them to a proper usable format if they are not). On the other hand, my own use case is for a player...
Good luck seeking (with the cursor) to a specific point in time with that
