On figuring out the appropriate bitrate for video conversions

Sometimes I look at source code I wrote a couple of years ago, when I was high on meth, and I think to myself, “You are a fucking imbecile”. Other times it seems I was much brighter then than I am now.

Anyway, I’m watching The Flash. I downloaded episodes 1 to 14 in MP4 format (DVD Rip), and then downloaded episodes 15 to 22 in the only format I could find, which turned out to be MKV (HD Rip). Incidentally, you can find the latest episode, when it gets released, by searching Google for the string “The Flash S1E23 – 350mb – kakashihatake2”, then find the torrent.

They all play on my TV, but unfortunately the MKV sorts before the MP4, which means I have episodes 15 to 22 displaying before episodes 1 to 14. (I can change the default sort order, but the TV doesn’t persist that setting, so it would be worthwhile to force the sort order to be correct by manipulating the files.) So I figured I may as well try converting the MKV to MP4. But what bitrate to choose?

Actually I came up with something a couple of years ago, when I was high, and wrote my own little GUI that wraps ffmpeg. The formula I came to then, which is of course not my own, was buried in this code:

        /// <summary>Calculates an appropriate bitrate. (Actually this probably only applies to mp4.)</summary>
        /// <remarks>Options.EncoderOptions is dynamic. Depending on the format, it may not have a VideoBitRate
        /// or TwoPassEncoding property. We thus try to set them, then handle and ignore any exceptions.</remarks>
        private void CalculateBitrate(Size value = default(Size))
            var factor = 1;

                /* A compromise: ffmpeg is failing on pass 2 with the error: The requested bitrate is too low.
                 * This is after applying the calculation below and a factor of 1. So for two-pass encoding, 
                 * increase the factor to 2. So far this works OK. */
                if (Options.EncoderOptions.TwoPassEncoding)
                    factor = 2;
            catch { }

                /* Using the "Kush Gauge" to calculate the bitrate: 
                 * Pixel count (wxh) * fps * motion factor (1, 2, or 4) * 0.07 = bitrate/1000 
                 * If this method is passed a valid Size parameter, use it. Otherwise, if the options specify 
                 * the video is being cropped and scaled, use Options.Scale; otherwise use Options.Size. */
                var newSize = value != default(Size) ? value :
                    Options.Crop.Size == Options.Size && Options.Scale.Size != Options.Size ?
                    Options.Scale.Size : Options.Size;

                Options.EncoderOptions.VideoBitRate = ((double)newSize.Width * newSize.Height * Options.FrameRate * 0.07D * factor / 1000D).ToString("F2") + "k";
            catch { }

My implementation uses something called the “Kush Guage”, whatever that is. I remember neither reading about it nor implementing it. As you can see, my meth-high-implementation doesn’t really follow the formula exactly. But anyway, it works quite well, so I’m running my ffmpeg wrapper to do the conversion. (After doing a test on a small part of the video to confirm that the result is good quality.)

I’m using the 64-bit static build version of ffmpeg, which I found here. My generated command-line to use, for H264 MP4 that converts fairly quickly is this:

ffmpeg.exe  -i “G:\Videos\Series\The Flash S1E15.mkv” -c:v libx264 -vprofile Main -preset Faster -b:v 1546.74k -maxrate 1546.74k -bufsize 1200k -threads 0 -acodec libvo_aacenc -b:a 128k -r 23.976 -ar 44100 -ac 2 -y “G:\Videos\Series\The Flash S1E15.mp4”

That command-line should work for all MKV to MP4 conversions, where the bitrate was calculated as:

Width*Height*Frame-rate*0.07*motion-factor/1000, where motion-factor is supposed to be 1, 2, or 4, but my implementation always uses a factor of 1 (for single-pass encoding and a factor of 2 for two-pass encoding). This command-line is doing the encoding in one pass. That works out to:
1280*720*23.976*0.07/1000 = 1546.74k

Actually there were some bugs in my code, which is why I shouldn’t be sharing my ffmpeg wrapper. (Actually all my code and the compiled application is shared, but I’m not linking to it here because I have not uploaded the version with bug fixes.) For one thing, it was using a lower “max bitrate” than the specified bitrate (which totally fucked up the video quality), so I worked around that now by hacking my code to always specify the same rate for both. The code I wrote back then to generate the command line is so complicated, I can no longer understand it. Modifying it now was like changing somebody else’s code. Some guy who is both a genius and an idiot simultaneously. Yet another reason not to use crystal meth.

[Other things worth noting… The audio bitrate is based on the source rate, so that is something that should be set dynamically, depending on the source. Disabling multi-threaded encoding with “-threads 0” is probably unnecessary, but back when I wrote that code, I mostly encoded to a DivX-compatible format, and my DVD player at the time couldn’t play files properly if they were encoded with more than one thread.]

I may share my video converting application once I get to understand the code enough again to clean it up a little.

Update: According to this site, this bitrate calculation is about right for H264 mp4. But it looks like the max rate should be 150% of it, not the same value. But it gets confusing. For VBR (variable bit rate), the bitrate should be about 75% of this value. I will have to adjust my code and play around to find the optimum bitrate.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s