Video Streaming Over Dialup

Date: Message-Id:

Assuming modern codecs and computing power, could we have video sharing sites over a dialup connection and have it be watchable?

Turns out, yes! Kinda!

I’m defining dialup as “50 kilobits per second”, or 6250 bytes per second. I am but a 23 year old who has never actually used it, so I’m going off Some Site I Found to tell me that 50 kilobits is an achievable, if optimistic, speed.

The only way we have a hope of pulling this off is if we choose modern codecs that are good at sounding Not Terrible at Very Low bitrates.

So let’s do opus and AV1. AV1 might be worse than h265 at very low bitrate, I don’t know, and I’m not testing it because fuck software patents.

Since I’m using AV1, if you’re on a browser that doesn’t support that, it will not work and you can go get a better browser.

(Also, if things don’t seem to work at first, try refreshing, I’ve noticed that helps. Not sure what causes it, sorry!)

If you’re on Firefox Mobile, then you can set media.av1.enabled in about:config, and if you’re on Edge, apparently there’s an AV1 Video Extension on I’ve not tested that though.

I’ll provide a mp4 encode of the final video though, but it of course won’t be 50kbit/s.

All audio and video content below is from A CD-R, that’s only partly a CD-R [Ricoh Encryptease] by Cathode Ray Dude, licensed under Creative Commons Attribution 4.0 (as seen in the end of the video). All changes made are purely encoding using opus/AV1 and taking the first segment of the video.

First off, the easy bit. Audio.

Audio, we only really have one knob to turn, and that’s the bitrate.

Below I have encoded audio using a variety of bitrates, from 4k to 16k.


This is a subjective thing, but to my ears, 12k is the best mix of quality and not being Awful to listen to.

Note that audio is more important than video. Bad quality audio is grating to listen to, but bad quality video is generally merely annoying. So I’m picking the highest quality audio that we can afford, bitrate-wise.

Now time to pick video settings.

There’s 3 settings we can experiment with, the resolution, frame rate, and quality. We’ll be setting them in that order.

Resolution, the only viable resolutions are from 480p to 240p. Let’s check out how the various resolutions affect how the video looks.

Also, I’ll be using a FPS of 30, and a CRF of 50. This is not the final settings we’ll be choosing later on, but it’s fine for now, and we have to pick something. Also, the audio track will be our 12kbit opus that we chose above.


480p (854x480)
360p (640x360)
240p (426x240)

I think 240p here is Good Enough, especially since it allows us to use 30fps for smoother motion. I tried 360p at 15fps, and it looked shit.

Frame Rates

Speaking of, let’s explore how different frame rates look. I’ll use our 240p video, again with a CRF of 50, and emit 60fps, 30fps, 15fps.


I think 30fps is good, and we can just about afford the bitrate since we went with 240p.

Though 15fps at 360p is also an option.


I’m going to be using crf (Constant Rate Factor) to adjust the quality, and since we need a very low bitrate, it will be quite high.

We’ve locked down our resolution (240p) and our frame rate (30fps), and since we have a target to hit, there’s only really one correct answer. (Spoiler, it’s 56).

But let’s experiment with other qualities, why not? The lowest quality you can set is 63, and I’ll go up to multiples of 5 from there.

CRF 63
CRF 60
CRF 55
CRF 50

The process of figuring out what CRF makes the video fit is a process of binary search, just trying different CRFs until one barely fits.

Now finally time for The Final Encode. The one we’ve all been waiting for :3

It’s rather much the same as the tests above, only with -g 225 (to increase the time between keyframes to something larger to help compression, but hurt seeking performance), and -preset 2 (to increase the amount of time spent encoding to have a better quality output).

The below video, in its entirety, takes up 374 989 bytes. Which is less than 50 kilobits*60=375 000 bytes.

And if the above is unwatchable because you have a bad browser that doesn’t support AV1, you can watch it here instead.

wrapping up

So, is this watchable? maybe.

Up to you to judge, really. Definitely not as good as TV, but maybe Acceptable. So if we were somehow stuck with 50kbit internet connections, but otherwise modern systems, you could stream video… and nothing else at the same time.

Also, I’m aware that simply averaging 50kbit overall doesn’t mean the file is actually watchable live at 50kbit, since you may have periods of more than 50kbit. However, with my tests using pv -L 6250 final.webm | mpv -, the buffering is largely a non-issue, it would be fine.

For reproducibility, the script I used to generate these files is


youtube-dl -f 299+251 -o input.mkv

ffmpeg -n -i input.mkv -t 00:01:00 1min.mkv

ffmpeg -n -i 1min.mkv -c:a libopus -b:a 4k 4k.opus
ffmpeg -n -i 1min.mkv -c:a libopus -b:a 6k 6k.opus
ffmpeg -n -i 1min.mkv -c:a libopus -b:a 8k 8k.opus
ffmpeg -n -i 1min.mkv -c:a libopus -b:a 10k 10k.opus
ffmpeg -n -i 1min.mkv -c:a libopus -b:a 12k 12k.opus
ffmpeg -n -i 1min.mkv -c:a libopus -b:a 14k 14k.opus
ffmpeg -n -i 1min.mkv -c:a libopus -b:a 16k 16k.opus

ffmpeg -t 30 -n -i 1min.mkv -vf "scale=854:480,fps=30" -crf 50 -c:v libsvtav1 -c:a libopus -b:a 12k res-480p.webm
ffmpeg -t 30 -n -i 1min.mkv -vf "scale=640:360,fps=30" -crf 50 -c:v libsvtav1 -c:a libopus -b:a 12k res-360p.webm
ffmpeg -t 30 -n -i 1min.mkv -vf "scale=426:240,fps=30" -crf 50 -c:v libsvtav1 -c:a libopus -b:a 12k res-240p.webm

ffmpeg -t 30 -n -i 1min.mkv -vf "scale=426:240,fps=60" -crf 50 -c:v libsvtav1 -c:a libopus -b:a 12k fps-60.webm
ffmpeg -t 30 -n -i 1min.mkv -vf "scale=426:240,fps=30" -crf 50 -c:v libsvtav1 -c:a libopus -b:a 12k fps-30.webm
ffmpeg -t 30 -n -i 1min.mkv -vf "scale=426:240,fps=15" -crf 50 -c:v libsvtav1 -c:a libopus -b:a 12k fps-15.webm

ffmpeg -t 30 -n -i 1min.mkv -vf "scale=426:240,fps=30" -crf 63 -c:v libsvtav1 -c:a libopus -b:a 12k crf-63.webm
ffmpeg -t 30 -n -i 1min.mkv -vf "scale=426:240,fps=30" -crf 60 -c:v libsvtav1 -c:a libopus -b:a 12k crf-60.webm
ffmpeg -t 30 -n -i 1min.mkv -vf "scale=426:240,fps=30" -crf 55 -c:v libsvtav1 -c:a libopus -b:a 12k crf-55.webm
ffmpeg -t 30 -n -i 1min.mkv -vf "scale=426:240,fps=30" -crf 50 -c:v libsvtav1 -c:a libopus -b:a 12k crf-50.webm

ffmpeg -n -i 1min.mkv -g 225 -vf "scale=426:240,fps=30" -preset 2 -crf 56 -c:v libsvtav1 -an output.webm

ffmpeg -n -i 12k.opus -i output.webm -c:a copy -c:v copy final.webm

with ffmpeg version

# ffmpeg -version          
ffmpeg version n5.1.2 Copyright (c) 2000-2022 the FFmpeg developers
built with gcc 12.2.1 (GCC) 20230201
configuration: --prefix=/usr --disable-debug --disable-static --disable-stripping --enable-amf --enable-avisynth --enable-cuda-llvm --enable-lto --enable-fontconfig --enable-gmp --enable-gnutls --enable-gpl --enable-ladspa --enable-libaom --enable-libass --enable-libbluray --enable-libbs2b --enable-libdav1d --enable-libdrm --enable-libfreetype --enable-libfribidi --enable-libgsm --enable-libiec61883 --enable-libjack --enable-libmfx --enable-libmodplug --enable-libmp3lame --enable-libopencore_amrnb --enable-libopencore_amrwb --enable-libopenjpeg --enable-libopus --enable-libpulse --enable-librav1e --enable-librsvg --enable-libsoxr --enable-libspeex --enable-libsrt --enable-libssh --enable-libsvtav1 --enable-libtheora --enable-libv4l2 --enable-libvidstab --enable-libvmaf --enable-libvorbis --enable-libvpx --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxcb --enable-libxml2 --enable-libxvid --enable-libzimg --enable-nvdec --enable-nvenc --enable-opencl --enable-opengl --enable-shared --enable-version3 --enable-vulkan
libavutil      57. 28.100 / 57. 28.100
libavcodec     59. 37.100 / 59. 37.100
libavformat    59. 27.100 / 59. 27.100
libavdevice    59.  7.100 / 59.  7.100
libavfilter     8. 44.100 /  8. 44.100
libswscale      6.  7.100 /  6.  7.100
libswresample   4.  7.100 /  4.  7.100
libpostproc    56.  6.100 / 56.  6.100