xiaosongshu/flv2mp4

Pure PHP native media processing library, realize FLV/MP4/HLS mutual conversion and H.264 re-encoding. Built-in live stream relay gateway, file server, multi-protocol push client: RTMP, HTTP-FLV, WebSocket-FLV, no FFmpeg external dependencies.

Maintainers

Package info

github.com/2723659854/flv2mp4

pkg:composer/xiaosongshu/flv2mp4

Transparency log

Statistics

Installs: 108

Dependents: 1

Suggesters: 0

Stars: 1

Open Issues: 0

v1.5.7 2026-08-26 04:00 UTC

README

🇨🇳 中文🇬🇧 English

Introduction

A lightweight pure PHP 8.1+ media processing toolkit with zero external dependencies (no FFmpeg required).
Supports FLV, FMP4, MP4, HLS mutual conversion, live streaming gateway, pushing, pulling, rebroadcasting, as well as H.264 decoding + scaling + re-encoding (Baseline Profile) and OPUS → AAC transcoding.

📋 Table of Contents

🎯 Core Features

Feature Direction Description
Container conversion FLV ↔ MP4 / FMP4 Generate standard MP4 or fragmented fMP4 (MSE compatible)
HLS slicing FLV → HLS Generate M3U8 + TS segments, compatible with hls.js, VLC, etc.
HLS restoration HLS → FLV Merge HLS segments back into a single FLV file
MP4 ↔ FLV MP4 → FLV / FMP4 → FLV Multi-container interconversion
Live gateway FLV gateway High-performance multi-level forwarding, supports high concurrency
Static file server HTTP file gateway Lightweight file server with directory browsing support
Pushing client FLV / MP4 → RTMP/HTTP-FLV/WS-FLV Push static files as a pseudo-live stream
Pulling client RTMP/HTTP-FLV/WS-FLV → FLV Pull live stream and save as local FLV
Rebroadcasting Multi-protocol input → Multi-protocol output One pull, multiple forwards
H.264 re-encoding Decode → Scale → Encode Baseline Profile, provides core support for multi-bitrate HLS
OPUS→AAC opus→pcm→aac Convert WebRTC Opus audio to AAC-LC

Requirements

Dependency Description
PHP ≥ 8.1 (CLI mode only)
sockets extension Required, provides low-level socket communication
gd extension Optional, used for generating watermarks from PNG/JPG images. Falls back to built‑in bitmap font if not available.
  • 💡 CLI only – does not work under Nginx/FPM web mode.
  • 💡 No FFmpeg, no third-party binaries – 100% pure PHP.
  • 💡 Container‑level conversion (FLV/MP4/HLS interop) is fast as it only changes the container.
  • ⚠️ H.264 re-encoding and Opus transcoding require proc_open to be enabled.
  • ⚠️ H.264 re-encoding module is CPU‑intensive – suitable for short offline videos, not for live real‑time transcoding. Enabling JIT is strongly recommended.

🚀 Installation

composer require xiaosongshu/flv2mp4

📚 Quick Start

<?php

declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';

ini_set('memory_limit', '512M');

$file = __DIR__ . '/test.flv';

// 1. FLV → fragmented fMP4 (merged)
\Xiaosongshu\Flv2mp4\Client::runFlv2Fmp4Mixed($file, __DIR__ . '/output_merge');

// 2. FLV → fragmented fMP4 (separate audio/video tracks)
\Xiaosongshu\Flv2mp4\Client::runFlv2Fmp4Separate($file, __DIR__ . '/output_separate');

// 3. FLV → HLS
\Xiaosongshu\Flv2mp4\Client::runFlv2Hls($file, __DIR__ . '/hls');

// 4. HLS → FLV
\Xiaosongshu\Flv2mp4\Client::runHls2Flv(__DIR__ . '/hls/index.m3u8', __DIR__ . '/output.flv');

// 5. MP4 → FLV
\Xiaosongshu\Flv2mp4\Client::runMp42Flv(__DIR__ . '/test.mp4', __DIR__ . '/output.flv');

// 6. FLV → MP4
\Xiaosongshu\Flv2mp4\Client::runFlv2Mp4($file, __DIR__ . '/output.mp4');

// 7. fMP4 → FLV (supports both merged and separate formats)
\Xiaosongshu\Flv2mp4\Client::runFmp42Flv(__DIR__ . '/output_merge/index.m3u8', __DIR__ . '/output.flv');

🌐 Advanced Features

Opus 2 AAC

WebRtcFlvRelay receives WebRTC RTP data, wraps H.264 video into FLV, transcodes Opus audio to AAC‑LC via a pure PHP Worker, and pushes it to a WebSocket‑FLV service for recording or forwarding to RTMP.

<?php

require_once __DIR__ . '/vendor/autoload.php';

use Xiaosongshu\Flv2mp4\Flv\WebRtcFlvRelay;
use Xiaosongshu\Flv2mp4\Opus\OpusWorkerClient;

$clientId = 1;
$streamId = 'stream_001';
$opusWorkerPort = 8330;
$pushUrl = "ws://127.0.0.1:8501/live/{$streamId}";

$relay = new WebRtcFlvRelay(
    $clientId,
    $streamId,
    $pushUrl,
    null,
    null,
    $opusWorkerPort
);
$relay->connect();

// Call these in your WebRTC server's RTP callback:
// $relay->pushRtp($plainRtp, 'video');
// $relay->pushRtp($plainRtp, 'audio');

// Close relay when done; shut down automatically started Workers on process exit.
$relay->finish();
OpusWorkerClient::shutdownOwnedWorkers();

A complete example is available at examples/webrtc.php. Common configuration:

// Each project instance must use a different Worker port.
$opusWorkerPort = 8330;

// Supports RTMP, HTTP‑FLV, and WebSocket‑FLV push URLs.
// The example uses WebSocket‑FLV and replaces placeholder with streamId.
$wsFlvPushUrl = 'ws://127.0.0.1:8501/live/{streamId}';

Run the example:

php webrtc.php

Notes:

  • The relay automatically starts bin/opus-worker.php if no Worker is listening on the port – no manual startup needed.
  • Worker listens only on 127.0.0.1, default port 8330.
  • Auto‑start passes the host project's real vendor/autoload.php via --autoload, working with both local development and Composer‑installed setups.
  • Default output: 48kHz, mono, 64kbps AAC‑LC.
  • One Worker process can manage multiple independent connections, but real‑time transcoding is CPU‑heavy; plan for one live stream per instance.
  • Different project instances on the same machine must use different $opusWorkerPort.
  • On Ctrl+C or process exit, call OpusWorkerClient::shutdownOwnedWorkers() – the example already handles this.
  • PHP must allow proc_open for automatic Worker creation.
  • The Worker queue has bounded back‑pressure; do not simply enlarge the queue to solve performance issues, as it may increase latency and cause A/V desync.
  • WebRTC service requires the xiaosongshu/webrtc package.

FLV Live Gateway

Supports multi‑level proxy deployment for high‑concurrency live stream forwarding. Create flvGateway.php:

<?php
require_once __DIR__ . '/vendor/autoload.php';
$gateway = new \Xiaosongshu\Flv2mp4\Manage\FlvGateway(8080, 'http://127.0.0.1:8501');
$gateway->debug = true;
$gateway->start();

Run:

php flvGateway.php

Static File Gateway

Lightweight HTTP file server with directory browsing toggle. Create fileGateway.php:

<?php
require_once __DIR__ . '/vendor/autoload.php';
$server = new \Xiaosongshu\Flv2mp4\Manage\FileGateway( '0.0.0.0',8100,__DIR__,false);
$server->debug = true;
$server->start();

Run:

php fileGateway.php

Pushing Client

Supports HTTP‑FLV, WS‑FLV, RTMP, with speed control and auto‑reconnect. Create pusher.php:

require_once __DIR__ . '/vendor/autoload.php';
ini_set('memory_limit', '2048M');
$pusher = new \Xiaosongshu\Flv2mp4\Manage\PusherManage(__DIR__."/test.flv", "http://127.0.0.1:8501/live/stream", 1.0, false);
$pusher->start();

Run:

php pusher.php

Pulling Client

Pulls a live stream and saves it as a local FLV file. Create puller.php:

require_once __DIR__ . '/vendor/autoload.php';
ini_set('memory_limit', '2048M');
$puller = new \Xiaosongshu\Flv2mp4\Manage\PullerManage("ws://127.0.0.1:8501/live/stream.flv", __DIR__."/pull_record.flv", 0, false);
$puller->start();

Run:

php puller.php

Rebroadcasting (Forwarding)

Pulls one stream and forwards it to multiple destinations (mixed protocols supported). Create forward.php:

require_once __DIR__ . '/vendor/autoload.php';
ini_set('memory_limit', '2048M');
$forwarder = new \Xiaosongshu\Flv2mp4\Flv\FlvForwardClient("http://127.0.0.1:8501/a/b.flv", ["rtmp://127.0.0.1:1935/c/d","ws://127.0.0.1:8501/c/e"], 0, true);
$forwarder->start();

Run:

php forward.php

🧪 Testing & Playback

Output format Recommended player Sample file
MP4 HTML5 <video> index.html
fMP4 MSE player play_merge.html, mse.html
HLS (TS) hls.js / Safari play.html
FLV flv.js flv.html
FLV (push test) Web push test push.html

🎯 Use Cases

  • Live recording: Save RTMP/FLV streams as fMP4 / HLS in real time.
  • Video playback: On‑demand playback of recorded streams.
  • Stream forwarding: Multi‑level gateways for load balancing and edge acceleration.
  • Offline batch processing: Bulk FLV / MP4 conversion.
  • Pseudo‑live streaming: Push on‑demand files as live streams.
  • Cross‑platform rebroadcasting: One pull, multiple pushes to different platforms.
  • Multi‑bitrate HLS: Pure PHP H.264 re‑encoding to generate adaptive‑bitrate HLS.

🔥 H.264 Decoding + Scaling + Re-encoding

Supports Baseline Profile H.264 decoding, scaling, and re‑encoding, enabling the following capabilities:

Use case Description
Multi‑bitrate HLS Convert a single FLV into multiple resolution HLS streams (adaptive bitrate)
FLV re‑encoding Change resolution/bitrate and output as FLV
MP4 re‑encoding Change resolution/bitrate and output as MP4
Format conversion Re‑encode during FLV ↔ MP4 conversion (not just remuxing)
Watermark overlay Decode YUV → overlay PNG/text watermark → re‑encode output
Image enhancement Apply filters (sharpen, denoise) after decoding → re‑encode
Resolution adaptation Downsample high‑resolution video to multiple output resolutions
Bitrate control Transcode high‑bitrate videos to target bitrate

This is a complete H.264 pixel processing pipeline (decode → process → encode), implemented entirely in PHP without FFmpeg.

FLV2HLS

Example for multi‑bitrate HLS generation:

<?php

require_once __DIR__ . '/vendor/autoload.php';
ini_set('memory_limit', '2048M');
$profiles = [
    '240p' => [
        'width' => 426,
        'height' => 240,
        'bitrate' => 300000, // 300 Kbps video
        'fps' => 24,
        'audioBitrate' => 48000, // 48 Kbps
        'qp' => 30,
        'watermark'=>true,
        'watermark_file'=> __DIR__."/src/Static/watermark_80x16.yuv",
    ]
];
// Enable multi-process acceleration for high-quality re-encoding; not needed for low bitrate.
$generator = new \Xiaosongshu\Flv2mp4\Recode\PurePhpHlsGenerator($profiles, __DIR__ . '/hls/output', true);
$generator->processFlv(__DIR__ . '/input.flv');
echo "Master playlist: hls/output/master.m3u8\n";
echo "All done!\n";

FLV2FLV

Re‑encode a FLV file with new bitrate/resolution:

<?php
require_once __DIR__ . '/vendor/autoload.php';
ini_set('memory_limit', '2048M');

$config = [
    'width' => 320,
    'height' => 180,
    'bitrate' => 150000,
    'fps' => 15,
    'qp' => 30,
    'watermark'=>true,
    'watermark_file'=> __DIR__."/src/Static/watermark_80x16.yuv",
];
// Enable multi-process acceleration for high-quality re-encoding; not needed for low bitrate.
$recoder = new \Xiaosongshu\Flv2mp4\Recode\FlvRecoder($config, true);
$recoder->setMaxFrames(50);
$recoder->processFlv(__DIR__ . '/input.flv', __DIR__.'/output.flv');
echo "FLV re‑encoding done.\n";

MP42MP4

Re‑encode a MP4 file:

<?php
require_once __DIR__ . '/vendor/autoload.php';
ini_set('memory_limit', '2048M');

$config = [
    'width' => 320,
    'height' => 180,
    'bitrate' => 150000,
    'fps' => 15,
    'qp' => 30,
    'watermark'=>true,
    'watermark_file'=> __DIR__."/src/Static/watermark_80x16.yuv",
];
// Enable multi-process acceleration for high-quality re-encoding; not needed for low bitrate.
$recoder = new \Xiaosongshu\Flv2mp4\Recode\Mp4Recoder($config, true);
$recoder->setMaxFrames(50);
$recoder->processMp4(__DIR__ . '/input.mp4', __DIR__ . '/output.mp4');
echo "MP4 re‑encoding done.\n";

Notes for watermarking:

  • Watermark files must be in YUV format, and the filename must include its dimensions (e.g., watermark_{width}x{height}.yuv).
  • The tool parses width and height from the filename automatically (e.g., watermark_80x16.yuv → width 80, height 16).
  • The re‑encoding module exposes a YUV pixel‑level interface, which can be used to implement custom features like subtitles, picture‑in‑picture, video stitching, etc.
  • For detailed H.264 usage, see src/Codec/README.md.

Supported Re‑encoding Features

  • I‑frame decode & encode (100% exact, INF dB)
  • P‑frame decode & encode (Baseline Profile)
  • Intra prediction: 4×4 (9 modes) + 16×16 (4 modes)
  • Inter prediction: P‑frame motion estimation (diamond search optimized)
  • 1/4‑pixel precision (6‑tap filter interpolation)
  • CAVLC entropy coding (Baseline Profile)
  • Resolution scaling (YUV scaling after decode → re‑encode)
  • Bitrate control (via QP parameter)
  • B‑frame support (planned, requires Main Profile with bidirectional prediction)
  • CABAC entropy coding (planned, Main Profile)

⚠️ Performance note: The H.264 re‑encoding module is pure PHP and is intended for short‑duration videos (≤ 10 seconds) for offline processing or functional verification. For long videos or high‑resolution transcoding, professional tools like FFmpeg are recommended.

Watermark Generator

Provides PHP functions to generate YUV watermark files. Uses GD extension if available, otherwise falls back to built‑in bitmap font.

  • generateFromText() – generates text watermark YUV. Uses GD if available; otherwise falls back to bitmap font (ASCII characters only).
  • generateFromImage() – generates watermark YUV from PNG/JPG images (requires GD extension).

Generate text watermark

<?php
require_once __DIR__ . '/vendor/autoload.php';

use Xiaosongshu\Flv2mp4\Codec\WatermarkUtil;

echo "=== Testing WatermarkUtil ===\n\n";

echo "1. Generate text watermark (xiaosongshu, 80x16)...\n";
$outputFile1 = __DIR__ . '/test_wm_text.yuv';
$start = microtime(true);
$result = WatermarkUtil::generateFromText(
    'xiaosongshu',
    $outputFile1,
    80,
    16,
    [
        'fontSize' => 5, // 1–5, built‑in font; ASCII only
        'fontColor' => [255, 255, 255],
        'bgColor' => [0, 0, 0],
    ]
);
$cost = round(microtime(true) - $start, 3);
if ($result && file_exists($outputFile1)) {
    $size = filesize($outputFile1);
    $expectedSize = 80 * 16 + (80 * 16 >> 1);
    echo "   Success! Size: {$size} bytes (expected: {$expectedSize}) - time: {$cost}s\n";
    if ($size === $expectedSize) {
        echo "   ✅ File size correct\n";
    } else {
        echo "   ❌ File size mismatch\n";
    }
} else {
    echo "   ❌ Generation failed\n";
}

Generate from image

Requires GD extension.

<?php

require_once __DIR__ . '/vendor/autoload.php';

use Xiaosongshu\Flv2mp4\Codec\WatermarkUtil;

echo "=== Testing WatermarkUtil ===\n\n";

echo "1. Generate watermark from image (xiaosongshu, 80x16)...\n";
$outputFile1 = __DIR__ . '/test_wm_copy_80x16.yuv';
$start = microtime(true);
$result = WatermarkUtil::generateFromImage(
    __DIR__."/watermark_80x16.png",
    $outputFile1,
    80,
    16,
);
$cost = round(microtime(true) - $start, 3);
if ($result && file_exists($outputFile1)) {
    $size = filesize($outputFile1);
    $expectedSize = 80 * 16 + (80 * 16 >> 1);
    echo "   Success! Size: {$size} bytes (expected: {$expectedSize}) - time: {$cost}s\n";
    if ($size === $expectedSize) {
        echo "   ✅ File size correct\n";
    } else {
        echo "   ❌ File size mismatch\n";
    }
} else {
    echo "   ❌ Generation failed\n";
}

Performance Test Report

Test Environment

Item Details
CPU 16 physical cores
RAM 15.8 GB (available)
Worker processes 8 ME sub‑processes (empirically optimal; more or fewer increases time)
PHP version 8.4.3 (CLI, JIT enabled)
OPcache opcache.enable_cli=on, opcache.jit=on, opcache.jit_buffer_size=100M
Test clip test.flv, 3.02 s, 720×742, 30 fps
Output specs output.flv, 360×360, 10 fps
Codec settings H.264 Constrained Baseline, AAC 128 kbps

Results

Output format Best time Typical range
FLV re‑encoding 28 s 28~29 s
MP4 re‑encoding 29 s 29~30 s
HLS (mpegts + m3u8) 37 s 37~38 s

Best value (28 s) was observed 3 times out of 6 runs; 29 s occurred 3 times.

Performance Breakdown

1. Processing scale

Decoding side (source)

  • Resolution: 720×742 → macroblock‑aligned to 720×752 (47×47 16×16 MBs)
  • MBs per frame: 47×47 = 2,209 MBs
  • Total frames: 90
  • Total decoded MBs: 2,209 × 90 = 198,810 MBs

Encoding side (output)

  • Resolution: 360×360 → aligned to 368×368 (23×23 MBs)
  • MBs per frame: 23×23 = 529 MBs
  • Total frames: 30
  • Total encoded MBs: 529 × 30 = 15,870 MBs

Total MBs processed: 198,810 + 15,870 ≈ 215,000 macroblocks

2. Motion estimation (hotspot)

Even with fast search, each macroblock checks about 150 candidate motion vectors. Each SAD computation processes 16×16 = 256 pixel differences.

  • Encoding ME operations: 15,870 × 150 × 256 ≈ 610 million integer ops
  • Combined with decoding motion compensation, DCT, quantization, entropy coding, total operations exceed 1 billion
  • ≈ 350 million primitive pixel ops per second

Architecture Overview

The system employs a pipeline + distributed computing architecture:

FLV file
   │
   ▼
┌──────────────────┐
│  Main process    │
│  (FLV Tag demux) │
└────────┬─────────┘
         │
         ▼
┌──────────────────┐      ┌──────────────────┐
│ Decode+Scale     │ ──▶  │ Encode master    │
│ (H.264 → YUV)    │      │ (YUV → H.264)    │
└──────────────────┘      └────────┬─────────┘
                                    │
            ┌───────────┬───────────┼───────────┬───────────┐
            ▼           ▼           ▼           ▼           ▼
       ┌───────────┐  ┌───────────┐  ┌───────────┐  ┌───────────┐  ┌───────────┐
       │ ME Worker │  │ ME Worker │  │ ME Worker │  │ ME Worker │  │ ME Worker │
       │  (8 total)│  │           │  │           │  │           │  │           │
       └───────────┘  └───────────┘  └───────────┘  └───────────┘  └───────────┘

8 ME worker processes are optimal for 16‑core systems. Increasing further adds context‑switching overhead; decreasing under‑utilises CPU.

Usage Recommendations

Scenario Recommended Explanation
Live real‑time transcoding ❌ Not suitable 3 s video takes 28 s (~9× real‑time), violates low‑latency requirements
Off‑line VOD transcoding ✅ Recommended Can be used as background job (Redis queue + Worker) for user‑uploaded files
Restricted environments (no FFmpeg) ✅ Recommended Pure PHP, zero external deps, deployable in containers/embedded systems
High‑concurrency bursts ⚠️ Control carefully Limit concurrency to avoid CPU starvation for critical services

Production Deployment Tips

1. Enable JIT (essential)

Testing shows PHP 8.4.3 with JIT is ~1 s faster than PHP 8.2.9.

; php.ini
opcache.enable_cli=1
opcache.jit=on
opcache.jit_buffer_size=100M

2. Recommended PHP version

  • PHP 8.4.x (best performance)
  • PHP 8.2.x works but is slightly slower (~29 s)

3. Worker tuning

# For 16‑core systems, 8 workers is optimal
php recode.php --workers=8

4. CPU isolation (optional)

# Bind to CPU cores 0-7 (matching the 8 workers)
taskset -c 0-7 php recode.php

5. Task queue

Push transcoding tasks to Redis or Beanstalkd queues and consume via Worker processes to avoid blocking the main request path.

Conclusion

The pure‑PHP H.264 re‑encoding system achieves stable results on a 16‑core test machine:

  • FLV re‑encoding: 28 s
  • MP4 re‑encoding: 29 s
  • HLS full pipeline: 37 s

Total operations exceed 1 billion integer operations, translating to ≈ 350 million pixel‑level operations per second. This system is suitable for offline asynchronous transcoding, but not for live real‑time transcoding.

Data Source

All metrics are from actual runs of the xiaosongshu/flv2mp4 project.

🔧 Technical Notes

  • 100% pure PHP 8.1+, no FFmpeg dependency.
  • Originally built to serve xiaosongshu/rtmp_server.
  • Recommended static analysis: PHPStan Level 8.
  • H.264 re‑encoding uses distributed multi‑process architecture; disable distributed mode if running on a single‑core machine.

Open Source License & Disclaimer

This project is licensed under the Apache License 2.0. You are free to use, modify, and distribute it (including commercial use).
The code is provided "AS IS", without warranty of any kind, express or implied. The author is not liable for any damages arising from its use.

📧 Contact