All posts
FFmpegStreamingHLS20 Jun 2025 · 3 min read

Where video transcoding should run

JPJean Perez

Transcoding a video means decoding every frame, re-encoding it at several bitrates and resolutions, and writing the result back out as files on disk. That's a CPU-bound job that runs for minutes and produces real output on a filesystem. A serverless function is built to do almost the opposite of that.

Why a request handler is the wrong place for it

A serverless function like a Vercel Edge Function or a Lambda is optimized for request and response: receive input, do a small amount of work, return, and be ready to do it again immediately for the next request. Several constraints follow from that design, and each one works against transcoding specifically.

The filesystem is read-only outside of /tmp, and /tmp itself is capped, often around 512MB. An encode job that reads a source file, writes intermediate segments, and produces a multi-rendition HLS output can outgrow that without much effort. Bundling an ffmpeg binary into the function adds tens of megabytes to the deployment package, which pushes against size limits that exist for cold-start speed, not for housing a media toolchain. And the function has a hard execution ceiling, seconds on some platforms, low minutes on others, while a real encode job for anything longer than a clip can take considerably more time than that.

None of these are edge cases. They're the default shape of the job. Transcoding wants a machine with disk to spare and no clock forcing it to finish before the HTTP connection times out.

What a managed transcoder buys back

The alternative is to not run ffmpeg in the request path at all. An application can submit a job description, a source file location and the renditions it wants, to a managed transcoding service, then poll for completion. Encoding happens on infrastructure built for it: durable disk, no execution ceiling, no binary to bundle into an application deploy. BeemMeUp submits its jobs to Google Cloud Transcoder and writes the HLS output to Cloud Storage.

The cost is a network hop and a job that finishes asynchronously instead of inline. For a job that already takes minutes, that trade is close to free. The same reasoning applies to inspection, not just encoding: probing a file with ffprobe is lighter than a full encode, but it's still a native binary with real CPU and memory needs, and moving it to a small dedicated service sidesteps the same filesystem and bundle-size constraints for the same reason.

The ladder a real upload gets encoded into

A managed transcoder doesn't produce one output file. It produces a rendition ladder, several versions of the same video at different bitrates, so a player can pick whichever one fits the viewer's bandwidth and switch between them as conditions change.

RenditionResolutionVideo bitrateProfile
1080p1920x10805,000,000 bpshigh
720p1280x7202,800,000 bpshigh
480p854x4801,400,000 bpsmain
360p640x360800,000 bpsmain

Every rendition in that ladder shares one thing beyond the codec: the same segment duration and the same keyframe interval.

Why the segments and the keyframes have to line up

Adaptive bitrate playback only works if a player can switch from one rendition's segment to a different rendition's segment at the same point in time, without a visible stutter or a decode error. That requires two things to match across every rendition: the segment boundaries have to fall at the same timestamps, and each segment has to start on a keyframe, a frame that decodes on its own without needing any frame before it.

A 6-second segment duration paired with a 3-second GOP (the interval between keyframes) gives exactly that. Since a keyframe lands every 3 seconds, every 6-second segment starts, and contains, a full keyframe boundary, in every rendition, at the same moment in the timeline. That's what lets a player jump from the 1080p version of segment 12 to the 480p version of segment 13 and start decoding immediately: segment 13 begins with a frame that doesn't depend on anything from segment 12.

Loosen either number (a GOP longer than the segment, or a segment length that isn't a multiple of the keyframe interval) and that guarantee breaks. Renditions can drift out of alignment, and a switch mid-stream can land on a frame the player can't decode cleanly.

That alignment comes from the encode settings. It has to be asked for in the job configuration, the same way the resolution and bitrate are. With it in place, the four renditions behave as one stream a player can move through freely, switching quality as often as it needs to.