FFmpeg 硬解码 RTMP/RTSP 视频流

摘要

在需要处理视频流的项目中通常会用到FFmpeg进行视频流的解码和编码,本文对解码流程进行简单的介绍,通过C++进行简单复现,加深对视频流解码流程的熟悉。

主要流程

引用

#include "decode.h"
#include 
#include 
#include 
#include 
#include 

1、注册FFmpeg相关组件

FFMPEG 3.x 版本:

//把全局的解码器、编码器等结构体注册到各自全局的对象链表里
av_register_all();

// 注册所有的硬件设备,需要其他硬件硬解码需要这一步,仅需要CPU解码则不需要
avdevice_register_all();

// 初始化网络库以及网络加密协议相关的库,解码网络流
avformat_network_init();

FFMPEG 4.x 版本:

av_register_all() 方法FFmepg内部去做,不需要用户调用API去注册。其他注册和FFMPEG 3.x 版本一致

2、解封装,打开视频流并获取相关上下文

// 为上下文申请内存
AVFormatContext *pFormatCtx = avformat_alloc_context();

// 视频文件路径或者网络流地址
const char *url = streamInfo.url.c_str();

// 视频封装格式,传空指针进去会自动识别
AVInputFormat *inputFmt = NULL;

// 包含视频上下文和demuxer私有选项的字典。此参数会替换为找不到的字典项
AVDictionary *options = NULL;

// 解封装并获得视频上下文以及异常处理
if ((ret = avformat_open_input(&pFormatCtx, url, inputFmt, &options)) < 0) {
ifError = true;
fprintf(stderr, “[libdecode] Cannot open input file ‘%s’\n”, streamInfo.url);
if (ifmt_ctx != NULL) {
avformat_close_input(&pFormatCtx);
ifmt_ctx = NULL;
}
sleep(SLEEPTIME);
continue;
}

// 释放字典内存
av_dict_free(&options);

3、寻找视频流

ret = avformat_find_stream_info(pFormatCtx, nullptr);
if (ret < 0) {
    printf("avformat_find_stream_info failed, ret: ");
}
if ((ret = d.findVideoStreamIndex()) < 0) {
    printf("findVideoStreamIndex failed, ret: " );
}

4、获得并打开解码器

// 获取指向视频流的编解码器上下文的指针
video = pFormatCtx->streams[videoStream];

// 声明解码器
AVCodec* pCodec = nullptr;

// 根据视频流类型确定解码器,此处以英伟达的硬解码为例
switch (video->codecpar->codec_id) {
case AV_CODEC_ID_H264:
pCodec = avcodec_find_decoder_by_name(“h264_cuvid”);
break;
case AV_CODEC_ID_HEVC:
pCodec = avcodec_find_decoder_by_name(“hevc_cuvid”);
break;
default:
pCodec = avcodec_find_decoder(video->codecpar->codec_id);
break;
}
if (pCodec == nullptr) {
printf(“Unsupported codec!”);
}

// 将视频流的信息拷贝到解码器中
pCodecCtx = avcodec_alloc_context3(pCodec);
if (avcodec_parameters_to_context(pCodecCtx, video->codecpar) != 0) {
printf(“Couldn’t copy codec context!”);
}
AVDictionary *decoder_opts = NULL;

// 设置硬件ID号
av_dict_set_int(&decoder_opts, “device_id”, devId, 0);

// 打开解码器
if (avcodec_open2(pCodecCtx, pCodec, &decoder_opts) < 0) {
printf(“Could not open codec!”);
}

5、声明AVPacket和AVFrame以及申请内存,以及申请缓存区

// 声明AVPacket,包含视频流的一些信息:显示时间戳(pts)、解码时间戳(dts)、数据时长,所在媒体流的索引等
AVPacket packet;
av_init_packet(&packet);

// 声明AVFrame
AVFrame *pFrame = av_frame_alloc();
AVFrame *pFrameBGR = av_frame_alloc();

// 确定解码所需的缓冲区大小并分配缓冲区
imgWidth = pCodecCtx->width;
imgHeight = pCodecCtx->height;
imgSize = av_image_get_buffer_size(AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height, 1);
buffer = (uint8_t*)av_malloc(imgSize * sizeof(uint8_t));

// 将缓冲区的分配给 pFrame 一部分
av_image_fill_arrays(pFrame->data,pFrame->linesize, d.buffer, AV_PIX_FMT_RGB24, d.imgWidth, d.imgHeight, 1);

// 初始化 SWS 上下文,用于后续图像处理和格式化
sws_ctx = sws_getContext(pCodecCtx->width,pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_RGB24, SWS_FAST_BILINEAR, nullptr, nullptr, nullptr);

6、解码

FRAME res;
res.result = NONE;
res.relativeTimestamp = 0;

图片队列,存放解码后的数据

std::queue *frameQueue = NULL;
std::mutex *lock = NUll

while (runFlag == RUN) {
// 获取视频流一帧数据
int ret = av_read_frame(d.pFormatCtx, &packet);
if (ret < 0) {
printf(" av_read_frame ret eof!");
}
if (packet.stream_index != d.videoStream) {
av_packet_unref(&packet);
continue;
}

// 发送待解码包
ret = avcodec_send_packet(pCodecCtx, &amp;packet);
if (packet.stream_index != videoStream) {
av_packet_unref(&amp;packet);
continue;
}

// 获得解码数据,并做图像 size 变化
if (ret = avcodec_receive_frame(pCodecCtx, pFrame) == 0){
    sws_scale(d.sws_ctx, (const uint8_t* const*),pFrame-&gt;data,
        pFrame-&gt;linesize, 0, imgHeight, pFrameBGR-&gt;data, pFrameBGR-&gt;linesize);

    // 获得 cv_mat 格式数据
    cv::Mat bgr24(imgHeight, imgWidth, CV_8UC3, pFrameBGR-&gt;data);
    res.frame = bgr24.clone();
    res.result = SUCCESS;
    res.relativeTimestamp = pFrame-&gt;pts * av_q2d(video-&gt;time_base) * 1000;

    // 数据放到图片队列中
    lock.lock();
    if (frameQueue.size() &gt;= MAX_QUEUE_SIZE) {
        frameQueue.pop();
    }
    frameQueue.push(res);
    lock.unlock();
    }
    av_packet_unref(&amp;packet);

}
// 释放内存
av_frame_free(&d.pFrameRGB);
av_frame_free(&d.pFrame);
sws_freeContext(d.sws_ctx);
avcodec_close(d.pCodecCtx);
avcodec_free_context(&d.pCodecCtx);
avformat_close_input(&d.pFormatCtx);

队列中取图像

FRAME Decode::Read() {
    FRAME res;
    lock.lock();
    // printf("Queue size: %d\n", frameQueue.size());
    if (ifError) {
    res.result = ERROR;
    } else if (frameQueue.size() == 0) {
    res.result = NONE;
    } else {
    res = frameQueue.front();
    frameQueue.pop();
    }
    lock.unlock();        
    return res;
}

头文件


#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "libyuv.h"
#include "timestamp.h"
// #include "opencv2/imgproc/imgproc.hpp"
extern "C" {
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
}

#define SLEEPTIME 60
#define END 1
#define SUCCESS 0
#define NONE -1
#define ERROR -2

#define RUN 1
#define STOP 0

#define MAX_QUEUE_SIZE 3

#define RTSP 1
#define RTMP 2
#define ONVIF 3
#define GB 4
#define FILE 5

typedef struct Struct_STREAMINFO {
int protocol;
std::string url;
std::string ip;
int port;
std::string username;
std::string password;
std::string format;
} STREAMINFO;

typedef struct Struct_FRAME {
int result;
cv::Mat frame;
int relativeTimestamp; // 当前帧相对于初始帧的毫秒时间戳
} FRAME;

typedef void (*HW_CALLBACK)(unsigned char *buf, int xsize, int ysize, void *userdef);

class Decode {

public:
Decode(STREAMINFO inputStreamInfo, std::string decoder, std::string pixel_transform);
~Decode();

void Init();
FRAME Read();
void Release();

private:
STREAMINFO streamInfo;
std::string decoder_type;
std::string pixel_transform_type;
bool ifError;
int video_stream_idx;
int got_picture;
int runFlag;
std::queue frameQueue;
std::mutex lock;

AVDictionary *options;
AVInputFormat *inputFmt;
AVFormatContext *ifmt_ctx;
AVCodecContext *pCodecCtx;
AVCodec *decoder;
AVFrame *pFrame;
AVFrame *FrameBGR;
AVPacket *pPacket;


struct SwsContext *img_convert_ctx;

};
</unistd.h></stdio.h>


这是一个从 https://juejin.cn/post/7369132465020190746 下的原始话题分离的讨论话题