[TOC]

C下FFmpeg开发环境的搭建

1、新建VC项目

新建->项目->控制台应用->创建

2、拷贝FFmpeg开发文件

1
2
3
1、头文件(*.h)拷贝至项目文件夹的include子文件夹下
2、导入库文件(*.lib)拷贝至项目文件夹的lib子文件夹下
3、动态库文件(*.dll)拷贝至项目文件夹下

PS:如果直接使用官网上下载的FFmpeg开发文件。则可能还需要将MinGW安装目录中的inttypes.h,stdint.h,_mingw.h三个文件拷贝至项目文件夹的include子文件夹下。

3、配置开发文件

step1: 打开属性面板
解决方案资源管理器->右键单击项目->属性

step2: 头文件配置
配置属性->C/C++->常规->附加包含目录,输入“include”(刚才拷贝头文件的目录)

step3: 导入库配置
配置属性->链接器->常规->附加库目录,输入“lib” (刚才拷贝库文件的目录)

配置属性->链接器->输入->附加依赖项,输入“avcodec.lib; avformat.lib; avutil.lib; avdevice.lib; avfilter.lib; postproc.lib; swresample.lib; swscale.lib”(导入库的文件名)

动态库不用配置

4、测试

step1: 创建源代码文件
在工程中创建一个包含main()函数的C/C++文件(如果已经有了可以跳过这一步)。
step2: 包含头文件

  • 如果是C语言中使用FFmpeg,则直接使用下面代码
1
2
3
4
5
6
7
8
#include<stdio.h>
#include "libavcodec/avcodec.h"
int main()
{
//▫main()中调用一个FFmpeg的接口函数, 打印出了FFmpeg的配置信息
printf("%s", avcodec_configuration());
return 0;
}
  • 如果是C++语言中使用FFmpeg,则使用下面代码
1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>
#define __STDC_CONSTANT_MACROS
extern "C"
{
#include "libavcodec/avcodec.h "
}
int main()
{
//▫main()中调用一个FFmpeg的接口函数, 打印出了FFmpeg的配置信息
printf("%s", avcodec_configuration());
return 0;
}

如果运行无误,则代表FFmpeg已经配置完成。

FFmpeg库简介

FFmpeg解码的函数

FFmpeg解码的流程图如下所示:

FFmpeg解码函数简介

1
2
3
4
5
6
7
8
9
av_register_all():注册所有组件。
avformat_open_input():打开输入视频文件。
avformat_find_stream_info():获取视频文件信息。
avcodec_find_decoder():查找解码器。
avcodec_open2():打开解码器。
av_read_frame():从输入文件读取一帧压缩数据。
avcodec_decode_video2():解码一帧压缩数据。
avcodec_close():关闭解码器。
avformat_close_input():关闭输入视频文件。

FFmpeg解码的数据结构

FFmpeg解码的数据结构如下所示:

FFmpeg数据结构简介

1、AVFormatContext

封装格式上下文结构体,也是统领全局的结构体,保存了视频文件封装格式相关信息。

2、AVInputFormat

每种封装格式(例如FLV, MKV, MP4, AVI)对应一个该结构体。

3、AVStream

视频文件中每个视频(音频)流对应一个该结构体。

4、AVCodecContext

编码器上下文结构体,保存了视频(音频)编解码相关信息。

5、AVCodec

每种视频(音频)编解码器(例如H.264解码器)对应一个该结构体。

6、AVPacket

存储一帧压缩编码数据。

7、AVFrame

存储一帧解码后像素(采样)数据。

FFmpeg数据结构分析

AVFormatContext:

iformat:输入视频的AVInputFormat
nb_streams :输入视频的AVStream 个数
streams :输入视频的AVStream []数组
duration :输入视频的时长(以微秒为单位)
bit_rate:输入视频的码率

  • AVInputFormat:

    name:封装格式名称
    long_name:封装格式的长名称
    extensions:封装格式的扩展名
    id:封装格式ID
    一些封装格式处理的接口函数

  • AVStream:

    id:序号
    codec:该流对应的AVCodecContext
    time_base:该流的时基
    r_frame_rate:该流的帧率

    • AVCodecContext:

      codec:编解码器的AVCodec
      width, height:图像的宽高(只针对视频)
      pix_fmt:像素格式(只针对视频)
      sample_rate:采样率(只针对音频)
      channels:声道数(只针对音频)
      sample_fmt:采样格式(只针对音频)

      • AVCodec:

        codec:编解码器的AVCodec
        width, height:图像的宽高(只针对视频)
        pix_fmt:像素格式(只针对视频)
        sample_rate:采样率(只针对音频)
        channels:声道数(只针对音频)
        sample_fmt:采样格式(只针对音频)

AVPacket:

pts:显示时间戳
dts :解码时间戳
data :压缩编码数据
size :压缩编码数据大小
stream_index :所属的AVStream

AVFrame:

data:解码后的图像像素数据(音频采样数据)。
linesize:对视频来说是图像中一行像素的大小;对音频来说是整个音频帧的大小。
width, height:图像的宽高(只针对视频)。
key_frame:是否为关键帧(只针对视频) 。
pict_type:帧类型(只针对视频) 。例如I,P,B。

最简单的基于FFmpeg的解码器

测试文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
/**
* 最简单的基于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>
#include<math.h>
#define __STDC_CONSTANT_MACROS

extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
};


int main(int argc, char* argv[])
{
AVFormatContext *pFormatCtx;
int i, 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[]="中国合伙人.flv";

int frame_cnt;

av_register_all();
avformat_network_init();
pFormatCtx = avformat_alloc_context();

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;
}
videoindex=-1;
for(i=0; i<pFormatCtx->nb_streams; i++)
if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO){
videoindex=i;
break;
}
if(videoindex==-1){
printf("Didn't find a video stream.\n");
return -1;
}

pCodecCtx=pFormatCtx->streams[videoindex]->codec;
pCodec=avcodec_find_decoder(pCodecCtx->codec_id);//1.查找解码器
if(pCodec==NULL){
printf("Codec not found.\n");
return -1;
}
if(avcodec_open2(pCodecCtx, pCodec,NULL)<0){//2.打开解码器
printf("Could not open codec.\n");
return -1;
}
/*
* 在此处添加输出视频信息的代码
* 取自于pFormatCtx,使用fprintf()
*/

pFrame=av_frame_alloc();
pFrameYUV=av_frame_alloc();
out_buffer=(uint8_t *)av_malloc(avpicture_get_size(PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height));
avpicture_fill((AVPicture *)pFrameYUV, out_buffer, PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);
packet=(AVPacket *)av_malloc(sizeof(AVPacket));
//Output Info-----------------------------
printf("--------------- File Information ----------------\n");
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, PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);

frame_cnt=0;
while(av_read_frame(pFormatCtx, packet)>=0){//3.从输入文件读取一帧压缩数据。
if(packet->stream_index==videoindex){
/*
* 在此处添加输出H264码流的代码
* 取自于packet,使用fwrite()
*/

ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet); //4.解码一帧压缩数据
if(ret < 0){
printf("Decode Error.\n");
return -1;
}
if(got_picture){
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()
*/

frame_cnt++;

}
}
av_free_packet(packet);
}
sws_freeContext(img_convert_ctx);

av_frame_free(&pFrameYUV);
av_frame_free(&pFrame);
avcodec_close(pCodecCtx); //5. 关闭解码器。
avformat_close_input(&pFormatCtx); //6. 关闭输入视频文件。

return 0;
}

练习

需求:

修改源代码。对于测试文件,输出以下几种文件
1、“output.h264”文件
解码前的H.264码流数据(只对MPEG-TS,AVI格式作要求)
2、“output.yuv”文件
解码后的YUV420P像素数据
3、“output.txt”文件

  • 封装格式参数:封装格式、比特率、时长
  • 视频编码参数:编码方式、宽高
  • 每一个解码前视频帧参数:帧大小
  • 每一个解码后视频帧参数:帧类型

完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
/**
* 最简单的基于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>
#include<math.h>
#define __STDC_CONSTANT_MACROS

extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
};


int main(int argc, char* argv[])
{
AVFormatContext *pFormatCtx;
int i, 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[]="中国合伙人.flv";

int frame_cnt;

av_register_all();
avformat_network_init();
pFormatCtx = avformat_alloc_context();

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;
}
videoindex=-1;
for(i=0; i<pFormatCtx->nb_streams; i++)
if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO){
videoindex=i;
break;
}
if(videoindex==-1){
printf("Didn't find a video stream.\n");
return -1;
}

pCodecCtx=pFormatCtx->streams[videoindex]->codec;
pCodec=avcodec_find_decoder(pCodecCtx->codec_id);//1.查找解码器
if(pCodec==NULL){
printf("Codec not found.\n");
return -1;
}
if(avcodec_open2(pCodecCtx, pCodec,NULL)<0){//2.打开解码器
printf("Could not open codec.\n");
return -1;
}
/*
* 在此处添加输出视频信息的代码
* 取自于pFormatCtx,使用fprintf()
*/

//----------------将信息输出到文件中-----------------------
FILE * file = fopen("out.txt","wb+");
//封装格式:
fprintf(file,"封装格式:%s\n", pFormatCtx->iformat->name);
fprintf(file, "时长:%f 秒 \n", pFormatCtx->duration / pow(10, 6));
//视频编码参数:
fprintf(file,"宽x高:%d x %d\n", pFormatCtx->streams[videoindex]->codec->width, pFormatCtx->streams[videoindex]->codec->height);
fprintf(file, "解码前帧大小:%d\n", pFormatCtx->streams[videoindex]->r_frame_rate);
fclose(file);
//-----------------------------------------------------

pFrame=av_frame_alloc();
pFrameYUV=av_frame_alloc();
out_buffer=(uint8_t *)av_malloc(avpicture_get_size(PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height));
avpicture_fill((AVPicture *)pFrameYUV, out_buffer, PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);
packet=(AVPacket *)av_malloc(sizeof(AVPacket));
//Output Info-----------------------------
printf("--------------- File Information ----------------\n");
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, PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);

frame_cnt=0;
FILE* file_h264 = fopen("out.h264", "wb+");
FILE* file_yuv = fopen("output.yuv", "wb+");
while(av_read_frame(pFormatCtx, packet)>=0){//3.从输入文件读取一帧压缩数据。
if(packet->stream_index==videoindex){
/*
* 在此处添加输出H264码流的代码
* 取自于packet,使用fwrite()
*/

fwrite(packet->data,1,packet->size,file_h264);

ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet); //4.解码一帧压缩数据
if(ret < 0){
printf("Decode Error.\n");
return -1;
}
if(got_picture){
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()
*/
/*
注意:1、这里YUV的数据有三部分组成,即Y,U,V数据
2、YUV420数据,其中U和V数据只占了数据的1/4大小
*/
fwrite( pFrameYUV->data[0], 1, pCodecCtx->height* pCodecCtx->width,file_yuv);
fwrite(pFrameYUV->data[1], 1, pCodecCtx->height * pCodecCtx->width/4, file_yuv);
fwrite(pFrameYUV->data[2], 1, pCodecCtx->height * pCodecCtx->width/4, file_yuv);

frame_cnt++;

}
}
av_free_packet(packet);
}
fclose(file_h264);
fclose(file_yuv);
sws_freeContext(img_convert_ctx);

av_frame_free(&pFrameYUV);
av_frame_free(&pFrame);
avcodec_close(pCodecCtx); //5. 关闭解码器。
avformat_close_input(&pFormatCtx); //6. 关闭输入视频文件。
return 0;
}

效果演示:

1、通过数据查看工具:YUV Player查看输出的YUV的Y数据,即只有亮度,没有色度信息,因此视频成黑白色。

2、通过数据查看工具:YUV Player查看输出的YUV数据,包括亮度和色度。

3、输出“output.txt”文件