SDL2 播放视频文件(MP4)

news/2024/7/10 22:23:03 标签: ffmpeg, SDL, 视频

1.简介

这里引入FFmpeg库,获取视频流数据,然后通过FFmpeg将视频流解码成YUV原始数据,再将YUV数据送入到SDL库中实现视频播放。

2.FFmpeg的操作流程

  • 注册API:av_register_all()
  • 构建输入AVFormatContext上下文:avformat_open_input()
  • 查找音视频流信息:avformat_find_stream_info()
  • 查找解码器:avcodec_find_decoder()
  • 打开解码器:avcodec_open2()
  • 然后通过while循环,不停的读取数据:av_read_frame()
  • 帧解码:avcodec_send_packet()和avcodec_receive_frame()

3.SDL显示视频流程

  • 初始化SDLSDL_Init();
  • 创建窗口:SDL_CreateWindow();
  • 创建渲染器:SDL_CreateRenderer();
  • 创建纹理:SDL_CreateTexture();
  • 设置纹理数据:SDL_UpdateTexture();
  • 纹理复制给渲染目标:使用SDL_RenderCopy()将纹理数据复制给渲染目标。在使用SDL_RenderCopy()之前,可以使用SDL_RenderClear()先使用清空渲染目标。
  • 显示界面:SDL_RenderPresent();

4.示例

#include <stdio.h>
#include <SDL.h>

extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libavutil/imgutils.h"
};

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

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(40);
	}

	//Break
	SDL_Event event;
	event.type = SFM_BREAK_EVENT;
	SDL_PushEvent(&event);

	return 0;
}

AVFrame *recv(AVCodecContext *codecCtx)
{
	if (!codecCtx)
	{
		return NULL;
	}

	AVFrame *frame = av_frame_alloc();
	int ret = avcodec_receive_frame(codecCtx, frame);

	if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
	{
		av_frame_free(&frame);
		return NULL;
	}
	else if (ret < 0)
	{
		av_frame_free(&frame);
		return NULL;
	}

	return frame;
}


#undef main
int main(int argc, char* argv[])
{
	///ffmpeg
	avformat_network_init();

	AVFormatContext* pFormatCtx = NULL;
	const char* inputUrl = "./3.mp4";

	///打开输入的流
	int ret = avformat_open_input(&pFormatCtx, inputUrl, NULL, NULL);
	if (ret != 0)
	{
		printf("Couldn't open input stream.\n");
		return -1;
	}

	//查找流信息
	if (avformat_find_stream_info(pFormatCtx, NULL) < 0)
	{
		printf("Couldn't find stream information.\n");
		return -1;
	}

	//找到视频流索引
	int video_index = av_find_best_stream(pFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
	AVStream* st = pFormatCtx->streams[video_index];
	AVCodec* codec = nullptr;
	//找到解码器
	codec = avcodec_find_decoder(st->codecpar->codec_id);
	if (!codec)
	{
		fprintf(stderr, "Codec not found\n");
		return -1;
	}

	//申请AVCodecContext
	AVCodecContext* pCodecCtx = avcodec_alloc_context3(codec);
	if (!pCodecCtx)
	{
		return -1;
	}

	avcodec_parameters_to_context(pCodecCtx, pFormatCtx->streams[video_index]->codecpar);

	//打开解码器
	if ((ret = avcodec_open2(pCodecCtx, codec, NULL) < 0))
	{
		return -1;
	}

	AVFrame *pFrameYUV = av_frame_alloc();
	AVPacket* pkt = av_packet_alloc();

	unsigned char *out_buffer = (unsigned char *)av_malloc(av_image_get_buffer_size(
		AV_PIX_FMT_YUV420P, 
		pCodecCtx->width, 
		pCodecCtx->height,
		1)
	);

	av_image_fill_arrays(pFrameYUV->data, 
		pFrameYUV->linesize, 
		out_buffer,
		AV_PIX_FMT_YUV420P,
		pCodecCtx->width, 
		pCodecCtx->height,
		1
	);

	//------------SDL----------------
	int screen_w, screen_h;
	SDL_Rect sdlRect;
	
	struct SwsContext *img_convert_ctx;
	char filepath[] = "Titanic.ts";


	//Output Info-----------------------------
	printf("---------------- File Information ---------------\n");
	av_dump_format(pFormatCtx, 0, filepath, 0);
	printf("-------------------------------------------------\n");

	img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,
		pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);


	if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
		printf("Could not initialize SDL - %s\n", SDL_GetError());
		return -1;
	}

	//SDL 2.0 Support for multiple windows
	screen_w = pCodecCtx->width;
	screen_h = pCodecCtx->height;
	SDL_Window *screen = SDL_CreateWindow("SDL Play Video", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
		screen_w, screen_h, SDL_WINDOW_OPENGL);

	if (!screen) 
	{
		printf("SDL: could not create window - exiting:%s\n", SDL_GetError());
		return -1;
	}

	SDL_Renderer *sdlRenderer = SDL_CreateRenderer(screen, -1, 0);
	//IYUV: Y + U + V  (3 planes)
	//YV12: Y + V + U  (3 planes)
	SDL_Texture *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;
	SDL_Thread *video_tid = SDL_CreateThread(sfp_refresh_thread, NULL, NULL);
	//------------SDL End------------
	//Event Loop
	SDL_Event event;
	for (;;) 
	{
		//Wait
		SDL_WaitEvent(&event);
		if (event.type == SFM_REFRESH_EVENT) 
		{
			//不断的读取一帧数据
			while (1) 
			{
				if (av_read_frame(pFormatCtx, pkt) < 0)
					thread_exit = 1;

				if (pkt->stream_index == video_index)
					break;
			}

			//一次send 多次recv
			int ret = avcodec_send_packet(pCodecCtx, pkt);
			if (ret < 0)
				continue;

			//释放资源
			av_packet_unref(pkt);

			while (1)
			{
				AVFrame *pFrame = recv(pCodecCtx);
				if (!pFrame)
					break;

				sws_scale(img_convert_ctx, (const unsigned char* const*)pFrame->data, pFrame->linesize, 0,
								pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);
				//SDL---------------------------
				SDL_UpdateTexture(sdlTexture, NULL, pFrameYUV->data[0], pFrameYUV->linesize[0]);
				SDL_RenderClear(sdlRenderer);
				//SDL_RenderCopy( sdlRenderer, sdlTexture, &sdlRect, &sdlRect );  
				SDL_RenderCopy(sdlRenderer, sdlTexture, NULL, NULL);
				SDL_RenderPresent(sdlRenderer);
				//SDL End-----------------------

				av_frame_free(&pFrame);
			}
		}
		else if (event.type == SDL_KEYDOWN) 
		{
			//空格键暂停播放
			if (event.key.keysym.sym == SDLK_SPACE)
				thread_pause = !thread_pause;
		}
		else if (event.type == SDL_QUIT) 
		{
			thread_exit = 1;
		}
		else if (event.type == SFM_BREAK_EVENT) 
		{
			break;
		}
	}

	SDL_Quit();
	//--------------
	sws_freeContext(img_convert_ctx);
	avcodec_close(pCodecCtx);
	avcodec_free_context(&pCodecCtx);
	avformat_close_input(&pFormatCtx);
	av_frame_free(&pFrameYUV);
	av_packet_free(&pkt);

	return 0;
}

5.相关推荐

[总结]FFMPEG视音频编解码零基础学习方法_零基础ffmpeg 雷霄骅-CSDN博客 

SDL2 播放视频数据(YUV420P)-CSDN博客

SDL2 消息循环和事件响应-CSDN博客

FFmpeg 视频解码(秒懂)-CSDN博客


http://www.niftyadmin.cn/n/5181391.html

相关文章

openssl+AES开发实例(linux)

文章目录 一、AES介绍二、AES原理三、AES开发实例 一、AES介绍 AES&#xff08;Advanced Encryption Standard&#xff09;是一种对称密钥加密标准&#xff0c;它是一种对称加密算法&#xff0c;意味着相同的密钥用于加密和解密数据。AES 是 NIST&#xff08;美国国家标准与技…

多语言外贸跨境商城源码/支持多商家入驻/一键采集铺货/后台下单等

随着全球化的不断深入&#xff0c;多语言外贸跨境商城已成为全球贸易的新趋势。我们致力于打造一个支持多商家入驻、一键采集铺货、后台下单等多项功能的电商平台&#xff0c;帮助您轻松开启全球贸易之旅。 一、多语言支持&#xff0c;让您的事业走向世界 我们提供多语言支持&a…

Netty Review - 从BIO到NIO的进化推演

文章目录 BIODEMO 1DEMO 2小结论单线程BIO的缺陷BIO如何处理并发多线程BIO服务器的弊端 NIONIO要解决的问题模拟NIO方案一&#xff1a; &#xff08;等待连接时和等待数据时不阻塞&#xff09;方案二&#xff08;缓存Socket&#xff0c;轮询数据是否准备好&#xff09;方案二存…

windows远程桌面登录ubuntu,黑屏闪退,

首先需要安装xrdp以后才能远程登录&#xff0c;安装命令 sudo apt-get install xrdp 这里需要注意一个问题&#xff0c;如果在ubuntu 本地登录的情况下&#xff0c;windows远程登录会出现黑屏甚至闪退的问题。原因是本地登录和远程登陆是互斥的&#xff0c;本地登录了就不能远…

thinkphp6(TP6)访问控制器报404(Nginx)

起因&#xff1a; 安装thinphp6后&#xff0c;发现无法访问控制器&#xff0c;直接通过URL访问&#xff0c;就报错404。 错误原因&#xff1a; Nginx不支持URL的 PathInfo。 解决方法&#xff1a; 配置伪静态。 伪静态代码&#xff1a; location / {if (!-e $request_filen…

<MySQL> 查询数据进阶操作 -- 聚合查询

目录 一、聚合查询概述 二、聚合函数查询 2.1 常用函数 2.2 使用函数演示 2.3 聚合函数参数为*或列名的查询区别 2.4 字符串不能参与数学运算 2.5 具有误导性的结果集 三、分组查询 group by 四、分组后条件表达式查询 五、MySQL 中各个关键字的执行顺序 一、聚合查询…

网络运维Day17

文章目录 什么是数据库MySQL介绍实验环境准备构建MySQL服务连接数据库修改root密码 数据库基础常用的SQL命令分类SQL命令使用规则MySQL基本操作创建库创建表查看表结构 记录管理命令 数据类型数值类型 数据类型日期时间类型时间函数案例枚举类型 约束条件案例修改表结构添加新字…

Linux socket编程(3):利用fork实现服务端与多个客户端建立连接

上一节&#xff0c;我们实现了一个客户端/服务端的Socket通信的代码&#xff0c;在这个例子中&#xff0c;客户端连接上服务端后发送一个字符串&#xff0c;而服务端接收到字符串并打印出来后就关闭所有套接字并退出了。 上一节的代码较为简单&#xff0c;在实际的应用中&…