视频解码实现的是将压缩域的视频数据解码为像素域的 YUV 数据。实现的过程,可以大致用如下图所示:
从图中可以看出,大致可以分为下面三个步骤:
void av_register_all(void);
作用:
该函数注册支持的所有的文件格式(容器)及其对应的CODEC,只需要调用一次。
注意点:在ffmpeg4.0之后的版本后,已经被弃用,为了兼容之前版本,在ffmpeg4.0之后的版本中,还保留该函数,但其内部其实什么都没有做,所以也可以直接省略。但在ffmpeg5.0之后,该函数完全被弃用,即无法完成函数声明。
int avformat_network_init(void)
作用:
对网络库进行全局初始化。
注意点: 此函数仅用于解决旧 GnuTLS 或 OpenSSL 库的线程安全问题。如果 libavformat 链接到这些库的较新版本,或者您不使用它们,则无需调用此函数。否则,您需要在使用它们的任何其他线程启动之前调用此函数。
AVFormatContext * avformat_alloc_context(void)
作用:
使用默认参数分配并初始化一个AVFormatContext对象。
返回值:
成功返回已分配且完成默认初始化的 AVFormatContext对象,否则返回NULL
注意:使用完毕后,必须使用函数avformat_free_context()函数进行释放
void avformat_free_context(AVFormatContext* s)
int avformat_open_input(AVFormatContext **ps, const char *filename,
ff_const59 AVInputFormat *fmt, AVDictionary **options);
作用:
- 如有必要,则为 AVFormatContext 分配内存。
- 尝试猜测输入文件格式,输入文件的编解码器参数
- 分配编解码器上下文、解复用上下文、I/O 上下文。
返回值:
成功返回0,否则返回负数错误码
参数含义:
- ps: 媒体相关的上下文结构信息,需要注意的是:若该函数调用失败,则其将被系统主动释放。
- filename: 媒体文件名或URL.
- fmt:将要打开的媒体格式的操作结构,因为是读,所以是AVInputFormat结构.对应ffmpeg命令行中的 -f xxx段,若为NULL,则有系统自己探测获取其格式,否则,以此参数为准而不会探测文件的实际格式.
- options:一个AVFormatContext和 demuxer-private 选项的字典。该函数返回时,将被销毁,无需配置则设置为NULL。
int avformat_find_stream_info(AVFormatContext *ic,AVDictionary **options)
作用:
读取媒体文件的数据包以获取媒体流信息。
返回值:
大于等于0表示成功,否则则表示失败
参数含义
- ic: 媒体相关的上下文结构信息
- options: 如果非 NULL,则为 ic.nb_streams 指向字典的长指针数组,其中第 i 个成员包含对应于第 i 个流的编解码器选项。 返回时,每个字典都将填充未找到的选项。
const AVCodec* avcodec_find_decoder(enum AVCodecID id)
作用:
查找 ID为id的已注册解码器
返回值:
成功返回指定的解码器,失败则返回NULL
参数含义:
- id:需要查找的解码器ID,其定义在codec_id.h文件中
int avcodec_open2(AVCodecContext *avctx,const AVCodec *codec,
AVDictionary **options)
作用:
初始化指定的编解码器
返回值:
成功返回0,失败则返回负数
参数含义:
- avctx:需要初始化的上下文信息
- codec:要为avctx初始化的编解码器。 如果传入非 NULL值,则此值必须与avctx->codec值相同
- options:一个AVCodecContext和 codec-private 选项的字典。返回时,此对象将填充未找到的选项。
struct SwsContext* sws_getContext(int srcW,int srcH,enum AVPixelFormat srcFormat,
int dstW,int dstH,enum AVPixelFormat dstFormat,
int flags,SwsFilter* srcFilter,SwsFilter* dstFilter,
const double* param)
作用:
分配并返回一个SwsContext。您需要它来使用sws_scale()执行缩放/转换操作。
返回值:
成功返回 一个SwsContext指针,失败则返回NULL
参数含义:
- srcW:源图像的宽度
- srcH:源图像的高度
- srcFormat:源图像格式
- dstW:目标图像的宽度
- dstH:目标图像的高度
- dstFormat:目标图像格式
- flags:指定用于重新缩放的算法和选项
- param:调整使用的缩放器的额外参数
int av_read_frame(AVFormatContext *s,AVPacket *pkt )
作用:
读取码流中的音频若干帧或者视频一帧
返回值:
成功返回0,失败则返回小于0的值,且pkt为NULL
参数含义:
s:输入的AVFormatContext
pkt:输出的AVPacket
#include
#define __STDC_CONSTANT_MACROS
//Windows
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
};
int main()
{
// 注册支持的所有的文件格式(容器)及其对应的CODEC,只需要调用一次
av_register_all();
// 加载socket库以及网络加密协议相关的库,为后续使用网络相关提供支持
avformat_network_init();
// 用来申请AVFormatContext类变量并初始化默认参数
AVFormatContext* pFormatCtx = avformat_alloc_context();
// RTSP地址
char filepath[] = "rtsp://admin:[email protected]:554/Streaming/Channels/201";
//打开网络流或文件流
if (avformat_open_input(&pFormatCtx, filepath, NULL, NULL) != 0) {
printf("Couldn't open input stream.\n");
return -1;
}
//读取一部分视音频数据并且获得一些相关的信息
if (avformat_find_stream_info(pFormatCtx, NULL) < 0){
printf("Couldn't find stream information.\n");
return -1;
}
int videoindex = -1;
int i = -1;
for (i = 0; i < pFormatCtx->nb_streams; i++) {
if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) //在多个数据流中找到视频流 video stream(类型为AVMEDIA_TYPE_VIDEO)
{
videoindex = i;
break;
}
}
if (videoindex == -1) {
printf("Didn't find a video stream.\n");
return -1;
}
AVCodecContext* pCodecCtx = pFormatCtx->streams[videoindex]->codec;
//查找video stream 相对应的解码器
AVCodec* pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
if (pCodec == NULL) {
printf("Codec not found.\n");
return -1;
}
//打开解码器
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
printf("Could not open codec.\n");
return -1;
}
AVFrame* pFrame = av_frame_alloc(); //为解码帧分配内存
AVFrame* pFrameYUV = av_frame_alloc();
uint8_t* out_buffer = (uint8_t*)av_malloc(avpicture_get_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height));
avpicture_fill((AVPicture*)pFrameYUV, out_buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);
//Output Info---输出一些文件(RTSP)信息
printf("---------------- File Information ---------------\n");
av_dump_format(pFormatCtx, 0, filepath, 0);
printf("-------------------------------------------------\n");
struct SwsContext* img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,
pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, 4, NULL, NULL, NULL);
AVPacket* packet = (AVPacket*)av_malloc(sizeof(AVPacket));
FILE* fpSave;
//h264保存的文件名
if ((fpSave = fopen("video.h264", "ab")) == NULL) {
return 0;
}
for (;;) {
if (av_read_frame(pFormatCtx, packet) >= 0) //从流中读取读取数据到Packet中
{
if (packet->stream_index == videoindex)
{
fwrite(packet->data, 1, packet->size, fpSave);//写数据到文件中
}
av_free_packet(packet);
}
}
av_frame_free(&pFrameYUV);
av_frame_free(&pFrame);
avcodec_close(pCodecCtx); //需要关闭avformat_open_input打开的输入流,avcodec_open2打开的CODEC
avformat_close_input(&pFormatCtx);
return 0;
}