ホームページ >バックエンド開発 >PHPチュートリアル >PHPでライブストリーミング機能を実装

PHPでライブストリーミング機能を実装

WBOY
WBOYオリジナル
2023-06-22 12:34:161289ブラウズ

インターネットの継続的な発展に伴い、ライブ ブロードキャストは人々の日常生活に欠かせないものとなっており、ライブ ストリーミング機能はライブ ブロードキャストを実現するための鍵となります。 PHP の登場は Web アプリケーション開発に強力なツールをもたらしましたが、ライブ ストリーミング機能の実装も例外ではありません。この記事では、PHPを使ってライブストリーミング機能を実装する方法を紹介します。

1. ライブ ストリーミングの基本原理を理解する

ライブ ストリーミング機能の実装方法を紹介する前に、まずライブ ストリーミングの基本原理を理解する必要があります。ライブストリーミングとは、ユーザーがライブ配信を行う際に、動画データをサーバーにアップロードし、他のユーザーに配信することを指します。具体的には、ライブ ブロードキャストのアップロードとライブ ブロードキャストの配信という 2 つの基本リンクが含まれています。

ライブアップロードとは、ユーザーがアップロードしたビデオデータを処理および保存し、さまざまな端末デバイスの再生ニーズを満たすためにリアルタイムでトランスコードすることを指します。ライブ配信とは、加工した動画データを他のユーザーに送信して視聴してもらうことを指します。通常、ライブブロードキャスト配信はサーバー配信とP2P配信の2つに分かれます。

2. PHP を使用してライブ ブロードキャスト アップロードを実装する

PHP には、ライブ ブロードキャスト アップロードを実装するために使用できるライブラリが多数あります。一般的に使用されるライブラリには、FFmpeg、Libav、H264 Streaming Module などが含まれます。これらのライブラリはすべて C/C 言語に基づいて記述されており、PHP と C/C 間の相互運用性は PHP の FFI 拡張機能を通じて実現できます。具体的な実装プロセスは次のとおりです:

1. FFI 拡張機能のインストール

PHP で FFI 拡張機能を使用するには、最初に拡張機能をインストールする必要があります。 FFI 拡張機能は、PHP の公式 Web サイト (https://www.php.net/manual/zh/ffi.installation.php) からダウンロードするか、パッケージ マネージャーを使用してインストールできます。インストールが完了したら、次のコードを php.ini ファイルに追加します:

extension=ffi.so

2. FFmpeg を使用してビデオ エンコーディングを実装します

FFmpeg を使用してビデオデータをエンコードするには、ビデオデータをいくつかのフレームに分割し、フレームごとにエンコード変換を行う必要があります。具体的な実装プロセスは次のとおりです。

use FFI;

$ffi = FFI::cdef('
    typedef struct AVCodecParameters AVCodecParameters;
    typedef struct AVCodecContext AVCodecContext;
    typedef struct AVPacket AVPacket;
    typedef struct AVFrame AVFrame;
    typedef struct AVCodec AVCodec;
    typedef struct SwsContext SwsContext;

    AVCodec *avcodec_find_encoder(enum AVCodecID id);
    AVCodecContext *avcodec_alloc_context3(AVCodec *codec);
    int avcodec_parameters_to_context(AVCodecContext *codec_ctx, const AVCodecParameters *par);
    int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);
    int avcodec_send_frame(AVCodecContext *avctx, const AVFrame *frame);
    int avcodec_receive_packet(AVCodecContext *avctx, AVPacket *avpkt);
    AVFrame *av_frame_alloc();
    void av_frame_free(AVFrame **frame);
    SwsContext* sws_getContext(int srcW, int srcH, enum AVPixelFormat srcFormat, int dstW, int dstH, enum AVPixelFormat dstFormat, int flags, SwsFilter *srcFilter, SwsFilter *dstFilter, const double *param);
    const uint8_t **av_fifo_generic_read(AVFifoBuffer *f, void *dest, int buf_size, void *(*func)(void *, const void *, size_t), void *dest_ctx);
    int av_fifo_size(const AVFifoBuffer *f);
    void av_fifo_reset(AVFifoBuffer *f);
    void av_fifo_free(AVFifoBuffer *f);
    int sws_scale(SwsContext *c, const uint8_t *const srcSlice[], const int srcStride[], int srcSliceY, int srcSliceH, uint8_t *const dst[], const int dstStride[]);
    #define AV_CODEC_ID_H264 AV_CODEC_ID_MPEG4
    #define AV_PIX_FMT_YUV420P AV_PIX_FMT_YUVJ420P
', 'libavcodec.so.58.54.100, libavutil.so.56.55.100, libavformat.so.58.29.100, libswscale.so.5.5.100');

$codec = $ffi->avcodec_find_encoder(AV_CODEC_ID_H264);
$codec_ctx = $ffi->avcodec_alloc_context3($codec);
$options = null;
$width = 640;
$height = 480;
$frame_rate = 25;
$bit_rate = 400000;
$codec_ctx->width = $width;
$codec_ctx->height = $height;
$codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
$codec_ctx->bit_rate = $bit_rate;
$codec_ctx->time_base = FFI::new('AVRational');
$codec_ctx->time_base->num = 1;
$codec_ctx->time_base->den = $frame_rate;
$codec_ctx->gop_size = $frame_rate * 2;
$codec_ctx->max_b_frames = 1;
$codec_ctx->rc_buffer_size = $bit_rate;
$codec_ctx->rc_max_rate = $bit_rate;
$codec_ctx->rc_min_rate = $bit_rate;
$codec_ctx->codec_tag = $codec->id;
$codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;

if ($codec->id == AV_CODEC_ID_H264) {
    $codec_ctx->profile = FF_PROFILE_H264_BASELINE;
    $codec_ctx->level = 30;
}

if ($ffi->avcodec_open2($codec_ctx, $codec, $options) < 0) {
    throw new Exception('Cannot init encoder');
}

$codec_pkt = $ffi->new('AVPacket[1]');
$frame = $ffi->av_frame_alloc();
$frame->format = AV_PIX_FMT_YUV420P;
$frame->width = $width;
$frame->height = $height;
$ffi->av_frame_get_buffer($frame, 32);

$sws = $ffi->sws_getContext(
    $width, $height, AV_PIX_FMT_RGB24,
    $width, $height, AV_PIX_FMT_YUV420P,
    SWS_FAST_BILINEAR, null, null, null);

$buffer = $ffi->malloc($width * $height * 3);
$i = 0;
while ($i < 120) {
    $i++;
    //$buffer = 获取一帧RGB24像素数据
    $image = 'data://' . base64_encode($buffer);
    $img = imagecreatefromstring(file_get_contents($image));
    $rgb = imagecreatetruecolor($width, $height);
    imagecopyresampled($rgb, $img, 0, 0, 0, 0, $width, $height, $width, $height);
    $rgb_buffer = $ffi->new('uint8_t[?]', $width * $height * 3);
    $p = $ffi->cast('char *', $ffi->addr($rgb_buffer));
    $linesize = $ffi->new('int[3]', [$width * 3, 0, 0]);
    $ffi->av_image_fill_arrays($frame->data, $frame->linesize, $p, AV_PIX_FMT_RGB24, $width, $height, 1);
    $ffi->sws_scale($sws, [$p], $linesize, 0, $height, $frame->data, $frame->linesize);
    $frame->pts = $i;
    $ret = $ffi->avcodec_send_frame($codec_ctx, $frame);
    if ($ret < 0) {
        throw new Exception('Cannot encode video frame');
    }
    while ($ret >= 0) {
        $ret = $ffi->avcodec_receive_packet($codec_ctx, $codec_pkt);
        if ($ret < 0) {
            break;
        }
        //将$codec_pkt->data中的视频数据作为流推送到服务器,代码略
        $ffi->av_packet_unref($codec_pkt);
    }   
}

上記のコードは、FFmpeg を使用して、取得した RGB24 ピクセル データをエンコードおよび変換し、変換されたビデオ データをサーバーにプッシュします。別のエンコーダを使用したい場合は、上記のコードの AV_CODEC_ID_H264 を置き換えるだけです。

3. PHP を使用してライブ ブロードキャスト配信を実現する

PHP でライブ ブロードキャスト配信を実現するには、サーバー配信や P2P 配信などさまざまな方法があります。サーバー配信は、処理されたビデオ データをサーバーにストリーミングすることを指し、中間サーバーはビデオをトランスコードして配信できます。P2P 配信は、UDP プロトコルを通じてビデオ データを他のユーザーに直接配信することを指します。

次は、Ratchet フレームワークを介してサーバー分散を実装するコードです:

use RatchetServerIoServer;
use RatchetWebSocketWsServer;
use AppVideoServer;

require dirname(__DIR__) . '/vendor/autoload.php';

$server = IoServer::factory(new WsServer(new VideoServer()), 8080);
$server->run();
use RatchetConnectionInterface;
use RatchetMessageComponentInterface;
use ReactEventLoopFactory;
use ReactEventLoopLoopInterface;
use ReactStreamReadableResourceStream;

class VideoServer implements MessageComponentInterface {
    protected $clients;
    /** @var LoopInterface $loop */
    protected $loop;
    protected $sourceUrl;
    protected $sourceRenderer;

    public function __construct() {
        $this->clients = new SplObjectStorage();
        $this->loop = Factory::create();
        $this->sourceUrl = '';
        $this->sourceRenderer = null;
    }

    public function onOpen(ConnectionInterface $conn) {
        $this->clients->attach($conn);
        $conn->on('close', function() use ($conn) {
            $this->clients->detach($conn);
            if ($this->clients->count() === 0 && $this->sourceRenderer !== null) {
                $this->sourceRenderer->close();
                $this->sourceRenderer = null;
            }
        });

        if ($this->sourceRenderer === null) {
            $this->loop->futureTick(function() use ($conn) {
                $conn->send('Waiting for source video stream...');
            });
            return;
        }

        $resource = new ReadableResourceStream($this->sourceRenderer->stdout, $this->loop);
        $resource->on('data', function ($chunk) use ($conn) {
            foreach ($this->clients as $client) {
                if ($client !== $conn) {
                    $client->send($chunk);
                }
            }
        });
    }
    
    public function onMessage(ConnectionInterface $from, $msg) {
        if ($this->sourceRenderer === null) {
            $this->sourceUrl = trim($msg);
            $this->sourceRenderer = new ReactChildProcessProcess('ffmpeg -i ' . escapeshellarg($this->sourceUrl) . ' -c:v libx264 -preset superfast -tune zerolatency -b:v 400k -pix_fmt yuv420p -r 30 -f mpegts -');
            $this->sourceRenderer->start($this->loop);
        }
    }

    public function onClose(ConnectionInterface $conn) {
        $this->clients->detach($conn);
        if ($this->clients->count() === 0 && $this->sourceRenderer !== null) {
            $this->sourceRenderer->close();
            $this->sourceRenderer = null;
        }
    }

    public function onError(ConnectionInterface $conn, Exception $e) {}
}

上記のコードは、Ratchet フレームワークを介して WebSocket サーバーを実装します。ユーザーがサーバーに接続すると、サーバーは FFmpeg を実行してビデオ ストリームを処理するサブプロセスを開始し、処理されたビデオ ストリームを WebSocket 経由でユーザーにプッシュします。複数のユーザーがサーバーに接続している場合、サーバーは処理されたビデオ ストリームを各ユーザーにリアルタイムで配信します。

4. まとめ

この記事では、PHP を使用してライブ ストリーミング機能を実装する方法を紹介します。まず、FFmpeg を呼び出してビデオ データをエンコードし、エンコードされたビデオ データをストリームを通じてサーバーにプッシュし、最後に Ratchet フレームワークを通じてサーバーに配信し、処理されたビデオ ストリームをリアルタイムでユーザーに配信します。 。これらのテクノロジーは効率的なライブ ストリーミング機能を実現するために不可欠であり、開発者がライブ ストリーミング アプリケーションの開発を簡単に完了するのに役立ちます。

以上がPHPでライブストリーミング機能を実装の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。