[音视频学习笔记]七、自制音视频播放器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;//创建AVFormatContextptr_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_avctxif (avcodec_open2(ptr_avctx, ptr_codec, nullptr) < 0) {avcodec_close(ptr_avctx);avformat_close_input(&ptr_formatCtx);qDebug() << "avcodec_open2 err.";return -5;}//初始化ptr_avpkgptr_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_swsCtxptr_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.mzph.cn/news/764267.shtml

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

相关文章

Flutter动画(一)Ticker、Animate 原理

在任何系统的UI框架中&#xff0c;动画原理都是类似的&#xff0c;即&#xff1a;在一段时间内&#xff0c;快速地多次改变UI外观&#xff1b;由于人眼会产生视觉暂留&#xff0c;所以最终看到的就是一个“连续”的动画。 Flutter中对动画进行了抽象&#xff0c;主要涉及 Anim…

后端前行Vue之路(一):初识Vue

1.Vue是什么 Vue (读音 /vjuː/&#xff0c;类似于 view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是&#xff0c;Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层&#xff0c;不仅易于上手&#xff0c;还便于与第三方库或既有项目整合。另一方…

如何用pytorch调用预训练Swin Transformer中的一个Swin block模块

1&#xff0c;首先&#xff0c;我们需要知道的是&#xff0c;想要调用预训练的Swin Transformer模型&#xff0c;必须要安装pytorch2&#xff0c;因为pytorch1对应的torchvision中不包含Swin Transformer。 2&#xff0c;pytorch2调用预训练模型时&#xff0c;不建议使用pretr…

【python】python3基础

文章目录 一、安装pycharm 二、输入输出输出 print()文件输出&#xff1a;格式化输出&#xff1a; 输入input注释 三、编码规范四、变量保留字变量 五、数据类型数字类型整数浮点数复数 字符串类型布尔类型序列结构序列属性列表list &#xff0c;有序多维列表列表推导式 元组tu…

基于python+vue网络相册设计与实现flask-django-nodejs-php

网络相册设计与实现的目的是让使用者可以更方便的将人、设备和场景更立体的连接在一起。能让用户以更科幻的方式使用产品&#xff0c;体验高科技时代带给人们的方便&#xff0c;同时也能让用户体会到与以往常规产品不同的体验风格。 与安卓&#xff0c;iOS相比较起来&#xff0…

CentOS/RHEL 6.5 上 NFS mount 挂起kernel bug

我本身有四台机器做WAS集群&#xff0c;挂载nfs&#xff0c;其中随机一台客户端计算机端口关闭释放将进入不良状态&#xff0c;对 NFSv4 挂载的任何访问都将挂起&#xff08;例如“ls&#xff0c;cd 或者df均挂起”&#xff09;。这意味着没有人并且所有需要访问共享的用户进程…

深度学习图像处理02:Tensor数据类型

上一讲深度学习图像处理01&#xff1a;图像的本质&#xff0c;我们了解到图像处理的本质是对矩阵的操作。这一讲&#xff0c;我们讲介绍深度学习图像处理的基本数据类型&#xff1a;Tensor类型。 在深度学习领域&#xff0c;Tensor是一种核心的数据结构&#xff0c;用于表示和…

复旦大学MBA:iLab项目探寻科技创新 助力企业出海

2024年2月底&#xff0c;新一轮复旦MBA iLab商业咨询项目&#xff08;以下简称iLab项目&#xff09;正式拉开序幕。      科创大时代&#xff0c;如何于变局中创新突破、绘就商业“蓝图”&#xff1f;怎样把握ESG投资机遇&#xff0c;创造可持续发展的未来&#xff1f;如何…

图论07-被包围的区域(Java)

7.被包围的区域 题目描述 给你一个 m x n 的矩阵 board &#xff0c;由若干字符 X 和 O &#xff0c;找到所有被 X 围绕的区域&#xff0c;并将这些区域里所有的 O 用 X 填充。 示例 1&#xff1a; 输入&#xff1a;board [["X","X","X",&qu…

2.6、媒体查询(mediaquery)

概述 媒体查询作为响应式设计的核心,在移动设备上应用十分广泛。媒体查询可根据不同设备类型或同设备不同状态修改应用的样式。媒体查询常用于下面两种场景: 针对设备和应用的属性信息(比如显示区域、深浅色、分辨率),设计出相匹配的布局。当屏幕发生动态改变时(比如分屏…

V2X技术与智能传感器的完美融合:提升城市道路安全

在科技不断创新的今天&#xff0c;城市交通领域涌现了大量新技术。有时候我们不仅仅需要独立应用这些新技术来实现交通的变革&#xff0c;更需要将它们巧妙地结合连接起来&#xff0c;以获取更高效更安全的交通环境。本文将探讨V2X技术与智能传感器的结合&#xff0c;如何在城市…

专为智能设备安全打造 | 基于ACM32 MCU的智能断路器方案

随着我国电网建设的快速发展&#xff0c;数字化变电站成为建设和研究的热点&#xff0c;数字化变电站的核心在于一次设备的智能化与二次设备的网络化&#xff0c;对于断路器这种极其重要的电力一次设备而言&#xff0c;其智能化的实现有十分重要的意义&#xff0c;断路器智能化…

平衡隐私与效率,Partisia Blockchain 解锁数字安全新时代

原文&#xff1a;https://cointelegraph.com/news/exploring-multiparty-computations-role-in-the-future-of-blockchain-privacy&#xff1b; https://medium.com/partisia-blockchain/unlocking-tomorrow-outlook-for-mpc-in-2024-and-beyond-cb170e3ec567 编译&#xff1…

skywalking监听apisix

一、原理 Skywalking结合OpenTelemetry Collector Apisix的promethus插件实现对apisix metrics数据的收集。 二、数据流图 1. Apisix Promethus插件从Apisix收集指标数据。 2. OpenTelemetry Collector通过promethus receiver获取来自Apisix Promethus插件的指标数据&#…

python的OA公文发文管理系统flask-django-php-nodejs

采用结构化的分析设计&#xff0c;该方法要求结合一定的图表&#xff0c;在模块化的基础上进行系统的开发工作。在设计中采用“自下而上”的思想&#xff0c;在OA公文发文管理系统实现了用户、公文分类、公文信息、待办提醒等的功能性。系统根据现有的管理模块进行开发和扩展&a…

(附源码)基于Spring Boot和Vue的前后端分离考研资料分享平台的设计与实现

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

旅游网站|基于JSP技术+ Mysql+Java+ B/S结构的旅游网站设计与实现(可运行源码+数据库+设计文档)

推荐阅读100套最新项目 最新ssmjava项目文档视频演示可运行源码分享 最新jspjava项目文档视频演示可运行源码分享 最新Spring Boot项目文档视频演示可运行源码分享 2024年56套包含java&#xff0c;ssm&#xff0c;springboot的平台设计与实现项目系统开发资源&#xff08;可…

「媒体宣传」企业活动发布会邀请媒体报道的好处与优势?

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 企业活动发布会邀请媒体报道具有多种好处与优势&#xff0c;这些都有助于提升企业的知名度、形象和影响力。以下是一些主要的好处与优势&#xff1a; 提升品牌知名度&#xff1a;媒体报道…

目标控制器数字孪生系统的研究与设计

文章来源&#xff1a;铁路计算机应用,2023,32(10):36-41. 作者&#xff1a;许婧&#xff0c;杨硕&#xff0c;季志均 摘要&#xff1a;随着目标控制器&#xff08;OC&#xff0c;Object Controller&#xff09;系统在轨道交通领域的推广应用&#xff0c;其硬件投入较高、研发…

短视频矩阵系统----源头开发

短视频矩阵源码技术开发要求及实现流程&#xff1a; 短视频矩阵开发要求具备视频录制、编辑、剪辑、分享等基本功能&#xff0c;支持实时滤镜、特效、音乐等个性化编辑&#xff0c;能够实现高效的视频渲染和处理。开发流程主要包括需求分析、技术选型、设计架构、编码实现、测试…