khenriks/mp3fs — pull request #31 by robertyseward (2014-10-10 19:47:41+00:00)
Title: Proposal for adding VBR encoding

This is my proposal for adding VBR encoding. It takes khenriks' suggestion of using a pessimistic size for the file size a step further: it uses the pessimistic size (the size as if constant bit rate encoding was used) until the real file size is computed, and then caches this real file size and uses that size onwards. Always using a pessimistic size is no better than just using CBR, so is pretty useless for VBR.

With the pessimistic size, the ID3v1 tag is still at the end of the file, but with empty space between the end of the encoded data and the tag. This is necessary because MP3 readers always get the ID3v1 tag from the last 128 bytes of the file, regardless of what else is in the file.

The stat cache caches the file size, the modified time of the FLAC file, and the last time the cache entry was accessed. The modified time is used to detect when the FLAC file has changed (thus invalidating the cache entry). The access time is used to implement a least recently used cache policy, where the least recently used entries are removed when the cache gets above a threshold specified on the command line (--statcachesize). When the cache reaches the threshold, 10% of the entries are removed (instead of just one entry) because the cost of purging can be a little expensive.

One tricky thing about VBR encoding the the Xing section. This section has (among other things) the average encoding bitrate, which is used by most programs to compute the play time of the MP3. This section is part of the first frame, so it is basically at the beginning of the file. However, one doesn't know the average encoding bitrate until the entire file has been encoded. So, lame writes dummy values in this section when it is first written, and then a call is made to rewrite this section once the encoding is done. The implication of all this is that this implementation will encode the entire file for *any* read.

Because any read triggers an encoding of the entire file, performance is not that great in many real scenarios. There really needs to be a file cache (in parallel with the stats cache) to cache a small number of files (really, a cache with just one entry would solve most of the problems). But, this change was already big enough; the file cache can come later.