ffmpeg android视频解码

解码流程:

  1. 获取文件信息,数据存储在AVFormatContext里面
  2. 根据AVFormatContext获取对应的AVCodecContext
  3. 解码原始数据AVPacket,解码为自己需要的数据AVFrame
  4. 释放相关资源


(图片来源于网络)

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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
#include "lang.h"
#include <string>
//封装格式
//解码
#include "log.h"

extern "C" {
#include <libavutil/imgutils.h>
#include <libavutil/samplefmt.h>
#include <libavutil/timestamp.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>

};


static void video_decode_example(const char *outfilename, const char *filename) {
//1.注册
av_register_all();

AVFormatContext *pFormatCtx = NULL;
//2. 打开文件
if (avformat_open_input(&pFormatCtx, filename, NULL, NULL) != 0) {
LOGE ("打开文件失败");
return;
}
//3. 获取流信息,数据封装在AVFormatContext里面
if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
LOGE ("获取流信息失败");
return;
}
//只输出输入文件的格式信息
av_dump_format(pFormatCtx, 0, filename, 0);
int video_index = -1;
//4. 从流中遍历获取video的index
for (int i = 0; i < pFormatCtx->nb_streams; i++) {
if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
video_index = i;
LOGE ("video_index = %d", video_index);
break;
}
}
if (video_index == -1) {
LOGE ("遍历获取video_index失败");
return;
}
AVCodecContext *pCodecCtxOrg = NULL;
AVCodecContext *pCodecCtx = NULL;

AVCodec *pCodec = NULL;
//5. 解码器获取
//5.1 根据video_index获取解码器上下文AVCodecContext
pCodecCtxOrg = pFormatCtx->streams[video_index]->codec; // codec context
//5.1 根据AVCodecContext获取解码器
pCodec = avcodec_find_decoder(pCodecCtxOrg->codec_id);

if (!pCodec) {
LOGE ("解码器获取失败");
return;
}

//6.获取一个AVCodecContext实例,并将第五步获取的AVCodecContext数据copy过来,解码的时候需要用这个
pCodecCtx = avcodec_alloc_context3(pCodec);
if (avcodec_copy_context(pCodecCtx, pCodecCtxOrg) != 0) {
LOGE ("解码器上下文数据copy失败");
return;
}
//7. 打开解码器
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
LOGE ("打开解码器失败");
return;
}
//原始数据帧
AVFrame *pFrame = NULL;
//yuv数据帧
AVFrame *pFrameYUV = NULL;
//内存开辟 不要忘记free
pFrame = av_frame_alloc();
pFrameYUV = av_frame_alloc();
int numBytes = 0;
uint8_t *buffer = NULL;
//根据需要解码的类型,获取需要的buffer,不要忘记free
numBytes = av_image_get_buffer_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1);
buffer = (uint8_t *) av_malloc(numBytes * sizeof(uint8_t));

//根据指定的图像参数和提供的数组设置数据指针和行数 ,数据填充到对应的pFrameYUV里面
av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize, buffer, AV_PIX_FMT_YUV420P,
pCodecCtx->width,
pCodecCtx->height, 1);

//获取SwsContext
struct SwsContext *sws_ctx = NULL;
sws_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,
pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC,
NULL, NULL, NULL);

FILE *pFile = fopen(outfilename, "wb+");
int ret;
AVPacket packet;
int frameFinished = 0;
//8. 根据AVFormatContext 读取帧数据,读取的编码数据存储到AVPacket里面
while (av_read_frame(pFormatCtx, &packet) >= 0) {
if (packet.stream_index == video_index) {
//9. 将读取到的AVPacket,转换为AVFrame
ret = avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
if (ret < 0) {
LOGE("解码失败");
return;
}
if (frameFinished) {
//10. 将原始的AVFrame数据转换为自己需要的YUV AVFrame数据
sws_scale(sws_ctx, (uint8_t const *const *) pFrame->data, pFrame->linesize, 0,
pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);
//11. 根据YUV AVFrame数据保存文件
if (pFile == NULL)
return;
int y_size = pCodecCtx->width * pCodecCtx->height;

//yuv420 存储为4:1:1
fwrite(pFrame->data[0], 1, static_cast<size_t>(y_size), pFile); //y
fwrite(pFrame->data[1], 1, static_cast<size_t>(y_size / 4), pFile);//u
fwrite(pFrame->data[2], 1, static_cast<size_t>(y_size / 4), pFile);//v
}
}
av_packet_unref(&packet);
}

//flush decoder
//FIX: Flush Frames remained in Codec
//12. 刷新解码器
while (1) {
ret = avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
if (ret < 0)
break;
if (!frameFinished)
break;
sws_scale(sws_ctx, (const unsigned char *const *) pFrame->data, pFrame->linesize, 0,
pCodecCtx->height,
pFrameYUV->data, pFrameYUV->linesize);

int y_size = pCodecCtx->width * pCodecCtx->height;
fwrite(pFrameYUV->data[0], 1, static_cast<size_t>(y_size), pFile); //Y
fwrite(pFrameYUV->data[1], 1, static_cast<size_t>(y_size / 4), pFile); //U
fwrite(pFrameYUV->data[2], 1, static_cast<size_t>(y_size / 4), pFile); //V
LOGE("Flush Decoder: Succeed to decode 1 frame!\n");
}
//release resource
sws_freeContext(sws_ctx);
fclose(pFile);
av_free(buffer);
av_frame_free(&pFrameYUV);
av_frame_free(&pFrame);
avcodec_close(pCodecCtx);
avcodec_close(pCodecCtxOrg);
avformat_close_input(&pFormatCtx);

}


extern "C"
JNIEXPORT jint JNICALL
Java_zzw_com_ffmpegdemo_VideoUtils_decode(JNIEnv *env, jclass type, jstring input_,
jstring output_) {
const char *input_file_name = env->GetStringUTFChars(input_, 0);
const char *output_file_name = env->GetStringUTFChars(output_, 0);

video_decode_example(output_file_name, input_file_name);

env->ReleaseStringUTFChars(input_, input_file_name);
env->ReleaseStringUTFChars(output_, output_file_name);
return 0;

}

总结:

要解码,我们需要获取解码器AVCodec,解码器我们需要通过codec_id获取,codec_id我们需要通过AVStream获取,AVStream我们需要通过AVCodecContext获取,AVCodecContext我们要根据AVFormatContext获取,解码的时候我们要通过AVFormatContext读取,解码数据存储在AVFrame里面,编码数据存储在AVPacket里面。

参考:
https://blog.csdn.net/leixiaohua1020/article/details/46889389
https://blog.csdn.net/u011913612/article/details/53419986

-------------The End-------------