参考资料 https://zhuanlan.zhihu.com/p/142593316
ffmpeg概念
多媒体文件是一个容器,在容器中有很多流,每个流是由不同的编码器编码的,一个包中包含一个或多个帧
ffmpeg 模块
libavcodec
提供了一系列编码器的实现
libavformat
实现了流协议、容器格式及其基本IO访问
libswscale
支持色彩转换和缩放功能
libavfilter
提供了各种音视频过滤器
libavdevice
提供了访问获取设备和回放设备的接口
libswresample
实现了混音和重采样
libavutil
包括了hash器,解码器和各种工具函数
变量
封装格式
AVFormatContext
这个类是最顶层的结构体,通过直接和流文件相对应
AVFormatContext* pFormatCtx = avformat_alloc_context();
if(avformat_open_input(&pFormatCtx, filename, NULL, NULL) != 0)
{
printf("Can't find the stream!\n");
}
AVFormatContext
描述了媒体文件的构成及基本信息,是统领全局的结构体,很多函数都要用到它作为参数,格式转换过程中实现输入和输出功能、保存相关数据的主要结构,描述一个媒体文件或媒体流构成和基本信息。
- nb_streams/streams:
AVStream
结构指针数组,包含了所有内嵌媒体流的描述,其内部有AVInputFormat
和AVOutputFormat
结构体,来表示输入输出的文件格式
AVFormatContext
是libavformat
中非常重要的结构,它几乎是ffmpeg中的一棵树,其成员AVStream可以包含0种或多种流nb_stream
。
在AVStream
中有可以包含已经打开的编解码器codec
,另外还有AVIOContext
成员,这个成员的作用是io。可以重写AVIOContext
结构的成员函数read_packet
和write_packet
等,来实现从不同介质读取音视频数据(比如从网络、内存或磁盘)
AVInputFormat
解复用器对象,每种输入的封装格式(FLV, MP4, TS等)对应一个该结构体
AVOutputFormat
复用器对象,每种输出的封装格式(FLV, MP4, TS等)对应一个该结构体
AVStream
用于描述一个媒体流,其中大部分信息可通过avformat_open_input
根据头文件信息确定,其他信息可通过avformat_find_stream_info
获得,典型的有视频流、中英文视频流、字幕流,可以通过av_new_stream
或avformat_new_stream
创建。
编解码
AVCodecContext
描述编解码器上下文的数据结构,保存AVCodec
指针和与编码器相关的数据
- codec_name
- height/width
- sample_fmt
- time_base
指向解码器的指针
AVCodecContext* pCodecCtx = pFormatCtx->streams[videoindex]->codec;
AVCodec *pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
它包含了当前流媒体的几乎所有参数(宽高,码率)以及编解码指针AVCodec
,甚至还可以设置硬件加速相关(linux下的VAAPI),其中最重要的就是AVCodec
,它直接指向编码器实现。
AVCodec
AVCodec* pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
if(pCodec == NULL){
printf("Cant't find the decoder !\n");
return -1;
}
解码器对象本身,每个AVCodecContext
中都包含一个AVCodec
。
可以通过avcodec_find_decoder
获得
AVCodecParameters
// 获取其中一个流的编码器参数
AVCodecParameters pCodecpar = pFormatCtx->streams[i]->codecpar;
// 根据编码器参数填充AVCodecContext
avcodec_parameters_to_context(pCodecCtx, pFormatCtx->streams[i]->codecpar);
编解码参数,每个AVStream中都含有一个AVCodecParameters,用来存放当前流的编解码参数。
数据存放
AVPacket
存放编码后,解码前的压缩数据,暂存解码之前的媒体数据(一个音/视频帧、一个字幕包)及附加信息(解码时间戳、显示时间戳、时长等),主要用于建立缓冲区并装载数据
- data/size/pos 数据缓冲区指针、长度和流媒体中的偏移量
- flags
-
AV_PKT_FLAG_KEY
表示该数据是一个关键帧 -
AV_PKT_FLAG_CORRUPT
表示该数据已经损坏
-
AVPacket* packet = (AVPacket*)malloc(sizeof(AVPacket));
if(av_read_frame(pFormatCtx, packet) >= 0){
...
}
AVFrame
AVFrame* pAvFrame = av_frame_alloc();
avcodec_decode_video2(pCodecCtx, pAvFrame, &got_picture, packet);
avcodec_decode_video2
在新的API被拆解成两个函数
ret = avcodec_send_packet(pCodecCtx, packet);
got_picture = avcodec_reveive_frame(pCodecCtx, pAvFrame);
存放编码前,解码后的原始数据
- data数组
- linesize
- key_frame 是否为关键帧
图像转换
SwsContext
图像转换上下文
libswscale
模块提供图像缩放、图像格式转换功能。其中贯穿整个模块的是SwsContext
结构体,方法包括sws_alloc_context
分配、sws_init_context
初始化、sws_getContext
获取上下文、sws_get_cachedContext
获取缓存,sws_freeContext
释放上下文的方法。
struct SwsContext *img_convert_ctx;
img_convert_ctx = sws_getContext(
pCodecCtx->width, // srcW
pCodecCtx->height, // dstW
pCodecCtx->pix_fmt,
pCodecCtx->width,
pCodecCtx->height,
AV_PIX_FMT_BGR24,
SWS_BICUBIC,
NULL, // srcFilter
NULL, // dstFilter
NULL
);
函数
av_register_all()
初始化ffmpeg的环境,过时的API,新版本不需要了
avformat_alloc_context()
分配一个AVFromatContext
AVFormatContext *pFormatCtx = avformat_alloc_context();
avformat_open_input()
https://ffmpeg.org/doxygen/4.1/group__lavf__decoding.html#ga31d601155e9035d5b0e7efedc894ee49
根据输入视频,获取AVCodecContext
。
if(avformat_open_input(&pFormatCtx, NULL) < 0){
printf("Can't find the stream!\n");
}
avformat_find_stream_info()
探测是否有AVFormatContext
中是否有stream存在
avcodec_alloc_context3()
为AVCodecContext分配内存,并利用传入的AVCodec初始化
avcodec_parameters_to_context()
获取AVCodecParameters
avcodec_find_decoder()
查找解码器
AVCodec *pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
avcodec_open2()
打开编码器
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
printf("Can't open the decoder !\n");
return -1;
}
av_frame_alloc()
为帧分配空间
AVFrame *pFrameBGR = av_frame_alloc();
av_image_get_buffer_size()
根据AVCodeContext
的记录的长宽高,计算buffer的大小
int size = av_image_get_buffer_size(AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height, 1);
av_image_fill_arrays()
填充AVFrame
的数据
AVFrame *pFrameBGR = av_frame_alloc();
uint8_t *out_buffer = (uint8_t *)av_malloc(size);
av_image_fill_arrays(pFrameBRG->data, pFrameBRG->linesize, out_buffer, AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height, 1);
av_dump_format()
打印AVFormatContext
和对应文件的信息
av_dump_format(pFormatCtx, 0, filename, 0);
sws_getContext()
获取SwsContext
上下文
SwsContext *img_convert_ctx;
img_convert_ctx = sws_getContext(
pCodecCtx->width,
pCodecCtx->pix_fmt,
pCodecCtx->width,
pCodecCtx->height,
AV_PIX_FMT_BGR24, // 设置sws_scale转换格式为BGR24,这样转换后可以直接使用OpenCV查看图像
SWS_BICUBIC,
NULL, // SwsFilter* srcFilter
NULL, // SwsFilter* dstFilter
NULL
);
av_read_frame()
从AVFormatContext
读取一个AVPacket
AVPacket* packet = (AVPacket*)malloc(sizeof(AVPacket));
if(av_read_frame(pFormatCtx, packet) >= 0){
...
}
avcodec_decode_video2()
这个函数过时了,可以替换为avcode_send_packet
和avcodec_receive_frame
从AVpacket
读取一帧到AVFrame
中
int ret = avcodec_decode_video2(pCodecCtx, pAvFrame, &got_picture, packet);
avcode_send_packet()
发送编码数据包
ret = avcodec_send_packet(pCodecCtx, packet);
avcodec_receive_frame()
接收编码后的数据
got_picture = avcodec_receive_frame(pCodeCtx, pAvFrame);
sws_scale
将一个AVFrame
的data转换为另一个AVFrame
的data
sws_scale(
img_convert_ctx,
(const uint8_t* const*)pAvFrame->data,
pAvFrame->lineSize,
0,
pCodecCtx->height,
pFrameBGR->data,
pFrameBGR->linesize,
);
av_packet_unref()
av_packet_unfer(packet);
av_free()
// uint8_t *out_buffer = (uint8_t *)av_malloc(size);
av_free(out_buffer);
// pAvFrame = av_frame_alloc();
av_free(pAvFrame);
avcodec_close()
avcodec_close(pCodecCtx);
avformat_close_input()
avformat_close_input(&pFormatCtx);
sws_freeContext()
sws_freeContext(img_convert_ctx);
一个跑通的ffmpeg转opencv的demo
旧API
// https://blog.csdn.net/guyuealian/article/details/79607568
#define __STDC_CONSTANT_MACROS
#include <opencv2/opencv.hpp>
extern "C"
{
#include "libavutil/avutil.h"
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h" //新版里的图像转换结构需要引入的头文件
}
#include <iostream>
const char* filename = "/home/cf206/下载/palace.mp4";
int main()
{
AVCodec *pCodec; //解码器指针
AVCodecContext* pCodecCtx; //ffmpeg解码类的类成员
AVFrame* pAvFrame; //多媒体帧,保存解码后的数据帧
AVFormatContext* pFormatCtx; //保存视频流的信息
av_register_all();
pFormatCtx = avformat_alloc_context();
if (avformat_open_input(&pFormatCtx, filename, NULL, NULL) != 0) { //检查文件头部
printf("Can't find the stream!\n");
}
if (avformat_find_stream_info(pFormatCtx, NULL)<0) { //查找流信息
printf("Can't find the stream information !\n");
}
int videoindex = -1;
for (int i = 0; i < pFormatCtx->nb_streams; ++i) //遍历各个流,找到第一个视频流,并记录该流的编码信息
{
if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
videoindex = i;
break;
}
}
if (videoindex == -1) {
printf("Don't find a video stream !\n");
return -1;
}
pCodecCtx = pFormatCtx->streams[videoindex]->codec; //得到一个指向视频流的上下文指针
pCodec = avcodec_find_decoder(pCodecCtx->codec_id); //到该格式的解码器
if (pCodec == NULL) {
printf("Cant't find the decoder !\n"); //寻找解码器
return -1;
}
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) { //打开解码器
printf("Can't open the decoder !\n");
return -1;
}
pAvFrame = av_frame_alloc(); // 分配帧存储空间
AVFrame *pFrameBGR = av_frame_alloc(); // 存储解码后的BGR数据
int size = avpicture_get_size(AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height);
uint8_t *out_buffer = (uint8_t *)av_malloc(size);
avpicture_fill((AVPicture *)pFrameBGR, out_buffer, AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height);
AVPacket* packet = (AVPacket*)malloc(sizeof(AVPacket));
printf("-----------输出文件信息---------\n");
av_dump_format(pFormatCtx, 0, filename, 0);
printf("------------------------------");
struct SwsContext *img_convert_ctx;
img_convert_ctx = sws_getContext(pCodecCtx->width,
pCodecCtx->height,
pCodecCtx->pix_fmt,
pCodecCtx->width,
pCodecCtx->height,
AV_PIX_FMT_BGR24, //设置sws_scale转换格式为BGR24,这样转换后可以直接用OpenCV显示图像了
SWS_BICUBIC,
NULL, NULL, NULL);
int ret;
int got_picture;
for (;;)
{
if (av_read_frame(pFormatCtx, packet) >= 0)
{
if (packet->stream_index == videoindex)
{
ret = avcodec_decode_video2(pCodecCtx, pAvFrame, &got_picture, packet);
if (ret < 0)
{
printf("Decode Error.(解码错误)\n");
return -1;
}
if (got_picture)
{
//YUV to RGB
sws_scale(img_convert_ctx,
(const uint8_t* const*)pAvFrame->data,
pAvFrame->linesize,
0,
pCodecCtx->height,
pFrameBGR->data,
pFrameBGR->linesize);
cv::Mat mRGB(cv::Size(pCodecCtx->width, pCodecCtx->height), CV_8UC3);
mRGB.data =(uchar*)pFrameBGR->data[0];//注意不能写为:(uchar*)pFrameBGR->data
cv::imshow("RGB", mRGB);
cv::waitKey(40);
}
}
av_free_packet(packet);
}
else
{
break;
}
}
av_free(out_buffer);
av_free(pFrameBGR);
av_free(pAvFrame);
avcodec_close(pCodecCtx);
avformat_close_input(&pFormatCtx);
sws_freeContext(img_convert_ctx);
system("pause");
}
新API
改写成新版ffmpeg的API
#define __STDC_CONSTANT_MACROS
#include <opencv2/opencv.hpp>
extern "C"
{
#include "libavutil/avutil.h"
#include "libavcodec/avcodec.h"
#include "libavutil/imgutils.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h" //新版里的图像转换结构需要引入的头文件
}
#include <iostream>
const char* filename = "/home/tuo/Videos/tracking.mp4";
int main()
{
AVCodec *pCodec; //解码器指针
AVCodecContext* pCodecCtx; //ffmpeg解码类的类成员
AVFrame* pAvFrame; //多媒体帧,保存解码后的数据帧
AVFormatContext* pFormatCtx; //保存视频流的信息
// av_register_all();
pFormatCtx = avformat_alloc_context();
if (avformat_open_input(&pFormatCtx, filename, NULL, NULL) != 0) { //检查文件头部
printf("Can't find the stream!\n");
}
if (avformat_find_stream_info(pFormatCtx, NULL)<0) { //查找流信息
printf("Can't find the stream information !\n");
}
// int videoindex = -1;
// for (int i = 0; i < pFormatCtx->nb_streams; ++i) //遍历各个流,找到第一个视频流,并记录该流的编码信息
// {
// if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
// videoindex = i;
// break;
// }
// }
pCodecCtx = avcodec_alloc_context3(NULL);
if (pCodecCtx == NULL)
{
printf("Could not allocate AVCodecContext\n");
return -1;
}
int videoindex = -1;
for (int i = 0; i < pFormatCtx->nb_streams; ++i) //遍历各个流,找到第一个视频流,并记录该流的编码信息
{
avcodec_parameters_to_context(pCodecCtx, pFormatCtx->streams[i]->codecpar);
if (pCodecCtx->codec_type == AVMEDIA_TYPE_VIDEO) {
videoindex = i;
break;
}
}
if (videoindex == -1) {
printf("Don't find a video stream !\n");
return -1;
}
pCodec = avcodec_find_decoder(pCodecCtx->codec_id); //到该格式的解码器
if (pCodec == NULL) {
printf("Cant't find the decoder !\n"); //寻找解码器
return -1;
}
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) { //打开解码器
printf("Can't open the decoder !\n");
return -1;
}
pAvFrame = av_frame_alloc(); // 分配帧存储空间
AVFrame *pFrameBGR = av_frame_alloc(); // 存储解码后的BGR数据
// int size = avpicture_get_size(AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height);
int size = av_image_get_buffer_size(AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height, 1);
uint8_t *out_buffer = (uint8_t *)av_malloc(size);
// avpicture_fill((AVPicture *)pFrameBGR, out_buffer, AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height);
av_image_fill_arrays(pFrameBGR->data, pFrameBGR->linesize, out_buffer, AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height, 1);
AVPacket* packet = (AVPacket*)malloc(sizeof(AVPacket));
printf("-----------输出文件信息---------\n");
av_dump_format(pFormatCtx, 0, filename, 0);
printf("------------------------------");
std::cout << pCodecCtx->height << " " << pCodecCtx->width << std::endl;
struct SwsContext *img_convert_ctx;
img_convert_ctx = sws_getContext(pCodecCtx->width,
pCodecCtx->height,
pCodecCtx->pix_fmt,
pCodecCtx->width,
pCodecCtx->height,
AV_PIX_FMT_BGR24, //设置sws_scale转换格式为BGR24,这样转换后可以直接用OpenCV显示图像了
SWS_BICUBIC,
NULL, NULL, NULL);
int ret;
int got_picture;
for (;;)
{
if (av_read_frame(pFormatCtx, packet) >= 0)
{
if (packet->stream_index == videoindex)
{
//ret = avcodec_decode_video2(pCodecCtx, pAvFrame, &got_picture, packet);
ret = avcodec_send_packet(pCodecCtx, packet);
got_picture = avcodec_receive_frame(pCodecCtx, pAvFrame);
if (ret < 0)
{
printf("Decode Error.(解码错误)\n");
return -1;
}
if (got_picture == 0)
{
//YUV to RGB
sws_scale(img_convert_ctx,
(const uint8_t* const*)pAvFrame->data,
pAvFrame->linesize,
0,
pCodecCtx->height,
pFrameBGR->data,
pFrameBGR->linesize);
cv::Mat mRGB(cv::Size(pCodecCtx->width, pCodecCtx->height), CV_8UC3);
mRGB.data =(uchar*)pFrameBGR->data[0];//注意不能写为:(uchar*)pFrameBGR->data
cv::imshow("RGB", mRGB);
cv::waitKey(40);
}
}
// av_free_packet(packet);
av_packet_unref(packet);
}
else
{
break;
}
}
av_free(out_buffer);
av_free(pFrameBGR);
av_free(pAvFrame);
avcodec_close(pCodecCtx);
avformat_close_input(&pFormatCtx);
sws_freeContext(img_convert_ctx);
system("pause");
}