摘要
在需要处理视频流的项目中通常会用到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 = NUllwhile (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, &packet); if (packet.stream_index != videoStream) { av_packet_unref(&packet); continue; } // 获得解码数据,并做图像 size 变化 if (ret = avcodec_receive_frame(pCodecCtx, pFrame) == 0){ sws_scale(d.sws_ctx, (const uint8_t* const*),pFrame->data, pFrame->linesize, 0, imgHeight, pFrameBGR->data, pFrameBGR->linesize); // 获得 cv_mat 格式数据 cv::Mat bgr24(imgHeight, imgWidth, CV_8UC3, pFrameBGR->data); res.frame = bgr24.clone(); res.result = SUCCESS; res.relativeTimestamp = pFrame->pts * av_q2d(video->time_base) * 1000; // 数据放到图片队列中 lock.lock(); if (frameQueue.size() >= MAX_QUEUE_SIZE) { frameQueue.pop(); } frameQueue.push(res); lock.unlock(); } av_packet_unref(&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 5typedef 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 下的原始话题分离的讨论话题