Fork me on GitHub

基于FFmpeg4.0+SDL2.0实现简单的纯视频播放器

本文使用FFmpeg4.0.2+SDL2.0实现一个简单的纯视频(无音频)播放器,代码尽量使用新的函数,注释完整。该播放器支持空格键暂停/播放和调整窗口大小。注:SDL.h可以不放在extern “C”中,因为SDL本身进行了C++程序的判断处理。另外av_register_all()和avformat_network_init()都已经不再是必须调用的函数了,前者弃用,后者可以自动调用。更加完整的播放器工程参见GitHub上本人对ffplay的简单重构:ffplay-refactor

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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
#include <iostream>
#include <SDL.h>

using std::cout;
using std::endl;

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

//注册两个自定义事件分别用于控制帧率和退出播放
Uint32 REFRESH_EVENT = SDL_RegisterEvents(1);
Uint32 QUIT_EVENT = SDL_RegisterEvents(1);

//控制播放的结束和暂停
int thread_exit = 0;
int thread_pause = 0;

//视频实际的帧率
double FPS;

//启动线程的函数的返回类型和参数类型是规定的
int refresh_video(void *udata)
{
thread_exit = 0;

//没有退出时不断刷新画面
while (!thread_exit)
{
//没有按下暂停时,每隔若干时间插入一个刷新事件以切换到下一帧;按下暂停则只有无限延时
if (!thread_pause)
{
SDL_Event ref_event;
ref_event.type = REFRESH_EVENT;
SDL_PushEvent(&ref_event);
}
SDL_Delay(1000 / round(FPS));
}

thread_exit = 0;
thread_pause = 0;

//插入退出事件
SDL_Event quit_event;
quit_event.type = QUIT_EVENT;
SDL_PushEvent(&quit_event);

return 0;
}

int main(int argc, char *argv[])
{
const char *src_file = "test.mp4";
int ret = 0; //各种函数返回的标志

//读取媒体文件的文件头并将文件格式相关的信息存储在AVFormatContext中
AVFormatContext *fmt_ctx = NULL;
ret = avformat_open_input(&fmt_ctx, src_file, NULL, NULL);
if (ret < 0)
{
cout << "Could not open file: " << src_file << endl;
return -1;
}

//将视音频流的信息读取到AVFormatContext中
ret = avformat_find_stream_info(fmt_ctx, NULL);
if (ret < 0)
{
cout << "Could not find stream information" << endl;
return -1;
}

//输出文件信息用于调试
cout << "----------------------File Info---------------------" << endl;
av_dump_format(fmt_ctx, 0, src_file, 0);
cout << "----------------------------------------------------" << endl;

//根据类型找到视频流的索引,同时获得相应的解码器
AVCodec *decoder;
int video_stream_idx = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &decoder, 0);
if (video_stream_idx < 0)
{
cout << "Could not find video stream" << endl;
return -1;
}
if (!decoder)
{
cout << "Could not find decoder" << endl;
return -1;
}

//根据索引拿到视频流
AVStream *video_stream = fmt_ctx->streams[video_stream_idx];

//计算帧率
FPS = video_stream->avg_frame_rate.num / video_stream->avg_frame_rate.den;

//根据解码器创建解码器上下文
AVCodecContext *dec_ctx = avcodec_alloc_context3(decoder);
if (!dec_ctx)
{
cout << "Could not allocate the " << av_get_media_type_string(AVMEDIA_TYPE_VIDEO)
<< "codec context" << endl;
return -1;
}

//把视频流里的参数传到视频解码器上下文中:
ret = avcodec_parameters_to_context(dec_ctx, video_stream->codecpar);
if (ret < 0)
{
cout << "Failed to copy " << av_get_media_type_string(AVMEDIA_TYPE_VIDEO)
<< " codec parameters to decoder context";
return -1;
}

//打开解码器上下文准备进行解码操作
ret = avcodec_open2(dec_ctx, decoder, NULL);
if (ret < 0)
{
cout << "Could not open codec" << endl;
return -1;
}

//建立上下文,主要是为了后面将源视频转换为YUV420P类型,如有缩放操作也可在此指定
struct SwsContext *sws_ctx = sws_getContext(dec_ctx->width, dec_ctx->height, dec_ctx->pix_fmt,
dec_ctx->width, dec_ctx->height, AV_PIX_FMT_YUV420P, SWS_BILINEAR, NULL, NULL, NULL);

//申请一个保存YUV的frame结构
AVFrame *frame_YUV = av_frame_alloc();
ret = av_image_alloc(frame_YUV->data, frame_YUV->linesize, dec_ctx->width, dec_ctx->height, AV_PIX_FMT_YUV420P, 1);
if (ret < 0)
{
cout << "Failed to allocate YUV frame" << endl;
return -1;
}

//创建packet用于获取码流数据
AVPacket *pkt = av_packet_alloc();
if (!pkt)
{
cout << "Failed to allocate packet" << endl;
return -1;
}

//创建frame用于获取解码的数据
AVFrame *frame = av_frame_alloc();
if (!frame)
{
cout << "Failed to allocate video frame" << endl;
return -1;
}

//初始化SDL
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER) != 0)
{
SDL_Log("Unable to initialize SDL: %s\n", SDL_GetError());
return -1;
}

int win_width = dec_ctx->width;
int win_height = dec_ctx->height;
//创建与视频大小相匹配的窗口
SDL_Window *win = SDL_CreateWindow("SimplePlayer", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
win_width, win_height, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
if (!win)
{
SDL_Log("Unable to create window: %s\n", SDL_GetError());
return -1;
}

//根据窗口创建渲染器
SDL_Renderer *renderer = SDL_CreateRenderer(win, -1, SDL_RENDERER_ACCELERATED);
if (!renderer)
{
SDL_Log("Unable to create renderer: %s\n", SDL_GetError());
return -1;
}

//基于渲染器创建一个IYUV像素格式且变化频繁的纹理
SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING,
dec_ctx->width, dec_ctx->height);
if (!texture)
{
SDL_Log("Unable to create texture: %s\n", SDL_GetError());
return -1;
}

//启动另一个线程
SDL_Thread *refresh_thread = SDL_CreateThread(refresh_video, NULL, NULL);

SDL_Event event;
SDL_Rect rect;
//循环读取数据包解码为帧,同时包含其他操作
while (1)
{
//若事件队列非空,则从取出一个事件
SDL_WaitEvent(&event);
if (event.type == REFRESH_EVENT)
{
//若正确读取一个数据包
if (av_read_frame(fmt_ctx, pkt) >= 0)
{
//可能拿到音频流、字幕流的数据,因此要判断
if (pkt->stream_index != video_stream_idx)
{
//该packet出现问题时需要释放内存才能继续下一个packet的读取
av_packet_unref(pkt);
continue;
}

//发送数据包
ret = avcodec_send_packet(dec_ctx, pkt);
if (ret < 0)
{
cout << "Failed to send video stream packet" << endl;
av_packet_unref(pkt);
continue;
}

//接收解码帧
ret = avcodec_receive_frame(dec_ctx, frame);
if (ret < 0)
{
cout << "Failed to receive frame " << dec_ctx->frame_number << endl;
continue;
}
//cout << "Successfully decoding the frame " << dec_ctx->frame_number << endl;

//解码帧转换为指定格式的YUV
sws_scale(sws_ctx, frame->data, frame->linesize, 0, dec_ctx->height,
frame_YUV->data, frame_YUV->linesize);

//用得到的YUV更新纹理
SDL_UpdateTexture(texture, NULL, frame_YUV->data[0], frame_YUV->linesize[0]);

//清空渲染目标
SDL_RenderClear(renderer);

//该矩形区域用于调整显示窗口的大小
rect.x = 0;
rect.y = 0;
rect.w = win_width;
rect.h = win_height;

//复制纹理到渲染器
SDL_RenderCopy(renderer, texture, NULL, &rect);

//渲染输出画面
SDL_RenderPresent(renderer);
}
else
{
thread_exit = 1; //读取错误则准备退出
}
}
else if (event.type == SDL_KEYDOWN)
{
//空格键暂停/重新播放
if (event.key.keysym.sym == SDLK_SPACE)
{
thread_pause = !thread_pause;
}
}
else if (event.type == SDL_WINDOWEVENT)
{
SDL_GetWindowSize(win, &win_width, &win_height); //调整窗口大小
}
else if (event.type == SDL_QUIT)
{
thread_exit = 1; //设置标志,准备退出
}
else if (event.type == QUIT_EVENT)
{
break; //退出循环,真正退出
}

}


//释放内存
av_frame_free(&frame);
av_frame_free(&frame_YUV);
av_packet_free(&pkt);
sws_freeContext(sws_ctx);
avcodec_free_context(&dec_ctx);
avformat_close_input(&fmt_ctx);

//退出SDL
SDL_Quit();

return 0;
}