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

news2024/9/29 3:26:47

前言

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

前一篇文章
[音视频学习笔记]六、自制音视频播放器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();
	}
}

.

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

备考ICA----Istio实验4---使用 Istio 进行金丝雀部署

备考ICA----Istio实验4—使用 Istio 进行金丝雀部署 上一个实验已经通过DestinationRule实现了部分金丝雀部署的功能,这个实验会更完整的模拟展示一个环境由v1慢慢过渡到v2版本的金丝雀发布. 1. 环境清理 kubectl delete gw/helloworld-gateway vs/helloworld dr/helloworld…

基于Arduino IDE 野火ESP8266模块 EEPROM 存储开发

一、操作存储器 我们可以使用ESP8266模块的EEPROM&#xff0c;也就是可读可擦存储器&#xff0c;可以掉电不丢失地帮我们存储一些数据。ESP8266微控制器有一个闪存区(Flash memory) 来模拟Arduino的EEPROM。这是微控制器中一个特殊的内存位置&#xff0c;即使在主板关闭后&…

Redis的安装与启动

一、Linux环境安装&启动Redis 第一步&#xff1a;在官网下载好Redis安装包&#xff0c;上传到Linux中并进行解压到相应&#xff08;如/opt/software/&#xff09;目录中&#xff1b;&#xff08;注意&#xff1a;完成了第二步后&#xff0c;即安装了C/C语言编译器后&#…

中型企业网络路由器配置(ensp)实验

vlan、vlan间路由、ospf协议等来实现三层交换机和单臂路由之间的通信 拓扑图&#xff1a; 1. 配置三层交换机vlan和vlan间路由 SW1 #进入视图 sys sysn sw1 undo info-center enable#配置vlan vlan batch 10 20 30 40 50 60#配置access口 int g0/0/1 port link-type access …

Web前端笔记+表单练习+五彩导航

一、笔记 表单&#xff1a;数据交互的一种方式 登录、注册、搜索 <from> <input type""> --- <input type"text"> --- 普通输入框&#xff0c;内容在一行显示 <input type"password"> --- 密码框 <input type"…

python拍卖行系统的设计与实现flask-django-nodejs-php

此系统设计主要采用的是python语言来进行开发&#xff0c;采用django/flask框架技术&#xff0c;框架分为三层&#xff0c;分别是控制层Controller&#xff0c;业务处理层Service&#xff0c;持久层dao&#xff0c;能够采用多层次管理开发&#xff0c;对于各个模块设计制作有一…

Linux系统------------MySQL事务

目录 一、MySQL事务的概念 二、事务的ACID特点 ●原子性 ●一致性 ●隔离性 ●持久性 事务之间的相互影响有以下几种&#xff1a; ①脏读 ②不可重复读 ③幻读 ④丢失更新 三、Mysql及事务隔离级别 3.1Mysql及事务隔离级别 &#xff08;1&#xff09;read…

搭建基于 Snowflake 的 CI/CD 最佳实践!

Snowflake 提供了可扩展的计算和存储资源&#xff0c;和基于 SQL 的界面 Snowsight&#xff0c;方便用户进行数据操作和分析。然而&#xff0c;如果用户想将自己的 CI/CD 流程与 Snowflake 集成时&#xff0c;会发现一些不便之处&#xff08;尤其相比其 SnowSight 优秀的查询能…

API调试管理工具Postman下载及操作介绍

1.下载安装postman地址&#xff1a;https://www.getpostman.com/downloads/ 2.创建项目 3.创建请求API 然后点击save保存api 4.用一个变量保存主域名&#xff0c;方便后续操作 就类似下面的baseurl 5.创建新环境 6.添加变量&#xff08;如添加本地测试环境url——ba…

ThingsBoard初始化数据库Postgres+Cassandra

本章将介绍ThingsBoard初始化数据PostgresCassandra&#xff0c;两种数据库结合使用&#xff0c;以及源码的编译安装。本机环境&#xff1a;Centos7、Docker、Postgres、Cassandra 环境安装 开发环境要求&#xff1a; docker &#xff1b;Docker&#xff1b;Postgres:Cassandr…

nodejs各版本下载

https://registry.npmmirror.com/binary.html 然后进入nodejs各个版本&#xff0c;然后按需选择

Qt 利用共享内存实现一次只能启动一个程序(单实例运行)

Qt 利用共享内存实现一次只能启动一个程序 文章目录 Qt 利用共享内存实现一次只能启动一个程序摘要利用共享内存实现一次只能启动一个程序示例代码 关键字&#xff1a; Qt、 unique、 单一、 QSharedMemory、 共享内存 摘要 今天接着在公司搞我的屎山代码&#xff0c;按照…

unbantu Apache的基本配置与配置静态资源访问

目录 前言: 1.Apache介绍 2.安装Apache 3. 测试Apache服务是否启动成功 3.1配置Servername 3.2重启服务 4.配置Apache主页面 5. 配置静态的资源 6.为静态资源设置访问权限(基于源地址) 致谢: 前言: 此博客是基于unbantu的Apache服务的详细解析&#xff0c;在这片博…

专业130+总分410+西南交通大学924信号与系统考研经验西南交大电子信息通信工程,真题,大纲,参考书。

初试分数出来&#xff0c;专业课924信号与系统130&#xff0c;总分410&#xff0c;整体上发挥正常&#xff0c;但是还有遗憾&#xff0c;其实自己可以做的更好&#xff0c;总结一下经验&#xff0c;希望对大家有所帮助。专业课&#xff1a;&#xff08;130&#xff09; 西南交…

(附源码)基于Spring Boot + Vue 在线网课学习系统的设计与实现

前言 &#x1f497;博主介绍&#xff1a;✌专注于Java、小程序技术领域和毕业项目实战✌&#x1f497; &#x1f447;&#x1f3fb; 精彩专栏 推荐订阅&#x1f447;&#x1f3fb; 2024年Java精品实战案例《100套》 &#x1f345;文末获取源码联系&#x1f345; &#x1f31…

仿京东项目——京西商城(数据库设计)

文章目录 仿京东——京西商城数据库设计建立E-R图数据库表设计用户表商品表订单表订单详情表评论表购物车表购物车项表 仿京东——京西商城 数据库设计 主要实体有&#xff1a; 用户 用户ID&#xff08;User_ID&#xff09;&#xff1a;唯一标识用户的主键 用户名&#xff0…

关于在vue中有时候表格的位置不对是怎么个情况

今天在写代码的时候多了一个<div>标签&#xff0c;导致表格的位置大小不对 <template><div><tr><td><input type"checkbox" checked"true" /></td><td>xxxxx</td><td><button class"…

Airgorah:一款功能强大的WiFi安全审计工具

关于Airgorah Airgorah是一款功能强大的WiFi安全审计工具&#xff0c;该工具可以轻松发现和识别连接到无线接入点的客户端&#xff0c;并对特定的客户端执行身份验证攻击测试&#xff0c;捕捉WPA握手包&#xff0c;并尝试破解接入点的密码。在该工具的帮助下&#xff0c;广大研…

AcWing 1360. 有序分数(每日一题)

目录 题目&#xff1a; 枚举版&#xff1a; 递归版&#xff08;Stern-Brocot Tree&#xff09;&#xff1a; 总结&#xff1a; 原题链接&#xff1a;1360. 有序分数 - AcWing题库 题目&#xff1a; 给定一个整数 N&#xff0c;请你求出所有分母小于或等于 N&#xff0c;大…

Flutter 项目架构技术指南

Flutter 项目架构技术指南 视频 https://www.bilibili.com/video/BV1rx4y127kN/ 前言 原文 https://ducafecat.com/blog/flutter-clean-architecture-guide 探讨Flutter项目代码组织架构的关键方面和建议。了解设计原则SOLID、Clean Architecture&#xff0c;以及架构模式MVC…