Estimating screen-sharing bitrate: what bpp actually means and when it lies
Every screen-sharing feature hits the same meeting: someone asks how much bandwidth a share needs, and someone answers with a formula. The formula is usually this:
bitrate ≈ width × height × fps × bpp
It looks authoritative: it has units, and it produces a number with "Mbps" after it. That is exactly why it is dangerous - it fails precisely on the content that matters most.
This post explains what bpp is, gives the ranges people use (as rules of thumb, not measurements), shows a measured control case where the formula holds, and lists three cases where it lies.
What bpp is, and what it is not
bpp - bits per pixel - is not a codec constant; no standard defines it. It is a budgeting convention: take one second of video, count its pixels, and ask how many bits to spend on each.
For a 1920×1080 stream at 30 fps, one second contains 62,208,000 pixels. At 0.08 bpp, that is about 4.98 Mbps.
The arithmetic is trivial; the entire difficulty is choosing bpp. The value folds in everything the codec does: motion compensation, quantization, entropy coding, and how much detail the encoder throws away.
So bpp is useful as a planning number and useless as a guarantee. It answers "what budget should I reserve", not "what will the encoder produce".
Reference ranges (empirical, not measured here)
The numbers below are rules of thumb from common practice. I did not measure them for this post, and you should not quote them as if I did.
| Codec / content | bpp (rule of thumb) |
|---|---|
| H.264, screen content | 0.05 - 0.15 |
| HEVC, same content and quality target | roughly 0.6 - 0.7 × the H.264 bitrate |
| AV1 | lower again; treat the multiplier as a hypothesis, not a fact |
Two warnings: the ranges are wide on purpose, and they describe typical mixed screen content - windows, text, images, some motion. Change the content and the range moves more than the codec does.
Remember what the formula multiplies. If you feed it a nominal 30 fps but the share emits 12 because the screen is static, you have overestimated by 2.5×. Use frames sent, not frames desired.
A control case where the estimate holds
We ran a share at 3024×1964, H.264 hardware encoding, median 29 fps. Per-frame encode time was 12.3 ms, and the encoder dropped 0 frames.
At 12.3 ms per frame the encoder ceiling is about 81 fps. At 29 fps, encoding occupies roughly 36% of the budget: 12.3 × 29 ≈ 357 ms out of every 1000 ms. The encoder is not saturated, and dropped stayed at 0.
That matters because the formula assumes the encoder can deliver the requested frames at the requested size. When it can, the estimate describes a real operating point.
At 0.08 bpp this stream implies:
3024 × 1964 × 29 × 0.08 ≈ 13.8 Mbps
The number is a budget, not a measurement. What the test establishes is narrower: nothing on the encode side prevents the stream from spending it, so a bitrate estimate is the right tool.
Flip the condition and the formula fails. If per-frame encode time were 40 ms, the ceiling would be 25 fps, and asking for 29 fps would drop frames. Actual bits per second would land below the estimate - not because the network refused, but because the encoder never produced those frames.
Rule: check encode time × fps before trusting any bitrate estimate. Above roughly 70-80% of the frame budget, stop estimating and start measuring.
Where bpp lies
Three content patterns break the formula in three different directions.
Static screens: the estimate is too high. Inter-frame prediction makes unchanged regions almost free. A desktop that has not moved for ten seconds can encode to a few kilobits per second, not the 13.8 Mbps the formula predicts. Sizing a link from the estimate reserves capacity nobody uses. Worse, a bpp-driven health check will read a static share as suspiciously cheap.
Pure text: the estimate is too high, and quality is the thing to watch. Text on a flat background compresses extremely well, especially with screen-content tools, so actual bitrate can sit far below the estimate. The failure mode is not bandwidth but legibility: at a low bitrate the encoder can preserve the rectangles and destroy the strokes. Bits per second looks fine; the reader cannot read the code.
Video playback inside the share: the estimate is too low. A video window is high-entropy, fast-changing content with little screen-like redundancy. The encoder must spend real bits; capping it at the formula's budget does not make the content cheaper - it makes it blurrier. Measured bitrate will hit the ceiling and stay there, while quality collapses.
bpp estimates a typical frame, and screen content is rarely typical. It is a prior, not an answer.
The correction loop that actually works
Treat 0.08 bpp as a starting point and let the transport correct it. Measure bytesSent on the outbound RTP stream, convert to bits per second, and back out the bpp actually used:
async function measureBpp(pc, width, height, windowMs = 10000) {
const snap = async () => {
const stats = await pc.getStats();
let r;
stats.forEach(s => {
if (s.type === 'outbound-rtp' && s.kind === 'video') r = s;
});
return { bytes: r.bytesSent, frames: r.framesSent, ts: r.timestamp };
};
const a = await snap();
await new Promise(res => setTimeout(res, windowMs));
const b = await snap();
const seconds = (b.ts - a.ts) / 1000;
return ((b.bytes - a.bytes) * 8) / seconds / (width * height * ((b.frames - a.frames) / seconds));
}
Use frames actually sent, not the configured rate. Read bytes at the transport, not the encoder, so RTP overhead and retransmissions are included. Use a window long enough to cross content states: ten seconds is a default.
Then close the loop: measure a representative minute, compare to the estimate, and correct the next estimate by the ratio. If measured bpp is 0.04 for your content mix at acceptable quality, plan with 0.04 - do not keep quoting 0.08 because a blog post said so. Including this one.
The short version
bitrate ≈ width × height × fps × bppis a budgeting formula, not a codec guarantee.- Start at 0.08 bpp for H.264 screen content, then measure and correct.
- Empirical ranges: H.264 0.05-0.15 bpp; HEVC roughly 0.6-0.7×; AV1 lower. These are rules of thumb, not measurements.
- Before trusting the estimate, check that encode time × fps leaves headroom. In our 3024×1964 / 29 fps / 12.3 ms / 0-dropped run, it did.
- Static screens and text will come in below the estimate; video playback will come in above it.
- Use frames sent, at the transport layer, over a 10-second window.
- The number you should trust is the one you measured on your own content.
To run the arithmetic and the trade-offs without a spreadsheet, there is a free calculator here: https://hhfa008.github.io/tools/screen-share-calc/. It shows every formula it uses and labels every output as an estimate.
Comments
No comments yet. Start the discussion.