SDL+ffmpeg 最新可直接运行 视频播放器

可以暂停 快进
引入刷新机制
作用:重复执行一个函数的效果通常不是周期性的,因为每次加载和处理的数据所消耗的时间是不固定的,因此单纯地在一个循环中使用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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值