Users upload videos in all sorts of formats—MOV from iPhones, MKV from torrents, AVI from 2008. The backend must accept them and serve a browser-friendly MP4/H.264 or WebM/VP9 without blocking the web process for minutes of transcoding. We designed a fault-tolerant pipeline that has been used in production for years—implemented it for over 30 projects. The result: adaptive quality profiles, real-time progress, and ready notifications. Typical server resource savings reach 40%—for a $1000 monthly server, that's $400 saved. CDN traffic costs drop significantly.
Why should you use asynchronous transcoding?
Video transcoding is a CPU-intensive operation that can take from seconds to tens of minutes depending on the clip length and encoding profile. Synchronous processing within an HTTP request is out of the question: it would block the web worker, cause timeouts, and tank responsiveness. The correct approach is an asynchronous job queue. Hardware encoding (NVENC) processes video 3x faster than software, but even it should not block the request.
Building a Transcoding Pipeline
The architecture includes several components. Below is a step-by-step process:
- Upload: the file is saved to object storage or local disk; a job ID is returned.
- Queue: a Job is pushed into a queue (RabbitMQ, Redis, SQS) to avoid blocking the HTTP request. This queue job video processing model ensures decoupling.
- Worker: picks up the Job, runs FFmpeg with the required parameters, writes progress to Redis.
- Notification: after completion, updates the database and notifies the client via WebSocket or polling.
A typical set of adaptive profiles for bitrate ladder:
| Profile | Resolution | Video bitrate | Audio bitrate | CRF | Preset |
|---|---|---|---|---|---|
| 360p | 640×360 | 600 kbps | 96 kbps | 28 | fast |
| 720p | 1280×720 | 2500 kbps | 128 kbps | 23 | fast |
| 1080p | 1920×1080 | 5000 kbps | 192 kbps | 22 | medium |
CRF (Constant Rate Factor) is the primary quality parameter for H.264 (H.264 conversion is handled by libx264): 18 = near lossless, 28 = acceptable quality at small size. preset affects encoding speed vs. file size. We use a Laravel FFmpeg service class to manage these profiles.
For HLS compatibility, we set keyframe interval (GOP size) to 2 seconds (e.g., -g 48 for 24fps). Using b-pyramid can improve compression by 5-10%. VMAF (Video Multimethod Assessment Fusion) metrics help fine-tune bitrate allocation per profile.
Tracking Transcoding Progress
FFmpeg can output progress via pipe. The worker parses lines in the format out_time_usec=... and saves percentages in Redis. The client receives updates via WebSocket (e.g., Laravel Echo) or polling. This allows accurate progress display without delays. Asynchronous video processing prevents timeouts. The progress mechanism is described in the FFmpeg documentation.
Container choice depends on the task: MP4 is universal for streaming, WebM with VP9 gives better compression, MKV is suitable for archival storage with subtitles. For the web, we recommend MP4 with H.264—browsers don't require plugins.
Implementing an FFmpeg Service
The key element is a service that manages the FFmpeg process with progress handling. This video processing PHP library handles the transcoding:
namespace App\Services; class FfmpegService { public function transcode( string $inputPath, string $outputPath, array $profile, ?callable $onProgress = null ): void { $width = $profile['width']; $height = $profile['height']; $videoBr = $profile['video_br']; $audioBr = $profile['audio_br']; $preset = $profile['preset']; $crf = $profile['crf']; // scale with aspect ratio preservation, pad to target size $scaleFilter = "scale={$width}:{$height}:force_original_aspect_ratio=decrease," . "pad={$width}:{$height}:(ow-iw)/2:(oh-ih)/2:black"; $cmd = implode(' ', [ 'ffmpeg -y', "-i " . escapeshellarg($inputPath), "-vf " . escapeshellarg($scaleFilter), "-c:v libx264", "-preset {$preset}", "-crf {$crf}", "-maxrate {$videoBr}", "-bufsize " . (intval($videoBr) * 2) . "k", "-c:a aac", "-b:a {$audioBr}", "-movflags +faststart", "-progress pipe:1", "-loglevel error", escapeshellarg($outputPath), ]); $descriptors = [ 0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w'], ]; $proc = proc_open($cmd, $descriptors, $pipes); // ... read progress and check exit code } } -movflags +faststart is mandatory: it moves the moov atom to the beginning of the MP4, allowing the browser to start playback before fully downloading the file. Without it, the video won't start until the entire file is downloaded.
Choosing Between Software and Hardware Encoding
Software encoding (libx264) delivers better quality at low bitrates but is slower. Hardware encoding (NVENC) is faster but may degrade quality under strong compression. Comparison:
| Parameter | Software (libx264) | Hardware (NVENC) |
|---|---|---|
| Speed | 1x | 3-5x |
| Quality | Better at low bitrate | Slightly worse |
| CPU load | High | Low |
| Availability | Always | Requires GPU |
For production, we often combine: NVENC hardware acceleration for previews, libx264 for final archive. The optimal choice depends on your priorities: speed or quality. Advanced rate-distortion optimization (RDO) in libx264 further improves compression efficiency, albeit at higher computational cost.
What's Included in the Work
As part of developing this FFmpeg pipeline, we provide:
- Queue setup (RabbitMQ/Redis) and supervisor worker for workers.
- Implementation of an FFmpeg service with progress parsing.
- Creation of Job classes with success/error handlers.
- Configuration of adaptive profiles for your needs.
- Endpoints to retrieve progress and results.
- WebSocket notification integration (optional).
- Deployment and monitoring documentation.
- Post-delivery support and training.
The pipeline development typically costs between $800 and $1200 and delivers a return on investment within a few months through reduced server and CDN costs.
Timeline and Ordering
Developing a basic pipeline takes 2 to 4 days, depending on profile complexity and WebSocket requirements. We'll evaluate your project for free—contact us to discuss the details. We guarantee code quality and post-delivery support.
Common mistakes and how to avoid them
- Ignoring the moov atom: without
-movflags +faststart, video won't stream. Check withffprobe -v quiet -print_format json -show_format output.mp4 | grep moov. - Too many parallel workers: CPU overload. Use
numprocs=1per worker in supervisor. - Forgetting job timeout: set an adequate
timeout(e.g., one hour for long videos). - Not handling FFmpeg errors: check the return code and log stderr.
Use these recommendations—and your pipeline will run stably even under high load.







