Rumah  >  Artikel  >  pembangunan bahagian belakang  >  PHP melaksanakan fungsi penstriman langsung

PHP melaksanakan fungsi penstriman langsung

WBOY
WBOYasal
2023-06-22 12:34:161199semak imbas

Dengan perkembangan berterusan Internet, siaran langsung telah menjadi bahagian yang amat diperlukan dalam kehidupan seharian orang ramai, dan fungsi penstriman langsung adalah kunci untuk merealisasikan siaran langsung. Kemunculan PHP telah membawa alat yang berkuasa untuk pembangunan aplikasi web, dan pelaksanaan fungsi penstriman langsung tidak terkecuali. Artikel ini akan memperkenalkan cara menggunakan PHP untuk melaksanakan fungsi penstriman langsung.

1. Fahami prinsip asas penstriman langsung

Sebelum memperkenalkan cara melaksanakan fungsi penstriman langsung, anda perlu terlebih dahulu memahami prinsip asas penstriman langsung. Penstriman langsung merujuk kepada proses memuat naik data video ke pelayan dan mengedarkannya kepada pengguna lain apabila pengguna melakukan siaran langsung. Secara khusus, ia termasuk dua pautan asas: muat naik siaran langsung dan pengedaran siaran langsung.

Muat naik langsung merujuk kepada pemprosesan dan penyimpanan data video yang dimuat naik oleh pengguna, dan transkodkannya dalam masa nyata untuk memenuhi keperluan main balik peranti terminal yang berbeza. Pengedaran langsung merujuk kepada penghantaran data video yang diproses kepada pengguna lain untuk mereka tonton. Biasanya pengedaran siaran langsung dibahagikan kepada dua pautan: pengedaran pelayan dan pengedaran P2P.

2. Gunakan PHP untuk melaksanakan muat naik siaran langsung

Dalam PHP, terdapat banyak perpustakaan yang boleh digunakan untuk melaksanakan muat naik siaran langsung. Perpustakaan yang biasa digunakan termasuk FFmpeg, Libav dan Modul Penstriman H264, dsb. Pustaka ini semuanya ditulis berdasarkan bahasa C/C++, dan kesalingoperasian antara PHP dan C/C++ boleh dicapai melalui sambungan FFI PHP. Proses pelaksanaan khusus adalah seperti berikut:

1 Pasang sambungan FFI

Untuk menggunakan sambungan FFI dalam PHP, anda perlu memasang sambungan terlebih dahulu. Anda boleh memuat turun sambungan FFI dari tapak web rasmi PHP (https://www.php.net/manual/zh/ffi.installation.php) atau memasangnya menggunakan pengurus pakej. Selepas pemasangan selesai, tambah kod berikut pada fail php.ini:

extension=ffi.so

2. Gunakan FFmpeg untuk melaksanakan pengekodan video

Gunakan FFmpeg untuk mengekod data video. Data video perlu dibahagikan kepada beberapa bingkai, dan setiap bingkai perlu dikodkan dan ditukar. Proses pelaksanaan khusus adalah seperti berikut:

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

Kod di atas menggunakan FFmpeg untuk mengekod dan menukar data piksel RGB24 yang diperoleh, dan menolak data video yang ditukar ke pelayan. Jika anda ingin menggunakan pengekod yang berbeza, cuma gantikan AV_CODEC_ID_H264 dalam kod di atas.

3. Gunakan PHP untuk melaksanakan pengedaran siaran langsung

Terdapat banyak cara untuk melaksanakan pengedaran siaran langsung dalam PHP, termasuk pengedaran pelayan dan pengedaran P2P. Pengedaran pelayan merujuk kepada penstriman data video yang diproses ke pelayan, dan pelayan perantaraan boleh mentranskod dan mengedarkan video pengedaran P2P merujuk kepada pengedaran data video terus kepada pengguna lain melalui protokol UDP;

Berikut ialah kod untuk melaksanakan pengedaran pelayan melalui rangka kerja 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) {}
}

Kod di atas melaksanakan pelayan WebSocket melalui rangka kerja Ratchet. Apabila pengguna menyambung ke pelayan, pelayan memulakan sub-proses untuk menjalankan FFmpeg untuk memproses strim video, dan menolak aliran video yang diproses kepada pengguna melalui WebSocket. Jika berbilang pengguna disambungkan ke pelayan, pelayan akan mengedarkan aliran video yang diproses kepada setiap pengguna dalam masa nyata.

4. Ringkasan

Artikel ini memperkenalkan cara menggunakan PHP untuk melaksanakan fungsi penstriman langsung. Pertama, data video dikodkan dengan memanggil FFmpeg, dan kemudian data video yang dikodkan ditolak ke pelayan melalui strim Akhirnya, pelayan diedarkan melalui rangka kerja Ratchet, dan strim video yang diproses diedarkan kepada pengguna dalam masa nyata. . Teknologi ini adalah penting untuk mencapai fungsi penstriman langsung yang cekap dan boleh membantu pembangun melengkapkan pembangunan aplikasi penstriman langsung dengan mudah.

Atas ialah kandungan terperinci PHP melaksanakan fungsi penstriman langsung. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn