可以暂停 快进
引入刷新机制
作用:重复执行一个函数的效果通常不是周期性的,因为每次加载和处理的数据所消耗的时间是不固定的,因此单纯地在一个循环中使用SDL_RenderPresent(renderer)会令视频播放产生帧率跳动的情况。因此需要引入一个定期刷新机制,令视频的播放有一个固定的帧率。
通常使用多线程的方式进行画面刷新管理,主线程进入主循环中等待事件,画面刷新线程在一段时间后发送画面刷新事件,主线程收到画面刷新事件后进行画面刷新操作。
实现帧率改变的初衷是改变播放视频的速度,但是总感觉这样的实现方式并不好,因为当你加速播放的时候,一秒内极有可能处理相当多的视频帧,会对播放设备产生明显影响,因此暂时认为对视频的加减速播放应该有更优的方案。
#include<iostream>
#include<string.h>
extern "C"
{
#include "SDL/SDL.h"
#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 s_thread_pause = 0; // 暂停标志 = 1则暂停
//视频播放相关参数
int delay_time = 40;
// 画面刷新线程
int refresh_video_timer(void *data)
{
while (s_thread_exit != 1)//如果没有置位退出
{
if (s_thread_pause != 1) {
SDL_Event event;
event.type = REFRESH_EVENT;
SDL_PushEvent(&event);// 向主线程发送刷新事件
}
SDL_Delay(delay_time);// 延时工作
}
s_thread_exit = 0;
s_thread_pause = 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 = "2_audio_track_5s.mp4"; // 视频文件路径
// 初始化ffmpeg组件
AVFormatContext *pFormatCtx = nullptr; // 视频文件上下文
int videostream, audiostream; // 视频流标识 音频流标识
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;
}
audiostream = av_find_best_stream(pFormatCtx, AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, 0);
if (audiostream == -1) {
cout << "can not open a audio 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;
}
}
if (packet.stream_index == videostream) {
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_KEYDOWN) {// 如果用户按下了键盘
if (event.key.keysym.sym == SDLK_ESCAPE) {// 按下ESC键 直接退出播放器
s_thread_exit = 1;
}
if (event.key.keysym.sym == SDLK_SPACE) {// 按下space键 控制视频播放暂停
s_thread_pause = !s_thread_pause;
}
if (event.key.keysym.sym == SDLK_F1) {// 按下F1 减速
delay_time += 10;
}
if (event.key.keysym.sym == SDLK_F2) {// 按下F2 加速
if (delay_time > 10) delay_time -= 10;
}
}
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;
}