在上一篇文章中,代码的画面刷新不是定期实现的,这就导致视频的播放速度与实际不符,因此我们需要对其进行修改,使用多线程实现画面刷新的控制。
该工程使用的ffmpeg版本为5.01,SDL版本为2.0.22。
#include#include #include extern "C" { #include "libavformat/avformat.h" //头文件不仅要在项目中鼠标点击配置,在代码中也要引入 #include "include/libavformat/avformat.h" #include "include/libswscale/swscale.h" #include "include/libavdevice/avdevice.h" #include"libavcodec/avcodec.h" } using namespace std; //声明函数 int refresh_video_timer(void *data); //自定义消息类型 #define REFRESH_EVENT (SDL_USEREVENT + 1) // 请求画面刷新事件 #define QUIT_EVENT (SDL_USEREVENT + 2) // 主线程循环退出事件 // 线程标志位 int s_thread_exit = 0; // 退出标志 = 1则退出 // 画面刷新线程 int refresh_video_timer(void *data) { while (!s_thread_exit)//如果没有置位退出 { SDL_Event event; event.type = REFRESH_EVENT; SDL_PushEvent(&event);// 向主线程发送刷新事件 SDL_Delay(40);// 延时工作 } s_thread_exit = 0; //需要结束播放 SDL_Event event; event.type = QUIT_EVENT; SDL_PushEvent(&event);// 向主线程发送退出循环事件 return 0; } int main(int argc, char *argv[]) { int ret = 1; const char *file = "ds.mov"; // 视频文件路径 // 初始化ffmpeg组件 AVFormatContext *pFormatCtx = nullptr; // 视频文件上下文 int videostream; // 视频流标识 AVCodecParameters *pCodeParameters = nullptr; // 解码器相关参数 const AVCodec *pCodec = nullptr; // 解码器 AVCodecContext *pCodecCtx = nullptr; // 解码器上下文 AVFrame *pFrame = nullptr; // 解码后的帧 AVPacket packet; // 解码前的帧 // 初始化SDL组件 SDL_Rect rect; // 渲染显示面积 SDL_Window *window = nullptr; // 窗口 SDL_Renderer *renderer = nullptr; // 渲染 SDL_Texture *texture = nullptr; // 纹理 Uint32 pixformat; // 视频格式 SDL_Thread *timer_thread = nullptr; // 线程变量 SDL_Event event; // 主线程使用的事件 // 视频分辨率 int w_width = 640; int w_height = 480; // 当前窗口的分辨率 int cur_width = 640; int cur_height = 480; // SDL初始化 if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) { cout << "can not initialize SDL" << endl; return ret; } //FFMPEG 视频文件读取 if (avformat_open_input(&pFormatCtx, file, 0, nullptr) != 0) { cout << "can not open the video file" << endl; goto __FAIL; } //FFMPEG 寻找视频流 videostream = av_find_best_stream(pFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0); if (videostream == -1) { cout << "can not open a video stream" << endl; goto __FAIL; } //FFMPEG 寻找合适的解码器 //pCodeParameters = pFormatCtx->streams[videostream]->codecpar; pCodec = avcodec_find_decoder(pFormatCtx->streams[videostream]->codecpar->codec_id); if (pCodec == nullptr) { cout << "can not find a codec" << endl; goto __FAIL; } //FFMPEG 解码器信息配置 pCodecCtx = avcodec_alloc_context3(pCodec); if (avcodec_parameters_to_context(pCodecCtx, pFormatCtx->streams[videostream]->codecpar) != 0) { cout << "can not copy codec context" << endl; goto __FAIL; } //FFMPEG 解码器启动 if (avcodec_open2(pCodecCtx, pCodec, nullptr) < 0) { cout << " can not open the decoder" << endl; goto __FAIL; } //FFMPEG 初始化解码的帧 pFrame = av_frame_alloc(); //SDL 获得显示的视频画面的长度与宽度 w_width = pCodecCtx->width; w_height = pCodecCtx->height; //SDL 窗口初始化 window = SDL_CreateWindow("MEDIA PLAYER", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, w_width, w_height, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE); if (!window) { cout << "can not create window" << endl; goto __FAIL; } //SDL 渲染器初始化 renderer = SDL_CreateRenderer(window, -1, 0); if (!renderer) { cout << "can not create renderer!" << endl; goto __FAIL; } //SDL 视频格式与纹理初始化 pixformat = SDL_PIXELFORMAT_IYUV; texture = SDL_CreateTexture(renderer, pixformat, SDL_TEXTUREACCESS_STREAMING, w_width, w_height); // 创建画面刷新线程 timer_thread = SDL_CreateThread(refresh_video_timer,nullptr,nullptr); //主循环 while (1) { SDL_WaitEvent(&event);// 从事件队列中取事件 if (event.type == REFRESH_EVENT) {// 画面刷新事件 while (1) {// 每次只需要读取一个packet if (av_read_frame(pFormatCtx, &packet) < 0) {//如果已经读取完所有的帧,则退出 s_thread_exit = 1; } if (packet.stream_index == videostream) {//读取的是视频帧就退出 break; } } avcodec_send_packet(pCodecCtx, &packet);// 将一个packet进行解码操作 while (avcodec_receive_frame(pCodecCtx, pFrame) == 0) {// 将frame进行读取 //SDL 刷新纹理 SDL_UpdateYUVTexture(texture, NULL, pFrame->data[0], pFrame->linesize[0], pFrame->data[1], pFrame->linesize[1], pFrame->data[2], pFrame->linesize[2]); rect.x = 0;//SDL设置渲染目标的显示区域 rect.y = 0; rect.w = pCodecCtx->width; rect.h = pCodecCtx->height; SDL_RenderClear(renderer);//SDL 清空渲染器内容 SDL_RenderCopy(renderer, texture, NULL, &rect);//SDL 将纹理复制到渲染器 SDL_RenderPresent(renderer);//SDL 渲染 } av_packet_unref(&packet);// 将packet内容清空 } else if (event.type == SDL_WINDOWEVENT) {// 如果窗口出现了调整 SDL_GetWindowSize(window, &cur_width, &cur_height); cout << "the current width is: " << cur_width << " the current height is: " << cur_height << endl; } else if (event.type == SDL_QUIT) {// 如果窗口被关闭 s_thread_exit = 1;// 退出画面刷新线程 } else if (event.type == QUIT_EVENT) {// 退出主线程循环 break; } } __QUIT: ret = 0; __FAIL: if (pFrame) { av_frame_free(&pFrame); pFrame = nullptr; } if (pCodecCtx) { avcodec_close(pCodecCtx); pCodecCtx = nullptr; pCodec = nullptr; } if (pFormatCtx) { avformat_close_input(&pFormatCtx); pFormatCtx = nullptr; } if (pCodeParameters) { avcodec_parameters_free(&pCodeParameters); pCodeParameters = nullptr; } if (texture) { SDL_DestroyTexture(texture); texture = nullptr; } if (renderer) { SDL_DestroyRenderer(renderer); renderer = nullptr; } if (window) { SDL_DestroyWindow(window); window = nullptr; } SDL_Quit(); cout << "succeed!" << endl; return ret; }
以上代码基本就是ffmpeg + SDL2 实现播放器(一)和SDL2播放yuv视频文件与事件(event)的结合版,需要新注意的点基本只有一个。
当我们使用画面刷新线程进行画面控制时,我们希望读取到一个画面刷新事件时,就进行一次packet的读取,但是这里会出现一个情况,就是如果视频读到了末尾,没有读到packet了怎么办?因此需要把这方面的逻辑特殊处理下。
while (1) {// 每次只需要读取一个packet
if (av_read_frame(pFormatCtx, &packet) < 0) {//如果已经读取完所有的帧,则退出
s_thread_exit = 1;
}
if (packet.stream_index == videostream) {//读取的是视频帧就退出
break;
}
}
如上所示,在收到画面刷新事件之后进入一个循环,当读取到packet之后就可以退出循环,如果没有读取到packet就向线程刷新线程发送线程关闭指令,并退出程序。



