PHP는 라이브 스트리밍 기능을 구현합니다.

WBOY
풀어 주다: 2023-06-22 13:32:01
원래의
1198명이 탐색했습니다.

인터넷의 지속적인 발전으로 라이브 방송은 사람들의 일상생활에서 없어서는 안 될 부분이 되었으며, 라이브 스트리밍 기능은 라이브 방송을 구현하는 핵심입니다. PHP의 출현은 웹 애플리케이션 개발에 강력한 도구를 가져왔으며 라이브 스트리밍 기능의 구현도 예외는 아닙니다. 이 기사에서는 PHP를 사용하여 라이브 스트리밍 기능을 구현하는 방법을 소개합니다.

1. 라이브 스트리밍의 기본 원리를 이해하세요

라이브 스트리밍 기능 구현 방법을 소개하기 전에 먼저 라이브 스트리밍의 기본 원리를 이해해야 합니다. 라이브 스트리밍이란 사용자가 라이브 방송을 할 때 영상 데이터를 서버에 업로드해 다른 사용자에게 배포하는 과정을 말한다. 특히 여기에는 라이브 방송 업로드와 라이브 방송 배포라는 두 가지 기본 링크가 포함되어 있습니다.

라이브 업로드는 사용자가 업로드한 비디오 데이터를 처리 및 저장하고 다양한 단말 장치의 재생 요구 사항을 충족하기 위해 실시간으로 트랜스코딩하는 것을 의미합니다. 실시간 배포란 처리된 영상 데이터를 다른 사용자가 시청할 수 있도록 전송하는 것을 의미합니다. 일반적으로 생방송 배포는 서버 배포와 P2P 배포의 두 가지 링크로 구분됩니다.

2. PHP를 사용하여 라이브 방송 업로드 구현

PHP에는 라이브 방송 업로드를 구현하는 데 사용할 수 있는 많은 라이브러리가 있습니다. 일반적으로 사용되는 라이브러리에는 FFmpeg, Libav 및 H264 스트리밍 모듈 등이 포함됩니다. 이들 라이브러리는 모두 C/C++ 언어를 기반으로 작성되었으며, PHP의 FFI 확장을 통해 PHP와 C/C++ 간의 상호 운용성을 구현할 수 있습니다. 구체적인 구현 과정은 다음과 같습니다.

1. FFI 확장 설치

PHP에서 FFI 확장을 사용하려면 먼저 확장을 설치해야 합니다. FFI 확장은 PHP 공식 웹사이트(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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
최신 이슈
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿
회사 소개 부인 성명 Sitemap
PHP 중국어 웹사이트:공공복지 온라인 PHP 교육,PHP 학습자의 빠른 성장을 도와주세요!