Maison  >  Article  >  développement back-end  >  PHP implémente la fonction de diffusion en direct

PHP implémente la fonction de diffusion en direct

WBOY
WBOYoriginal
2023-06-22 12:34:161244parcourir

Avec le développement continu d'Internet, la diffusion en direct est devenue un élément indispensable de la vie quotidienne des gens, et la fonction de diffusion en direct est la clé pour réaliser une diffusion en direct. L'émergence de PHP a apporté un outil puissant au développement d'applications Web, et la mise en œuvre de la fonction de diffusion en direct ne fait pas exception. Cet article explique comment utiliser PHP pour implémenter la fonction de diffusion en direct.

1. Comprendre les principes de base de la diffusion en direct

Avant de présenter comment mettre en œuvre la fonction de diffusion en direct, vous devez d'abord comprendre les principes de base de la diffusion en direct. La diffusion en direct fait référence au processus de téléchargement de données vidéo sur le serveur et de leur distribution à d'autres utilisateurs lorsque les utilisateurs effectuent des diffusions en direct. Plus précisément, il comprend deux liens de base : le téléchargement de diffusion en direct et la distribution de diffusion en direct.

Le téléchargement en direct fait référence au traitement et au stockage des données vidéo téléchargées par les utilisateurs, et à leur transcodage en temps réel pour répondre aux besoins de lecture des différents terminaux. La distribution en direct fait référence à la transmission de données vidéo traitées à d'autres utilisateurs pour qu'ils les regardent. Habituellement, la distribution de diffusion en direct est divisée en deux liens : la distribution sur serveur et la distribution P2P.

2. Utilisez PHP pour implémenter le téléchargement de diffusion en direct

En PHP, il existe de nombreuses bibliothèques qui peuvent être utilisées pour implémenter le téléchargement de diffusion en direct. Les bibliothèques couramment utilisées incluent FFmpeg, Libav et H264 Streaming Module, etc. Ces bibliothèques sont toutes écrites sur la base du langage C/C++, et l'interopérabilité entre PHP et C/C++ peut être obtenue grâce à l'extension FFI de PHP. Le processus d'implémentation spécifique est le suivant :

1. Installez l'extension FFI

Pour utiliser l'extension FFI en PHP, vous devez d'abord installer l'extension. Vous pouvez télécharger l'extension FFI depuis le site officiel de PHP (https://www.php.net/manual/zh/ffi.installation.php) ou l'installer à l'aide du gestionnaire de packages. Une fois l'installation terminée, ajoutez le code suivant au fichier php.ini :

extension=ffi.so

2. Utilisez FFmpeg pour implémenter l'encodage vidéo

En utilisant FFmpeg pour encoder les données vidéo, vous devez diviser les données vidéo en plusieurs images et effectuer une conversion de codage sur chaque image. Le processus de mise en œuvre spécifique est le suivant :

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);
    }   
}

Le code ci-dessus utilise FFmpeg pour encoder et convertir les données de pixels RVB24 obtenues, et transmettre les données vidéo converties au serveur. Si vous souhaitez utiliser un autre encodeur, remplacez simplement AV_CODEC_ID_H264 dans le code ci-dessus.

3. Utilisez PHP pour implémenter la distribution de diffusion en direct

Il existe de nombreuses façons d'implémenter la distribution de diffusion en direct en PHP, notamment la distribution sur serveur et la distribution P2P. La distribution sur serveur fait référence à la diffusion des données vidéo traitées vers le serveur, et le serveur intermédiaire peut transcoder et distribuer la vidéo ; la distribution P2P fait référence à la distribution de données vidéo directement à d'autres utilisateurs via le protocole UDP.

Ce qui suit est le code pour implémenter la distribution du serveur via le framework 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) {}
}

Le code ci-dessus implémente le serveur WebSocket via le framework Ratchet. Lorsqu'un utilisateur se connecte au serveur, le serveur démarre un sous-processus pour exécuter FFmpeg afin de traiter le flux vidéo et transmet le flux vidéo traité à l'utilisateur via WebSocket. Si plusieurs utilisateurs sont connectés au serveur, le serveur distribuera le flux vidéo traité à chaque utilisateur en temps réel.

4. Résumé

Cet article présente comment utiliser PHP pour implémenter la fonction de diffusion en direct. Tout d'abord, les données vidéo sont codées en appelant FFmpeg, puis les données vidéo codées sont transmises au serveur via le flux. Enfin, le serveur est distribué via le framework Ratchet et le flux vidéo traité est distribué à l'utilisateur en temps réel. . Ces technologies sont cruciales pour obtenir des fonctions de diffusion en direct efficaces et peuvent aider les développeurs à terminer facilement le développement d'applications de diffusion en direct.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn