结合ffmpeg用SDL播放YUV实现简易播放器

通过解码之后得到的yuv视频数据我们直接可以进行播放,本篇使用SDL来实现视频播放。

SDL环境配置:https://blog.csdn.net/furzoom/article/details/53992124

SDL视频播放基础教程: https://blog.csdn.net/xuyankuanrong/article/details/77574152

有了上一篇文章的解码的基础,代码看着就很轻松了。

代码上写的很详细了,具体看代码

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


#include "stdafx.h"
#include <iostream>

// LNK2019 无法解析的外部符号 SDL_main,该符号在函数 main_utf8 中被引用
#define SDL_MAIN_HANDLED

//Refresh Event
#define SFM_REFRESH_EVENT (SDL_USEREVENT + 1)
#define SFM_BREAK_EVENT (SDL_USEREVENT + 2)

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



void log_s(const char * msg, int d = -1123) {
if (d == -1123) {
printf_s("%s\n", msg);
}
else {
printf_s("%s %d \n", msg, d);
}
}


int thread_exit = 0;
int thread_pause = 0;

int sfp_refresh_thread(void *opaque) {
while (!thread_exit) {
if (!thread_pause) {
SDL_Event event;
event.type = SFM_REFRESH_EVENT;
SDL_PushEvent(&event);
}
SDL_Delay(10);
}
thread_exit = 0;
thread_pause = 0;
//Break
SDL_Event event;
event.type = SFM_BREAK_EVENT;
SDL_PushEvent(&event);

return 0;
}

int play(const char* filePath) {
AVFormatContext * pFmtCtx = NULL;
AVCodecContext *pCodecCtx = NULL;
AVFrame *pFrame = NULL;
AVFrame *pFrameYUV = NULL;
uint8_t *outBuffer = NULL;
AVPacket *pPacket = NULL;
SwsContext *pSwsCtx = NULL;

//SDL
int screen_w, screen_h;
SDL_Window *screen=NULL;
SDL_Renderer* sdlRenderer = NULL;
SDL_Texture* sdlTexture = NULL;
SDL_Rect sdlRect;
SDL_Thread *video_tid = NULL;
SDL_Event event;


//1. 初始化
av_register_all();
avformat_network_init();
//2. AVFormatContext获取
pFmtCtx = avformat_alloc_context();
//3. 打开文件
if (avformat_open_input(&pFmtCtx, filePath, NULL, NULL) != 0) {
log_s("Couldn't open input stream.\n");
return -1;
}
//4. 获取文件信息
if (avformat_find_stream_info(pFmtCtx, NULL)<0) {
log_s("Couldn't find stream information.");
return -1;
}
//5. 获取视频的index
int i = 0, videoIndex = -1;
for (; i < pFmtCtx->nb_streams; i++) {
if (pFmtCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
videoIndex = i;
break;
}
}

if (videoIndex == -1) {
log_s("Didn't find a video stream.");
return -1;
}
//6. 获取解码器并打开
pCodecCtx = avcodec_alloc_context3(NULL);
if (avcodec_parameters_to_context(pCodecCtx, pFmtCtx->streams[videoIndex]->codecpar) < 0) {
log_s("Didn't parameters to contex.");
return -1;
}
AVCodec *pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
if (pCodec == NULL) {
log_s("Codec not found.");
return -1;
}
if (avcodec_open2(pCodecCtx, pCodec, NULL)<0) {//打开解码器
log_s("Could not open codec.");
return -1;
}
//7. 解码播放开始准备工作
pFrame = av_frame_alloc();
pFrameYUV = av_frame_alloc();

//根据需要解码的类型,获取需要的buffer,不要忘记free
outBuffer = (uint8_t *)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1) * sizeof(uint8_t));
//根据指定的图像参数和提供的数组设置数据指针和行数 ,数据填充到对应的pFrameYUV里面
av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize, outBuffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1);

pPacket = av_packet_alloc();
log_s("--------------- File Information ----------------");
av_dump_format(pFmtCtx, 0, filePath, 0);
log_s("-------------------------------------------------");
//获取SwsContext
pSwsCtx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,
pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, NULL, NULL, NULL, NULL);
//----------------------------------------------------------------------------------------------------------------
// 8. SDL相关初始化
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
log_s("Could not initialize SDL - ");
log_s(SDL_GetError());
return -1;
}
screen_w = pCodecCtx->width;
screen_h = pCodecCtx->height;
screen = SDL_CreateWindow("WS ffmpeg player", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
pCodecCtx->width/2, pCodecCtx->height/2, SDL_WINDOW_OPENGL);
if (!screen) {
log_s("SDL: could not create window - exiting");
log_s(SDL_GetError());
return -1;
}

sdlRenderer = SDL_CreateRenderer(screen, -1, 0);
//IYUV: Y + U + V (3 planes)
//YV12: Y + V + U (3 planes)
sdlTexture = SDL_CreateTexture(sdlRenderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, pCodecCtx->width, pCodecCtx->height);

sdlRect.x = 0;
sdlRect.y = 0;
sdlRect.w = screen_w;
sdlRect.h = screen_h;

video_tid = SDL_CreateThread(sfp_refresh_thread, NULL, NULL);
//----------------------------------------------------------------------------------------------------------------


int count = 0;

//9.读取数据播放
for (;;) {
//Wait
SDL_WaitEvent(&event);
if (event.type == SFM_REFRESH_EVENT) {
if (av_read_frame(pFmtCtx, pPacket) == 0) {
if (pPacket->stream_index == videoIndex) {
if (avcodec_send_packet(pCodecCtx, pPacket) != 0) {//解码一帧压缩数据
log_s("Decode end or Error.");
break;
}
else {//处理解码数据
avcodec_receive_frame(pCodecCtx, pFrame);

if (sws_scale(pSwsCtx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height,
pFrameYUV->data, pFrameYUV->linesize) == 0) {
continue;
}

count++;

//SDL播放---------------------------
SDL_UpdateTexture(sdlTexture, NULL, pFrameYUV->data[0], pFrameYUV->linesize[0]);
SDL_RenderClear(sdlRenderer);
//SDL_RenderCopy( sdlRenderer, sdlTexture, &sdlRect, &sdlRect);
SDL_RenderCopy(sdlRenderer, sdlTexture, &sdlRect, NULL);
SDL_RenderPresent(sdlRenderer);
//SDL End-----------------------

log_s("Succeed to play frame!", count);
}
}
}else {
//退出线程
thread_exit = 1;
av_packet_unref(pPacket);
}
}
else if (event.type == SDL_KEYDOWN) {
log_s("Pause");
//Pause
if (event.key.keysym.sym == SDLK_SPACE)
thread_pause = !thread_pause;
}
else if (event.type == SDL_QUIT) {
log_s("quit");
thread_exit = 1;
break;
}
else if (event.type == SFM_BREAK_EVENT) {
log_s("break");
break;
}
}
//sdl退出
SDL_Quit();

//回收
if (pSwsCtx != NULL) {
sws_freeContext(pSwsCtx);
}
if (outBuffer != NULL) {
av_free(outBuffer);
}
if (pFrameYUV != NULL) {
av_frame_free(&pFrameYUV);
}
if (pFrame != NULL) {
av_frame_free(&pFrame);
}
if (pCodecCtx != NULL) {
avcodec_close(pCodecCtx);
}
if (pFmtCtx != NULL) {
avformat_close_input(&pFmtCtx);
}
}


int main()
{
play("F:/视频资源/gxsp.mp4");
getchar();
return 0;
}

参考链接:https://blog.csdn.net/king1425/article/details/71171142

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