FFmpeg视频解码器_ffmpeg解码器-CSDN博客
1.FFmpeg库简介
2.FFmpeg解码的函数
2.1 FFmpeg解码的流程图
av_read_frame():循环读取一帧一帧的数据,实际上是h264码流
2.2 FFmpeg解码函数简介
3. FFmpeg解码的数据结构
显示时间戳:该桢该几分几秒显示,基于AVStream的成员变量time_base,该流的时间基
AVPacket:装h.264数据(解码前的数据,编码数据)
AVFrame:装YUV数据(解码后的数据,解码数据)
4.补充小知识:解码后的数据为什么要经过sws_scale()函数处理?
5.课后练习
/**
* 最简单的基于FFmpeg的解码器
* Simplest FFmpeg Decoder
*
* 雷霄骅 Lei Xiaohua
* leixiaohua1020@126.com
* 中国传媒大学/数字电视技术
* Communication University of China / Digital TV Technology
* http://blog.csdn.net/leixiaohua1020
*
* 本程序实现了视频文件的解码(支持HEVC,H.264,MPEG2等)。
* 是最简单的FFmpeg视频解码方面的教程。
* 通过学习本例子可以了解FFmpeg的解码流程。
* This software is a simplest video decoder based on FFmpeg.
* Suitable for beginner of FFmpeg.
*
*/
#include <stdio.h>
#define __STDC_CONSTANT_MACROS
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libavutil/imgutils.h"
};
int main(int argc, char* argv[])
{
AVFormatContext* pFormatCtx; // 存储的所有关于视频的信息
int i, videoindex; // videoindex:视频流所在的序号值
AVCodecContext* pCodecCtx;
AVCodec* pCodec;
AVFrame* pFrame, * pFrameYUV;
uint8_t* out_buffer;
AVPacket* packet;
int y_size;
int ret, got_picture;
struct SwsContext* img_convert_ctx;
// 要解码的音视频文件路径
char filepath[] = "./test.avi";
// 视频桢的数量
int frame_cnt;
// 1.初始化,注册所有组件
//av_register_all();
avformat_network_init();
pFormatCtx = avformat_alloc_context();
// 2.打开视频文件
if (avformat_open_input(&pFormatCtx, filepath, NULL, NULL) != 0) {
printf("Couldn't open input stream.\n");
return -1;
}
// 3.获取视频码流信息
if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
printf("Couldn't find stream information.\n");
return -1;
}
// 获取流信息,进行解码
videoindex = -1;
// 寻找视频流所在的序号值
// nb_streams:记录有几个stream,一般stream[0]是视频,stream[1]是音频
for (i = 0; i < pFormatCtx->nb_streams; i++)
if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
// 枚举值AVMEDIA_TYPE_VIDEO 为0,表示视频
videoindex = i;
break;
}
if (videoindex == -1) {
printf("Didn't find a video stream.\n");
return -1;
}
// AVCodecParameters:用于保存音视频流的基本参数信息
AVCodecParameters* codecParameters = pFormatCtx->streams[videoindex]->codecpar;
pCodecCtx = avcodec_alloc_context3(nullptr);
avcodec_parameters_to_context(pCodecCtx, codecParameters);
// 4.查找解码器,eg:H.264编码器
// pCodecCtx=pFormatCtx->streams[videoindex]->codec;
pCodec = (AVCodec*)avcodec_find_decoder(pCodecCtx->codec_id);
if (pCodec == NULL) {
printf("Codec not found.\n");
return -1;
}
// 5.打开解码器
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
printf("Could not open codec.\n");
return -1;
}
// 打开文件
FILE * fp, * fp_264, * fp_yuv;
fopen_s(&fp, "info.txt", "wb+");
fopen_s(&fp_264, "output264.h264", "wb+");
fopen_s(&fp_yuv, "outputyuv.yuv", "wb+");
/*
* 此处终端输出视频信息的代码
* 取自于pFormatCtx,使用fprintf()
*/
// 写入到文件info.txt中
fprintf(fp, "时长: %d μs\n", pFormatCtx->duration); // 微秒,转换成秒除以1e6
fprintf(fp, "封装格式:%s\n", pFormatCtx->iformat->long_name);
fprintf(fp, "分辨率: %d*%d\n", pCodecCtx->width, pCodecCtx->height);
packet = av_packet_alloc();
pFrame = av_frame_alloc();
pFrameYUV = av_frame_alloc();
out_buffer = (uint8_t*)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1));
av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize, out_buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1);
// 输出视频文件信息
printf("--------------- File Information ----------------\n");
/*
* av_dump_format()函数:
Print detailed information about the input or output format, such as
* duration, bitrate, streams, container, programs, metadata, side data,
* codec and time base.
*/
av_dump_format(pFormatCtx, 0, filepath, 0);
printf("-------------------------------------------------\n");
img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);
// 视频桢数量
frame_cnt = 0;
// 输出H264码流
// 6. av_read_frame():从输入文件读取一帧压缩数据。
while (av_read_frame(pFormatCtx, packet) >= 0) {
if (packet->stream_index == videoindex) {
// videoindex:判断是视频
/*
* 此处输出H264码流的代码
* 取自于packet,使用fwrite(),把内存中的数据写入文件output264.h264
*/
fwrite(packet->data, 1, packet->size, fp_264);
if (avcodec_send_packet(pCodecCtx, packet) < 0) {
printf("avcodec_send_packet failed!.\n");
continue;
}
while (1)
{
ret = avcodec_receive_frame(pCodecCtx, pFrame);
if (ret != 0)break;
// sws_scale函数处理生成的图,生成数据存储在pFrameYUV中
sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);
// 输出处理的桢的序号
printf("Decoded frame index: %d\n", frame_cnt);
/*
* 此处添加输出YUV的代码
* 取自于pFrameYUV,使用fwrite()
*
*
* data[0]:Y数据,屏幕所有亮的点,黑白数据
* data[1]:U数据,数据为Y的四分之一
* “U”和“V”表示的则是色度,
作用是描述影像色彩及饱和度,用于指定像素的颜色。
* data[2]:V数据,数据为Y的四分之一
*/
fwrite(pFrameYUV->data[0], 1, pCodecCtx->width * pCodecCtx->height, fp_yuv);
fwrite(pFrameYUV->data[1], 1, pCodecCtx->width * pCodecCtx->height / 4, fp_yuv);
fwrite(pFrameYUV->data[2], 1, pCodecCtx->width * pCodecCtx->height / 4, fp_yuv);
frame_cnt++;
}
}
av_packet_unref(packet);
}
sws_freeContext(img_convert_ctx);
fclose(fp);
fclose(fp_264);
fclose(fp_yuv);
av_frame_free(&pFrameYUV);
av_frame_free(&pFrame);
avcodec_close(pCodecCtx);
avformat_close_input(&pFormatCtx);
return 0;
}