SDL2文章列表
经过前面一系列的SDL2学习,终于到最后实现一个完整的简易播放器了。
线程模型
这是实现的简易播放器的线程模型,通过这张图再结合我们之前博客中学习的内容,基本可以了解播放器的一个整体运行流程。具体代码也是根据这张图来实现。
重要结构体
VideoState
整个播放器中最重要的结构体,解复用、解码、音视频同步、渲染相关参数都在该结构体中,它贯穿了整个播放流程。
typedef struct VideoState {
char filename[1024]; // 文件名称
AVFormatContext *pFormatCtx; // 上下文
int videoStream, audioStream; //音视频流index
//// 同步相关
double audio_clock;
double frame_timer;
double frame_last_pts;
double frame_last_delay;
double video_clock;
double video_current_pts;
int64_t video_current_pts_time;
//音频相关
AVStream *audio_st; // 音频流
AVCodecContext *audio_ctx; // 音频解码上下文
PacketQueue audioq; // 音频队列
uint8_t audio_buf[(MAX_AUDIO_FRAME_SIZE * 3) / 2]; // 音频缓存
unsigned int audio_buf_size;
unsigned int audio_buf_index;
AVFrame audio_frame; // 音频帧
AVPacket audio_pkt; // 音频包
uint8_t *audio_pkt_data;
int audio_pkt_size;
struct SwrContext *audio_swr_ctx; // 音频重采样
//video
AVStream *video_st; // 视频流
AVCodecContext *video_ctx; // 视频流解码上下文
PacketQueue videoq; // 视频流队列
VideoPicture pictq[VIDEO_PICTURE_QUEUE_SIZE]; // 解码后视频帧数组
int pictq_size, pictq_rindex, pictq_windex;
SDL_mutex *pictq_mutex;
SDL_cond *pictq_cond;
SDL_Thread *parse_tid; // 解复用线程
SDL_Thread *video_tid;// 视频解码线程
int quit; // 退出标记位
} VideoState;
复制代码
PacketQueue
//// 解复用后音视频packet保存队列
typedef struct PacketQueue {
AVPacketList *first_pkt, *last_pkt;
int nb_packets;
int size;
SDL_mutex *mutex;
SDL_cond *cond;
} PacketQueue;
复制代码
VideoPicture
//// 解码后视频帧
typedef struct VideoPicture {
AVFrame *frame;
int width, height;
double pts; // 音视频同步后视频帧应该播放的时间
} VideoPicture;
复制代码
具体代码
Main
- 初始化
- 创建定时器,定时视频帧的刷新
- 创建解复用线程
- 等待事件
int WinMain(int argc, char *argv[]) {
char *file = "C:\\Users\\lenovo\\Desktop\\IMG_5950.mp4";
SDL_Event event;
VideoState *is;
is = av_mallocz(sizeof(VideoState));
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());
exit(1);
}
//创建SDL Window
win = SDL_CreateWindow("Media Player",
100,
100,
640, 480,
SDL_WINDOW_RESIZABLE);
if (!win) {
fprintf(stderr, "SDL_CreateWindow error,exit!", SDL_GetError());
exit(1);
}
renderer = SDL_CreateRenderer(win, -1, 0);
text_mutex = SDL_CreateMutex();
strlcpy(is->filename, file, sizeof(is->filename));
is->pictq_mutex = SDL_CreateMutex();
is->pictq_cond = SDL_CreateCond();
// 定时刷新器,主要用来控制视频的刷新
schedule_refresh(is, 40);
// 创建解复用线程
is->parse_tid = SDL_CreateThread(demux_thread, "demux_thread", is);
if (!is->parse_tid) {
av_free(is);
return -1;
}
for (;;) {
// 等待SDL事件,否则阻塞
SDL_WaitEvent(&event);
switch (event.type) {
case FF_QUIT_EVENT:
case SDL_QUIT: // 退出
is->quit = 1;
goto Destroy;
case SDL_KEYDOWN:// ESC退出
if (event.key.keysym.sym == SDLK_ESCAPE) {
is->quit = 1;
goto Destroy;
}
break;
case FF_REFRESH_EVENT: // 定时器刷新事件
video_refresh_timer(event.user.data1);
break;
default:
break;
}
}
// 退出
Destroy:
SDL_Quit();
return 0;
}
复制代码
解复用
- 打开文件
- 找到音视频流
- 打开音频、视频流,创建视频解码线程,准备解码
- 读取packet,将音视频packet分别放入队列中,等待解码线程取出
int