摘要
在需要处理视频流的项目中通常会用到FFmpeg进行视频流的解码和编码,本文对解码流程进行简单的介绍,通过C++进行简单复现,加深对视频流解码流程的熟悉。
主要流程
引用
#include "decode.h"
#include <iostream>
#include <sstream>
#include <thread>
#include <chrono>
#include <iomanip>
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<FRAME> *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, &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 <stdio.h>
#include <mutex>
#include <queue>
#include <string>
#include <thread>
#include <unistd.h>
#include <vector>
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui_c.h>
#include "libyuv.h"
#include "timestamp.h"
// #include "opencv2/imgproc/imgproc.hpp"
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/pixdesc.h>
#include <libavutil/hwcontext.h>
#include <libavutil/opt.h>
#include <libavutil/avassert.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
#include <libavdevice/avdevice.h>
}
#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<FRAME> 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;
};