FFMPEG--推送摄像头视频流发布m3u8

本文介绍了流媒体的直播和点播应用场景,重点讲解了HLS和RTMP协议的区别。在点播业务中,选择HLS协议因其良好的HTTP兼容性和码率平滑切换能力。HLS通过m3u8索引文件维护TS片段,但直播时延时较高。RTMP则适用于低延迟直播,但不被浏览器原生支持。文章还展示了FFMPEG用于推流的环境和文件目录。
摘要由CSDN通过智能技术生成

流媒体主要有两种应用场景,即直播和点播:

  • 直播:服务端实时发送直播来源(如系统桌面、摄像头)的数据流,客户端通过支持流媒体协议的播放器实时播放同样的内容,不可拖动进度。
  • 点播:服务端存放多个视频文件,客户端可通过网路点播客户端任意观看其中一个视频,并可拖动进度进行观看。

流媒体技术能提供诸如视频加密和播放体验大幅提升等优点。

协议的选择

流媒体播放有两种协议可供选择:HLS 和 RMTP。

  • HLS,是苹果公司实现的基于 HTTP 的流媒体传输协议,全称 HTTP Live Streaming,可支持流媒体的直播和点播,主要应用在 iOS 系统,为 iOS 设备(如 iPhone、iPad)提供音视频直播和点播方案。
  • RTMP,实时消息传输协议,Real Time Messaging Protocol,是 Adobe Systems 公司为 Flash 播放器和服务器之间音频、视频和数据传输开发的开放协议。协议基于 TCP,是一个协议族,包括 RTMP 基本协议及 RTMPT/RTMPS/RTMPE 等多种变种。RTMP 是一种设计用来进行实时数据通信的网络协议,主要用来在 Flash/AIR 平台和支持RTMP协议的流媒体/交互服务器之间进行音视频和数据通信。

在web点播业务中选用HLS协议,两种协议的详细介绍和选择HLS的原因会在下文给出。

HLS协议
HLS协议规范文档如下:https://tools.ietf.org/html/rfc8216
HLS 数据通过 HTTP 协议传输,在H5中可以很容易得到支持,具有良好的兼容性。 HLS 的基本原理就是将视频分割为多个TS格式的文件片段存储在服务器,同时需要建立一个 m3u8 的索引文件来维护所有的 TS 片段的索引。客户端播放视频时,它是从 m3u8 索引文件获取 TS 视频文件片段来播放。相对于常见的流媒体播放协议,例如 RTMP 协议、RTSP 协议等,HLS 最大的不同在于直播客户端获取到的并不是一个完整的数据流,而是连续的、短时长的媒体文件,客户端不断的下载并播放这些小文件。每个 TS 文件的时长并无强制规定,可以根据需求自由分割,推荐是 5-10 秒一个分片。

值得一提的是,由于 HLS 的分段策略,如果应用于直播,则理论最小延时为一个 TS 文件的时长,一般情况为 2-3 个 ts 文件的时长,通常 HLS 在直播场景可能的延时会达到 20-30s,而高延时对于需要实时互动体验的直播来说是不可接受的。这就意味着,如果直播过程中需要双向互动,则HLS不太适合。当然,对于点播来说,则无需考虑上述问题。

HLS 使用短时长的分片文件来播放,客户端可以平滑的切换码率,以适应不同带宽条件下的播放。

HLS原理示意图如下:
在这里插入图片描述
RTMP 协议
相对于 HLS 来说,采用 RTMP 协议时,对于直播场景,从采集推流端到流媒体服务器再到播放端是一条数据流,因此在服务器不会有落地文件。相对来说,延时较小,通常为 1-3s,参考播放器 如ijkplayer、毫秒级的播放器,可以参考大牛直播SDK的RTMP播放器。

因此业界大部分直播业务都会选择用 RTMP 作为流媒体协议。
但是RTMP也有一些问题需要解决,浏览器无法原生支持该协议,如要在web中应用,就需要开发支持相关协议的播放器。因此该协议多应用 native 环境。

综上,针对点播业务场景,HLS显然是最合适的选择

m3u8文件格式详解
m3u8 文件本质上是一个文本文件,下载上述示例文件(test.m3u8),并用文本编辑器打开,内容如下(部分):

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:4
#EXT-X-MEDIA-SEQUENCE:3
#EXTINF:4.300000,
test3.ts
#EXTINF:4.133333,
test4.ts
#EXTINF:1.933333,
test0.ts
#EXTINF:2.700000,
test1.ts
#EXTINF:3.166667,
test2.ts

m3u8 用 UTF-8 编码, 实质是一个播放列表(playlist), 其内部信息记录的是一系列媒体片段资源,顺序播放该片段资源,即可完整展示多媒体资源。对于点播来说,客户端只需按顺序下载上述片段资源,依次进行播放即可。而对于直播来说,客户端需要定时重新请求该 m3u8 文件,看下是否有新的片段数据需要进行下载并播放。

需要注意,m3u8文件本身及TS文件,均需保证与当前域一致或者目标服务器能够支持当前域跨域。

环境

在这里插入图片描述

FFMPEG 版本

ffmpeg-4.3.1.tar.bz2

./configure --prefix=./install --enable-shared --disable-static --disable-x86asm

文件目录

├── 3rdparty
│   ├── FreeSerif
│   ├── ffmpeg
│   │   ├── include
│   │   │   ├── libavcodec
│   │   │   ├── libavdevice
│   │   │   ├── libavfilter
│   │   │   ├── libavformat
│   │   │   ├── libavutil
│   │   │   ├── libswresample
│   │   │   └── libswscale
│   │   └── lib
│   ├── opencv
│   └── spd
│       └── spdlog
│           ├── cfg
│           ├── details
│           ├── fmt
│           │   └── bundled
│           └── sinks
├── CMakeLists.txt
├── README.md
├── build
├── docs
│   ├── imgs
│   │   └── spdlog.png
│   └── spdlog.md
├── incs
│   ├── CGDesktopCatch.hpp
│   ├── CGUsbCamera.hpp
│   ├── common.hpp
│   └── mylog.hpp
├── srcs
│   ├── CGDesktopCatch.cpp
│   └── CGUsbCamera.cpp
└── test
    ├── CGDesktopCatchDemo.cpp
    ├── CMakeLists.txt
    ├── UsbCameraDemo.cpp
    └── spddemo.cpp

common.hpp

#pragma once

extern "C" {
   
#include "libavcodec/avcodec.h"
#include "libavdevice/avdevice.h"
#include "libavfilter/avfilter.h"
#include "libavfilter/buffersink.h"
#include "libavfilter/buffersrc.h"
#include "libavformat/avformat.h"
#include "libavformat/avio.h"
#include "libavutil/channel_layout.h"
#include "libavutil/common.h"
#include "libavutil/fifo.h"
#include "libavutil/imgutils.h"
#include "libavutil/mathematics.h"
#include "libavutil/opt.h"
#include "libavutil/samplefmt.h"
#include "libavutil/time.h"
#include "libswresample/swresample.h"
#include "libswscale/swscale.h"
}
#include "mylog.hpp"

#define RET_OK (0)
#define RET_FAILED (-1)

CGDesktopCatch.hpp

#pragma once
#include <iostream>
#include <memory>
#include <string>
#include <thread>
#include "common.hpp"

class SwsScaleContext
{
   
  public:
    SwsScaleContext() {
   }
    void SetSrcResolution(int width, int height)
    {
   
        srcWidth  = width;
        srcHeight = height;
    }

    void SetDstResolution(int width, int height)
    {
   
        dstWidth  = width;
        dstHeight = height;
    }
    void SetFormat(AVPixelFormat iformat, AVPixelFormat oformat)
    {
   
        this->iformat = iformat;
        this->oformat = oformat;
    }

  public:
    int srcWidth;
    int srcHeight;
    int dstWidth;
    int dstHeight;
    AVPixelFormat iformat;
    AVPixelFormat oformat;
};

class CGDesktopCatch
{
   
  public:
    AVFormatContext *inputContext  = nullptr;
    AVCodecContext *encodeContext  = nullptr;
    AVFormatContext *outputContext = nullptr;
    struct SwsContext *pSwsContext = nullptr;
    uint8_t *pSwpBuffer            = nullptr;
    AVFrame *videoFrame            = nullptr;
    AVFrame *pSwsVideoFrame        = nullptr;
    SwsScaleContext swsScaleContext;

    int64_t startTime;
    int64_t lastReadPacktTime;
    int64_t packetCount = 0;
    std::thread m_hThread;

  private:
    bool m_bStoped{
   true};
    bool m_bInputInited{
   false};
    bool m_bOutputInited{
   false};
    std::shared_ptr<std::mutex> ctxLock = std::make_shared<std::mutex>();

  public:
    CGDesktopCatch();
    ~CGDesktopCatch();

    void StartDecode();
    void StopDecode();

  private:
    void Init();
    void ReadingThrd(void *pParam);
    void run();
    int OpenInput(std::string inputUrl);
    int OpenOutput(std::string outUrl, AVCodecContext *encodeCodec);
    void CloseInput();
    void CloseOutput();
    void DecodeAndEncode();
    int WritePacket(std::shared_ptr<AVPacket> packet);
    bool Decode(AVStream *inputStream, AVPacket *packet, AVFrame *frame);
    int InitDecodeContext(AVStream *inputStream);
    int initSwsFrame(AVFrame *pSwsFrame, int iWidth, int iHeight);
    int initEncoderCodec(AVStream *inputStream, AVCodecContext **encodeContext);
    int initSwsContext(struct SwsContext **pSwsContext, SwsScaleContext *swsScaleContext);
    std::shared_ptr<AVPacket> Encode(AVCodecContext *encodeContext, AVFrame *frame);
    std::shared_ptr<AVPacket> ReadPacketFromSource();
};

CGDesktopCatch.cpp

#include "CGDesktopCatch.hpp"
#include <exception>
#include <iostream>
#include <memory>
#include <mutex>
#include <sstream>
#include <string>
#include <thread>

using namespace std;

static string AvErrToStr(int avErrCode)
{
   
    const auto bufSize = 1024U;
    char *errString    = (char *) calloc(bufSize, sizeof(*errString));
    if (!errString)
    {
   
        
要实现无缝推流推送顺序,可以使用 FFmpeg 的 `concat` 协议来合并多个视频文件,并通过 `segment_time` 参数设置每个分片的时长,从而实现无缝切换。 具体的操作步骤如下: 1. 将所有要推流的视频文件列表保存到一个文本文件中。例如,将要推流的视频文件列表保存到 `list.txt` 文件中,内容如下: ``` file 'video1.mp4' file 'video2.mp4' file 'video3.mp4' ``` 2. 使用 FFmpeg 的 `concat` 协议合并多个视频文件。示例命令如下: ``` ffmpeg -f concat -safe 0 -i list.txt -c copy -flags +global_header -f segment -segment_time 10 -segment_list_flags +live -segment_list playlist.m3u8 -segment_format mpegts output%03d.ts ``` 在上面的命令中,`-f concat -safe 0 -i list.txt` 表示以 `concat` 协议合并多个视频文件,`-c copy` 表示直接复制视频流,`-flags +global_header` 表示在每个分片的开头添加全局头,`-f segment -segment_time 10 -segment_list_flags +live -segment_list playlist.m3u8 -segment_format mpegts output%03d.ts` 表示将合并后的视频文件切分成 10 秒的分片,并生成 M3U8 播放列表和多个分片文件。 3. 在推流时,推送生成的 M3U8 播放列表即可。例如,使用 FFmpeg 推流到本地的 nginx-rtmp 服务器,示例命令如下: ``` ffmpeg -re -i playlist.m3u8 -c copy -f flv rtmp://localhost/hls/stream1 ``` 在上面的命令中,`-re` 表示以实时模式推流,`-i playlist.m3u8` 表示推送生成的 M3U8 播放列表,`-c copy` 表示直接复制视频流,`-f flv` 表示推送到一个 RTMP 服务器的地址。 通过以上的方法,就可以无缝推流推送顺序了。推流过程中,如果需要添加水印或者进行其他处理,可以在合并视频文件时一起处理,例如使用 FFmpeg 的滤镜功能等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

血_影

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值