[音视频学习笔记]七、自制音视频播放器Part2 - VS + Qt +FFmpeg 写一个简单的视频播放器

前言

话不多说,重走霄骅登神路

前一篇文章
[音视频学习笔记]六、自制音视频播放器Part1 -新版本ffmpeg,Qt +VS2022,都什么年代了还在写传统播放器?

本文相关代码仓库:
MediaPlay-FFmpeg - Public

转载雷神的两个流程图,挺实用的,不过现在接口有一些小修改,可能有点不一样了

在这里插入图片描述

流程

  1. 首先,定义了一些变量,包括用于存储视频帧的缓冲区 buf,标记是否为视频流的 isVideo,以及一些用于处理视频的结构体和指针。

  2. 创建并初始化 AVFormatContext 结构体 ptr_formatCtx,并打开视频文件以获取音视频流数据信息。

  3. 使用 avformat_find_stream_info 函数获取音视频流信息,并通过循环找到视频流的索引 streamIndex。

  4. 检查是否成功找到视频流,判断 ptr_formatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO 如果没有找到则退出。

  5. 分配并初始化 AVCodecContext 结构体 ptr_avctx,并根据流的参数找到AVCodecParameters,将其放在ptr_formatCtx->streams[streamIndex]->codecpar。找到对应视频流的解码器avcodec_find_decoder,并检查。

  6. 使用 avcodec_open2 函数打开解码器,初始化ptr_avctx,准备解码视频帧。

  7. 分配并初始化 AVPacket 结构体 ptr_avpkg,以及视频帧的空间 ptr_avframe和 ptr_avframeRGB。

  8. 为图像数据存储空间 buf 分配内存,并根据视频的宽高调整窗口大小。

  9. 初始化 SwsContext 结构体 ptr_swsCtx,用于图像的缩放和格式转换。

  10. 进入循环,不断读取视频数据帧,并解码、处理、显示。

  11. 在循环中,通过 av_read_frame 读取视频帧数据,然后使用 avcodec_send_packet将数据发送给解码器进行解码,使用avcodec_receive_frame解码视频数据,并将解码后的图像数据经过格式转换后显示在界面上。

  12. 循环中还包含了处理视频播放状态的逻辑,包括播放、暂停和结束播放等情况。

  13. 最后,释放相关资源,包括 SwsContext 结构体、图像帧内存和解码器等。

需要注意的点

  1. 播放延时
    我们在播放的时候实际上是需要提供延时的,但是又不能直接QThread::msleep(10),因为画面的渲染在主线程上进行,而主线程是严格禁止msleep(10)的否则容易崩溃,所以采用以下方式:
//延时, 不能直接sleep延时,UI主线程不能直接被阻塞,不然会有问题的
void delay(int ms)
{
    QTime stopTime;
    stopTime.start();
    //qDebug()<<"start:"<<QTime::currentTime().toString("HH:mm:ss.zzz");
    while(stopTime.elapsed() < ms)//stopTime.elapsed()返回从start开始到现在的毫秒数
    {
        QCoreApplication::processEvents();
    }
    //qDebug()<<"stop :"<<QTime::currentTime().toString("HH:mm:ss.zzz");
}
  1. 播放速度
    播放速度实际上就是通过影响上述的delay来实现的,在播放的过程中通过对应的值影响延时,从而修改播放速度。

项目效果:
在这里插入图片描述

核心代码

#include "../include/MediaPlay_Core.h"

MediaPlay_Core::MediaPlay_Core()
{

}

MediaPlay_Core::~MediaPlay_Core()
{}

void MediaPlay_Core::register_func(func_frame func)
{
	this->callback = func;
}

int MediaPlay_Core::playVideo(const char* videopath)
{
	unsigned char* buf;
	bool blnVideo = false;
	int ret, gotPicture;
	unsigned int streamIndex = 0;

	const AVCodec* ptr_codec = nullptr;
	AVPacket* ptr_avpkg = nullptr;
	AVCodecContext* ptr_avctx = nullptr;
	AVFrame* ptr_avframe, * ptr_avframeRGB = nullptr;
	AVFormatContext* ptr_formatCtx = nullptr;
	struct SwsContext* ptr_swsCtx = nullptr;

	this->videoUrl = QString::fromLocal8Bit(videopath);

	int width = 0;
	int height = 0;
	//创建AVFormatContext
	ptr_formatCtx = avformat_alloc_context();

	//初始化ptr_formatCtx 
	if (avformat_open_input(&ptr_formatCtx, videopath, nullptr, nullptr) != 0) {
		qDebug() << "AVFormat open input Error";
		return -1;
	}

	//获取音频流数据信息
	if (avformat_find_stream_info(ptr_formatCtx, nullptr) < 0) {
		avformat_close_input(&ptr_formatCtx);
		qDebug() << "AVFormat find stream info Error";
		return -2;
	}

	//找到视频流的索引

	for (int i = 0; i < ptr_formatCtx->nb_streams; ++i) {
		if (ptr_formatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
			streamIndex = i;
			blnVideo = true;
			break;
		}
	}

	//没有找到视频流则直接退出
	if (!blnVideo) {
		avformat_close_input(&ptr_formatCtx);
		qDebug() << "nv_streams error";
		return -3;
	}

	//获取视频流编码
	ptr_avctx = avcodec_alloc_context3(nullptr);

	//查找编码器
	avcodec_parameters_to_context(ptr_avctx, ptr_formatCtx->streams[streamIndex]->codecpar);
	ptr_codec = avcodec_find_decoder(ptr_avctx->codec_id);

	if (!ptr_codec) {
		avcodec_close(ptr_avctx);
		avformat_close_input(&ptr_formatCtx);
		qDebug() << "Can't find decoder";
		return -4;
	}

	//初始化ptr_avctx
	if (avcodec_open2(ptr_avctx, ptr_codec, nullptr) < 0) {
		avcodec_close(ptr_avctx);
		avformat_close_input(&ptr_formatCtx);
		qDebug() << "avcodec_open2 err.";
		return -5;
	}

	//初始化ptr_avpkg
	ptr_avpkg = (AVPacket*)av_malloc(sizeof(AVPacket));

	//初始化数据帧空间
	ptr_avframe = av_frame_alloc();
	ptr_avframeRGB = av_frame_alloc();

	//创建图像数据存储buf
   //av_image_get_buffer_size一帧大小
	buf = (unsigned char*)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_RGB32, ptr_avctx->width, ptr_avctx->height, 1));
	av_image_fill_arrays(ptr_avframeRGB->data, ptr_avframeRGB->linesize, buf, AV_PIX_FMT_RGB32, ptr_avctx->width, ptr_avctx->height, 1);

	width = ptr_avctx->width;
	height = ptr_avctx->height;

	//初始化ptr_swsCtx
	ptr_swsCtx = sws_getContext(ptr_avctx->width, ptr_avctx->height, ptr_avctx->pix_fmt, ptr_avctx->width, ptr_avctx->height, AV_PIX_FMT_RGB32, SWS_BICUBIC, nullptr, nullptr, nullptr);

	//尝试循环读取视频数据
	while (true) {
		if (this->state_playVideo == PlayState::Video_Playing) { // 正在播放
			if (av_read_frame(ptr_formatCtx, ptr_avpkg) >= 0) {	//读取一帧未解码的数据
				//如果是视频数据
				if (ptr_avpkg->stream_index == (int)streamIndex) {
					// 将数据发送给解码器进行解码
					ret = avcodec_send_packet(ptr_avctx, ptr_avpkg);
					if (ret < 0) {
						qDebug() << "发送数据包到解码器时发生错误: "<<ret;
						continue; // 处理错误情况并继续读取下一帧数据
					}
					//解码视频数据
					ret = avcodec_receive_frame(ptr_avctx, ptr_avframe);
					if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
					{
						av_packet_unref(ptr_avpkg); // 释放解码后的帧数据
						continue; // 没有可解码的帧数据或者已经解码完毕,继续读取下一帧
					}
					if (ret < 0) {
						qDebug() << "Decode error";
						continue;
					}
					// 处理解码后的图像帧
					sws_scale(ptr_swsCtx, (const unsigned char* const*)ptr_avframe->data, ptr_avframe->linesize, 0, ptr_avctx->height, ptr_avframeRGB->data, ptr_avframeRGB->linesize);
					if(this->callback != nullptr)
						this->callback((uchar*)ptr_avframeRGB->data[0], width, height);

					av_frame_unref(ptr_avframe);
				}

			}
			else {
				break;
			}
		}
		else if (this->state_playVideo == PlayState::Video_Finish)//播放结束
		{
			break;
		}
		else//暂停
		{
			delay(300);
		}
	}

	//释放资源
	sws_freeContext(ptr_swsCtx);
	av_frame_free(&ptr_avframeRGB);
	av_frame_free(&ptr_avframe);
	avcodec_close(ptr_avctx);
	avformat_close_input(&ptr_formatCtx);

	this->state_playVideo = PlayState::Video_Finish;
	qDebug() << "play finish!";
	return 0;
}

void MediaPlay_Core::VideoPlayControl(bool blnPlay)
{
	if (blnPlay) {
		if (this->state_playVideo != PlayState::Video_Playing) {
			this->state_playVideo = PlayState::Video_Playing;
			playVideo(this->videoUrl.toLocal8Bit().data());
		}
		else {
			this->state_playVideo = PlayState::Video_Playing;
		}
	}
	else {
		if (this->state_playVideo == PlayState::Video_Playing) {
			this->state_playVideo = PlayState::Video_Pause;
		}
	}
}

void MediaPlay_Core::VideoPlayEnd()
{
	//停止播放视频
	this->state_playVideo = PlayState::Video_Finish;
}

void MediaPlay_Core::delay(int msc)
{
	QTime stopTime;
	stopTime.start();
	while (stopTime.elapsed() < msc)//stopTime.elapsed()返回从start开始到现在的毫秒数
	{
		QCoreApplication::processEvents();
	}
}

.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/480082.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

css3鼠标悬停图片特效,图片悬停效果源码

特效介绍 css3鼠标悬停图片特效,图片悬停效果源码&#xff0c;可以在网页上面作为自己的动态加载名片&#xff0c;放到侧边栏或者网站合适的位置即可 动态效果 代码下载 css3鼠标悬停图片特效,图片悬停效果源码

阿里云 EMR Serverless Spark 版免费邀测中

随着大数据应用的广泛推广&#xff0c;企业对于数据处理的需求日益增长。为了进一步优化大数据开发流程&#xff0c;减少企业的运维成本&#xff0c;并提升数据处理的灵活性和效率&#xff0c;阿里云开源大数据平台 E-MapReduce &#xff08;简称“EMR”&#xff09;正式推出 E…

数据挖掘与机器学习 1. 绪论

于高山之巅&#xff0c;方见大河奔涌&#xff1b;于群峰之上&#xff0c;便觉长风浩荡 —— 24.3.22 一、数据挖掘和机器学习的定义 1.数据挖掘的狭义定义 背景&#xff1a;大数据时代——知识贫乏 数据挖掘的狭义定义&#xff1a; 数据挖掘就是从大量的、不完全的、有噪声的、…

基于docker配置pycharm开发环境

开发过程中&#xff0c;为了做好环境隔离&#xff0c;经常会采用docker来进行开发&#xff0c;但是如何快速将docker中的环境和本地开发的IDE链接起来是一个常见问题&#xff0c;下面对其进行简单的总结&#xff1a; &#xff08;1&#xff09;前期准备 开发环境docker和工具p…

ENISA 2023年威胁态势报告:主要发现和建议

欧盟网络安全局(ENISA)最近发布了其年度2023年威胁态势报告。该报告确定了预计在未来几年塑造网络安全格局的主要威胁、主要趋势、威胁参与者和攻击技术。在本文中&#xff0c;我们将总结报告的主要发现&#xff0c;并提供可操作的建议来缓解这些威胁。 介绍 ENISA 威胁态势报告…

活动回顾 | 走进华为向深问路,交流数智办公新体验

3月20日下午&#xff0c;“企业数智办公之走进华为”交流活动在华为上海研究所成功举办。此次活动由上海恒驰信息系统有限公司主办&#xff0c;华为云计算技术有限公司和上海利唐信息科技有限公司协办&#xff0c;旨在通过对企业数字差旅和HR数智化解决方案的交流&#xff0c;探…

在 Linux/Ubuntu/Debian 上安装 SQL Server 2019

Microsoft 为 Linux 发行版&#xff08;包括 Ubuntu&#xff09;提供 SQL Server。 以下是有关如何执行此操作的基本指南&#xff1a; 注册 Microsoft Ubuntu 存储库并添加公共存储库 GPG 密钥&#xff1a; sudo wget -qO- https://packages.microsoft.com/keys/microsoft.as…

53、Qt/信号与槽、QSS界面设计20240322

一、使用手动连接&#xff0c;将登录框中的取消按钮使用qt4版本的连接到自定义的槽函数中&#xff0c;在自定义的槽函数中调用关闭函数 将登录按钮使用qt5版本的连接到自定义的槽函数中&#xff0c;在槽函数中判断ui界面上输入的账号是否为"admin"&#xff0c;密码是…

IDEA调优-四大基础配置-编码纵享丝滑

文章目录 1.JVM虚拟机选项配置2.多线程编译速度3.构建共享堆内存大小4.关闭不必要的插件 1.JVM虚拟机选项配置 -Xms128m -Xmx8192m -XX:ReservedCodeCacheSize1024m -XX:UseG1GC -XX:SoftRefLRUPolicyMSPerMB50 -XX:CICompilerCount2 -XX:HeapDumpOnOutOfMemoryError -XX:-Omi…

赋能 DevOps:平台工程的关键作用

在当今快节奏的数字环境中&#xff0c;DevOps 已成为寻求简化软件开发和交付流程的组织的关键方法。DevOps 的核心在于开发和运营团队之间协作的概念&#xff0c;通过一组旨在自动化和提高软件交付生命周期效率的实践和工具来实现。 DevOps 实践的关键推动因素之一是平台工程。…

小程序渲染层图标错误

小程序渲染图标层出现错误&#xff1a; 官方提示&#xff1a;不影响可以忽略&#xff1b; 通过阿里巴巴矢量图标库--项目设置--字体格式--选中base64格式&#xff1b; 重新更新图标库代码&#xff0c;替换项目中的图标库&#xff1b; 重新加载小程序--渲染层错误的提示消失&…

[运维] 可视化爬虫易采集-EasySpider(笔记)

一、下载 ​下载地址 下滑到Assets页面&#xff0c;选择下载 二、解压运 ​解压压缩包&#xff0c;打开文件夹 在此文件夹下打开Linux Terimal, 并输入以下命令运行软件&#xff1a; ./easy-spider.sh 注意软件运行过程中不要关闭terminal。 三、使用 1.开始 首先点击…

Qt实现TFTP Server和 TFTP Client(一)

1 概述 TFTP协议是基于UDP的简单文件传输协议&#xff0c;协议双方为Client和Server.Client和Server之间通过5种消息来传输文件,消息前两个字节Code是消息类型&#xff0c;消息内容随消息类型不同而不同。传输模式有三种&#xff1a;octet,netascii和mail&#xff0c;octet为二…

浅谈Javascript虚拟列表(virtaul list)改造成虚拟表格(virtaul table)的技术

前端加载百万条数据列表&#xff0c;如果采用真实的DOM插入100万个div&#xff08;或li&#xff09;标签&#xff0c;肯定是非常卡顿的。这就不得不使用虚拟列表技术方案&#xff0c;但是虚拟列表技术方案网上有很详细的实现方法&#xff0c;今天我就来谈谈根据网上的方案&…

Prompt进阶系列5:LangGPT(提示链Prompt Chain)--提升模型鲁棒性

Prompt进阶系列5:LangGPT(提示链Prompt Chain)–提升模型鲁棒性 随着对大模型的应用实践的深入&#xff0c;许多大模型的使用者&#xff0c; Prompt 创作者对大模型的应用越来越得心应手。和 Prompt 有关的各种学习资料&#xff0c;各种优质内容也不断涌现。关于 Prompt 的实践…

ETL的全量和增量模式

在当今信息爆炸的时代&#xff0c;数据管理已经成为各行各业必不可少的一环。而在数据管理中&#xff0c;全量与增量模式作为两种主要的策略&#xff0c;各自具有独特的优势和适用场景&#xff0c;巧妙地灵活运用二者不仅能提升数据处理效率&#xff0c;更能保障数据的准确性。…

Alibaba spring cloud Dubbo使用(基于Zookeeper或者基于Nacos+泛化调用完整代码一键启动)

Quick Start Dubbo&#xff01;用更优雅的方式来实现RPC调用吧 - 掘金 dubbozookeeper demo 项目结构&#xff1a; RpcService 仅仅是提供服务的接口&#xff1a; public interface HelloService {String sayHello(String name); }DubboServer pom&#xff1a; <?xm…

爱普生EPSON全新传感技术方案亮相高交会,创造新时代“精智生活”

2023年中国国际高新技术成果交易会在深圳福田会展中心盛大举行&#xff0c;是目前中国规模最大、最具影响力的科技类展会之一。爱普生作为始终坚持“科技本地化”战略的技术创新前沿企业参与此次展会&#xff0c;为中国用户带来爱普生电子元器件三款创新技术与四大成熟传感器解…

基于JavaSpringmvc+myabtis+html的鲜花商城系统设计和实现

基于JavaSpringmvcmyabtishtml的鲜花商城系统设计和实现 博主介绍&#xff1a;多年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 央顺技术团队 Java毕设项目精品实战案例《1000套》 欢迎点赞 收藏 ⭐留言 文末…

C++学习随笔(6)——类和对象的拓展

1. 构造函数回顾 1.1 构造函数体赋值 在创建对象时&#xff0c;编译器通过调用构造函数&#xff0c;给对象中各个成员变量一个合适的初始值。 class Date { public:Date(int year, int month, int day){_year year;_month month;_day day;} private:int _year;int _mont…