FFmpeg入门教程10.04:解码视频流过程

系列索引:FFmpeg入门系列索引

上一篇:FFmpeg入门教程10.03:输出视频信息

FFmpeg使用较多的就是解码视频了,我们先进行正常的解码流程。

解码流程图为:

flowchart TB
    F --Yes--> I
    K --下一帧--> F
    I --No--> F
    subgraph init
        direction TB
        A(开始) --> B[打开文件]
        B --> C[查找流信息]
        C --> D[查找对应解码器]
        D --> E[打开解码器]
        E --> F{读取帧}
        F --No--> G(结束)
    end
    subgraph decode
        direction TB
        I{是视频帧?} --Yes--> J[发送帧给解码器]
        J --> K[从解码器获取结果]
    end
flowchart TB
    F --Yes--> I
    K --Next Frame--> F
    I --No--> F
    subgraph init
        direction TB
        A(Start) --> B[avformat_open_input]
        B --> C[av_find_stream_info]
        C --> D[avcodec_find_decoder]
        D --> E[avcodec_open]
        E --> F{av_read_frame}
        F --No--> G(End)
    end
    subgraph decode
        direction TB
        I{Video Packet?} --Yes--> J[avcodec_send_packet]
        J --> K[avcodec_receive_frame]
    end

测试代码如下:

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
#include <stdio.h>
#include <stdlib.h>
#include "libavcodec/avcodec.h"
#include "libavfilter/avfilter.h"
#include "libavformat/avformat.h"
#include "libavutil/avutil.h"
#include "libavutil/ffversion.h"
#include "libswresample/swresample.h"
#include "libswscale/swscale.h"
#include "libpostproc/postprocess.h"

int main() {
char filePath[] = "beat.mp4";//文件地址
int videoStreamIndex = -1;//视频流所在流序列中的索引
int ret=0;//默认返回值

//需要的变量名并初始化
AVFormatContext *fmtCtx=NULL;
AVPacket *pkt =NULL;
AVCodecContext *codecCtx=NULL;
AVCodecParameters *avCodecPara=NULL;
const AVCodec *codec=NULL;

do{
//=========================== 创建AVFormatContext结构体 ===============================//
//分配一个AVFormatContext,FFMPEG所有的操作都要通过这个AVFormatContext来进行
fmtCtx = avformat_alloc_context();
//==================================== 打开文件 ======================================//
if ((ret=avformat_open_input(&fmtCtx, filePath, NULL, NULL)) != 0) {
printf("cannot open video file\n");
break;
}

//=================================== 获取视频流信息 ===================================//
if ((ret=avformat_find_stream_info(fmtCtx, NULL)) < 0) {
printf("cannot retrive video info\n");
break;
}

//循环查找视频中包含的流信息,直到找到视频类型的流
//便将其记录下来 保存到videoStreamIndex变量中
for (unsigned int i = 0; i < fmtCtx->nb_streams; i++) {
if (fmtCtx->streams[ i ]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
videoStreamIndex = i;
break;//找到视频流就退出
}
}

//如果videoStream为-1 说明没有找到视频流
if (videoStreamIndex == -1) {
printf("cannot find video stream\n");
break;
}

//打印输入和输出信息:长度 比特率 流格式等
av_dump_format(fmtCtx, 0, filePath, 0);

//================================= 查找解码器 ===================================//
avCodecPara = fmtCtx->streams[ videoStreamIndex ]->codecpar;
codec = avcodec_find_decoder(avCodecPara->codec_id);
if (codec == NULL) {
printf("cannot find decoder\n");
break;
}
//根据解码器参数来创建解码器内容
codecCtx = avcodec_alloc_context3(codec);
avcodec_parameters_to_context(codecCtx, avCodecPara);
if (codecCtx == NULL) {
printf("Cannot alloc context.");
break;
}

//================================ 打开解码器 ===================================//
if ((ret=avcodec_open2(codecCtx, codec, NULL)) < 0) { // 具体采用什么解码器ffmpeg经过封装 我们无须知道
printf("cannot open decoder\n");
break;
}

//=========================== 分配AVPacket结构体 ===============================//
int i = 0;//用于帧计数
pkt = av_packet_alloc(); //分配一个packet
av_new_packet(pkt, codecCtx->width * codecCtx->height); //调整packet的数据

//=========================== 读取视频信息 ===============================//
while (av_read_frame(fmtCtx, pkt) >= 0) { //读取的是一帧视频 数据存入一个AVPacket的结构中
if(pkt->stream_index==videoStreamIndex){
i++;//只计算视频帧
}
av_packet_unref(pkt);//重置pkt的内容
}
printf("There are %d frames int total.\n", i);
}while(0);
//===========================释放所有指针===============================//
av_packet_free(&pkt);
avcodec_close(codecCtx);
avformat_close_input(&fmtCtx);
avformat_free_context(fmtCtx);

return ret;
}

编译执行,输出为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from '/home/jackey/Videos/1.mp4':
Metadata:
major_brand : isom
minor_version : 1
compatible_brands: isomavc1
creation_time : 2015-06-06T14:14:34.000000Z
Duration: 00:06:32.89, start: 0.000000, bitrate: 1923 kb/s
Stream #0:0(und): Video: h264 (High 10) (avc1 / 0x31637661), yuv420p10le, 1280x720, 1787 kb/s, 23.98 fps, 23.98 tbr, 24k tbn, 59.94 tbc (default)
Metadata:
creation_time : 2015-06-06T14:14:34.000000Z
Stream #0:1(jpn): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 165 kb/s (default)
Metadata:
creation_time : 2015-06-06T14:14:29.000000Z
There are 9420 frames int total.%

根据输出,在视频流里面统计每一帧,共计9420帧。

视频共计6分32秒,帧率为24帧/秒,那么视频的视频帧数为:(6 * 60 + 32.89 ) * 23.98 = 9421.5022 。考虑到执行误差,两者非常接近,没有太大的错误。

代码开发要模块化,我们将部分流程合并,按照谷歌编码规范,一个函数最多40行,保持简洁。

但是开发才刚开始,并没有太多代码。

既然流程没有错,那么我们进行解码视频数据看看。

GitHub项目地址(源代码):ffmpeg_beginner中的10.04.video_decode_flow

下一篇我们将解码视频前5帧并保存为图片。

下一篇:FFmpeg入门教程10.05:保存视频帧


FFmpeg入门教程10.04:解码视频流过程
https://blog.jackeylea.com/ffmpeg/ffmpeg-flow-of-decode-video/
作者
JackeyLea
发布于
2020年7月12日
许可协议