Qt 环境实现视频和音频播放

news2024/11/13 14:20:12

在这个示例中,我们将使用 FFmpeg 进行视频和音频的解码,并使用 Qt 的界面进行显示和控制。为了实现音频和视频的解码以及同步显示,我们需要使用 FFmpeg 的解码库进行视频和音频解码,使用 Qt 的 QLabel 显示解码后的视频帧,使用 QAudioOutput 来播放解码后的音频。

环境依赖
FFmpeg:用于解码音视频数据。
Qt:用于图形界面和音频输出。
示例代码
这是一个使用 Qt 和 FFmpeg 结合实现的视频播放器,包括播放、暂停、进度条等功能。

#include <QMainWindow>
#include <QTimer>
#include <QSlider>
#include <QLabel>
#include <QPushButton>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QWidget>
#include <QImage>
#include <QPixmap>
#include <QFileDialog>
#include <QAudioOutput>
#include <QAudioFormat>

// FFmpeg 头文件
extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
#include <libswresample/swresample.h>
#include <libavutil/imgutils.h>
}

class VideoPlayer : public QMainWindow {
    Q_OBJECT

public:
    VideoPlayer(QWidget *parent = nullptr);
    ~VideoPlayer();

private slots:
    void play();
    void pause();
    void openFile();
    void updateFrame();

private:
    void initializeFFmpeg();
    void decodeVideo();
    void decodeAudio();
    void displayFrame(AVFrame *frame);
    void initializeAudio();
    void audioOutput();

    AVFormatContext *formatContext;
    AVCodecContext *videoCodecContext;
    AVCodecContext *audioCodecContext;
    SwsContext *swsContext;
    SwrContext *swrContext;
    int videoStreamIndex;
    int audioStreamIndex;

    QLabel *videoLabel;
    QPushButton *playButton;
    QPushButton *pauseButton;
    QSlider *progressSlider;
    QTimer *timer;
    QAudioOutput *audioOutputDevice;

    QByteArray audioBuffer;
    bool isPlaying;
};

VideoPlayer::VideoPlayer(QWidget *parent) : QMainWindow(parent), isPlaying(false) {
    // GUI 部件初始化
    videoLabel = new QLabel(this);
    playButton = new QPushButton("Play", this);
    pauseButton = new QPushButton("Pause", this);
    progressSlider = new QSlider(Qt::Horizontal, this);
    timer = new QTimer(this);

    // 布局
    QHBoxLayout *controlLayout = new QHBoxLayout;
    controlLayout->addWidget(playButton);
    controlLayout->addWidget(pauseButton);
    controlLayout->addWidget(progressSlider);

    QVBoxLayout *mainLayout = new QVBoxLayout;
    mainLayout->addWidget(videoLabel);
    mainLayout->addLayout(controlLayout);

    QWidget *centralWidget = new QWidget(this);
    centralWidget->setLayout(mainLayout);
    setCentralWidget(centralWidget);

    // 连接信号和槽
    connect(playButton, &QPushButton::clicked, this, &VideoPlayer::play);
    connect(pauseButton, &QPushButton::clicked, this, &VideoPlayer::pause);
    connect(timer, &QTimer::timeout, this, &VideoPlayer::updateFrame);

    // 初始化 FFmpeg
    initializeFFmpeg();
    initializeAudio();
}

VideoPlayer::~VideoPlayer() {
    avcodec_free_context(&videoCodecContext);
    avcodec_free_context(&audioCodecContext);
    avformat_close_input(&formatContext);
    sws_freeContext(swsContext);
    swr_free(&swrContext);
}

void VideoPlayer::initializeFFmpeg() {
    av_register_all();
    formatContext = avformat_alloc_context();

    QString fileName = QFileDialog::getOpenFileName(this, "Open Video File");
    if (fileName.isEmpty()) {
        return;
    }

    // 打开文件并找到流
    avformat_open_input(&formatContext, fileName.toStdString().c_str(), nullptr, nullptr);
    avformat_find_stream_info(formatContext, nullptr);

    // 查找视频流和音频流
    for (unsigned int i = 0; i < formatContext->nb_streams; i++) {
        AVCodecParameters *codecParams = formatContext->streams[i]->codecpar;
        AVCodec *codec = avcodec_find_decoder(codecParams->codec_id);

        if (codecParams->codec_type == AVMEDIA_TYPE_VIDEO) {
            videoCodecContext = avcodec_alloc_context3(codec);
            avcodec_parameters_to_context(videoCodecContext, codecParams);
            avcodec_open2(videoCodecContext, codec, nullptr);
            videoStreamIndex = i;
        } else if (codecParams->codec_type == AVMEDIA_TYPE_AUDIO) {
            audioCodecContext = avcodec_alloc_context3(codec);
            avcodec_parameters_to_context(audioCodecContext, codecParams);
            avcodec_open2(audioCodecContext, codec, nullptr);
            audioStreamIndex = i;

            // 音频重采样设置
            swrContext = swr_alloc();
            swr_alloc_set_opts(swrContext, AV_CH_LAYOUT_STEREO, AV_SAMPLE_FMT_S16,
                               audioCodecContext->sample_rate, audioCodecContext->channel_layout,
                               audioCodecContext->sample_fmt, audioCodecContext->sample_rate, 0, nullptr);
            swr_init(swrContext);
        }
    }

    // 视频缩放上下文
    swsContext = sws_getContext(videoCodecContext->width, videoCodecContext->height,
                                videoCodecContext->pix_fmt, videoCodecContext->width,
                                videoCodecContext->height, AV_PIX_FMT_RGB24, SWS_BILINEAR,
                                nullptr, nullptr, nullptr);
}

void VideoPlayer::initializeAudio() {
    QAudioFormat format;
    format.setSampleRate(audioCodecContext->sample_rate);
    format.setChannelCount(2);
    format.setSampleSize(16);
    format.setCodec("audio/pcm");
    format.setByteOrder(QAudioFormat::LittleEndian);
    format.setSampleType(QAudioFormat::SignedInt);

    audioOutputDevice = new QAudioOutput(format, this);
}

void VideoPlayer::play() {
    isPlaying = true;
    audioOutputDevice->start();
    timer->start(30);  // 30ms刷新一次
}

void VideoPlayer::pause() {
    isPlaying = false;
    audioOutputDevice->suspend();
    timer->stop();
}

void VideoPlayer::updateFrame() {
    decodeVideo();
    decodeAudio();
}

void VideoPlayer::decodeVideo() {
    AVPacket packet;
    while (av_read_frame(formatContext, &packet) >= 0) {
        if (packet.stream_index == videoStreamIndex) {
            avcodec_send_packet(videoCodecContext, &packet);
            AVFrame *frame = av_frame_alloc();
            if (avcodec_receive_frame(videoCodecContext, frame) == 0) {
                displayFrame(frame);
            }
            av_frame_free(&frame);
        }
        av_packet_unref(&packet);
    }
}

void VideoPlayer::decodeAudio() {
    AVPacket packet;
    while (av_read_frame(formatContext, &packet) >= 0) {
        if (packet.stream_index == audioStreamIndex) {
            avcodec_send_packet(audioCodecContext, &packet);
            AVFrame *frame = av_frame_alloc();
            if (avcodec_receive_frame(audioCodecContext, frame) == 0) {
                // 音频重采样
                int outSamples = swr_convert(swrContext, nullptr, 0,
                                             (const uint8_t **)frame->data, frame->nb_samples);
                audioBuffer.resize(outSamples * 2 * sizeof(int16_t));
                audioOutputDevice->write(audioBuffer.data(), audioBuffer.size());
            }
            av_frame_free(&frame);
        }
        av_packet_unref(&packet);
    }
}

void VideoPlayer::displayFrame(AVFrame *frame) {
    AVFrame *rgbFrame = av_frame_alloc();
    int numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGB24, videoCodecContext->width,
                                            videoCodecContext->height, 1);
    uint8_t *buffer = (uint8_t *)av_malloc(numBytes);
    av_image_fill_arrays(rgbFrame->data, rgbFrame->linesize, buffer, AV_PIX_FMT_RGB24,
                         videoCodecContext->width, videoCodecContext->height, 1);

    sws_scale(swsContext, frame->data, frame->linesize, 0, videoCodecContext->height,
              rgbFrame->data, rgbFrame->linesize);

    QImage img(rgbFrame->data[0], videoCodecContext->width, videoCodecContext->height,
               QImage::Format_RGB888);
    videoLabel->setPixmap(QPixmap::fromImage(img));

    av_free(buffer);
    av_frame_free(&rgbFrame);
}

#include "main.moc"


代码说明
FFmpeg 初始化和解码:通过 initializeFFmpeg 初始化视频和音频流,设置解码器以及音视频重采样(用于音频)。
音频输出:通过 QAudioOutput 将解码后的音频数据写入到音频输出设备中。
视频显示:解码后的视频帧通过 sws_scale 转换为 RGB24 格式并在 QLabel 中显示。
播放控制:实现了播放和暂停控制,同时可以通过定时器 (`

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

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

相关文章

CyclicBarrier使用详解及遇到的坑

上一篇文章讲的是关于是使用CountDownLatch实现生成年底报告遇到的问题&#xff0c;这个计数器和CyclicBarrier也有类似功能&#xff0c;但是应用场景不同。 一、应用场景 CountDownLatch&#xff1a; 有ABCD四个任务&#xff0c;ABC是并行执行,等ABC三个任务都执行完…

Java-I/O框架14:Properties集合及使用

视频链接&#xff1a;16.32 Properties使用&#xff08;2&#xff09;_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1Tz4y1X7H7?spm_id_from333.788.player.switch&vd_sourceb5775c3a4ea16a5306db9c7c1c1486b5&p32 1.Properties集合 特性&#xff1a; 存储…

Windows下mysql数据库备份策略

Windows下mysql的增量备份和全量备份&#xff0c;并利用schtasks设置定时任务执行bat脚本。 一、备份要求 序号 备份类型 备份频次 备份时间 1 增量备份 每周一-每周六各一次 18:00:00 2 全量备份 每周日一次 18:00:00 二、备份方法 2.1增量备份 2.1.1准备工作…

架构师备考-概念背诵(软件工程)

软件工程 软件开发生命周期: 软件定义时期:包括可行性研究和详细需求分析过程,任务是确定软件开发工程必须完成的总目标,具体可分成问题定义、可行性研究、需求分析等。软件开发时期:就是软件的设计与实现,可分成概要设计、详细设计、编码、测试等。软件运行和维护:就是…

【Linux】Linux入门实操——vim、目录结构、远程登录、重启注销

一、Linux 概述 1. 应用领域 服务器领域 linux在服务器领域是最强的&#xff0c;因为它免费、开源、稳定。 嵌入式领域 它的内核最小可以达到几百KB, 可根据需求对软件剪裁&#xff0c;近些年在嵌入式领域得到了很大的应用。 主要应用&#xff1a;机顶盒、数字电视、网络…

【Java项目】基于SpringBoot的【生鲜交易系统】

技术简介&#xff1a; 系统软件架构选择B/S模式、java技术和MySQL数据库等&#xff0c;总体功能模块运用自顶向下的分层思想。 系统简介&#xff1a; 考虑到实际生活中在生鲜交易方面的需要以及对该系统认真的分析,将系统权限按管理员&#xff0c;用户这两类涉及用户划分。 (…

AI Weekly『11月4-10日』: Anthropic发布Claude 3.5 Haiku,腾讯开源混元-Large模型!

大家好&#xff0c;我是木易&#xff0c;一个持续关注AI领域的互联网技术产品经理&#xff0c;国内Top2本科&#xff0c;美国Top10 CS研究生&#xff0c;MBA。我坚信AI是普通人变强的“外挂”&#xff0c;专注于分享AI全维度知识&#xff0c;包括但不限于AI科普&#xff0c;AI工…

贪心算法day3(最长递增序列问题)

目录 1.最长递增三元子序列 2.最长连续递增序列 1.最长递增三元子序列 题目链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a;我们只需要设置两个数进行比较就好。设a为nums[0]&#xff0c;b 为一个无穷大的数&#xff0c;只要有比a小的数字就赋值…

vue实现图片无限滚动播放

本人vue新手菜鸡&#xff0c;文章为自己在项目中遇到问题的记录&#xff0c;如有不足还请大佬指正 文章目录 实现效果代码展示总结 因为刚接触vue&#xff0c;本想着看看能不能用一些element的组件实现图片的轮播效果&#xff0c;尝试使用过element-UI里的走马灯Carouse&#x…

[ 内网渗透实战篇-2 ] 父域子域架构的搭建与安装域环境判断域控定位组策略域森林架构配置信任关系

&#x1f36c; 博主介绍 &#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 _PowerShell &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【数据通信】 【通讯安全】 【web安全】【面试分析】 &#x1f389;点赞➕评论➕收藏 养成习…

Follow软件的使用入门教程

开篇 看到很多兄弟还不知道怎么用这个当下爆火的浏览器&#xff01;在这里简单给需要入门的小伙伴一些建议&#xff1a; 介绍 简单解释一下&#xff0c;RSS 意思是简易信息聚合&#xff0c;用户可以通过 RSS 阅读器或聚合工具自主订阅并浏览各个平台的内容源&#xff0c;不用…

esp32学习:用虫洞ESP32开发板,快速实现无线图传

我们的虫洞ESP32-S3-EYE开发板&#xff0c;能够完美运行esp who AI代码&#xff0c;所以实现无线图传那是非常容易的&#xff0c;我们先看看esp who代码库中examples目录&#xff1a; 里面有比较多的web例程&#xff0c;在这些例程下&#xff0c;稍作修改&#xff0c;就可以快速…

最新三维视觉下的扩散模型综述——Diffusion Models in 3D Vision: A Survey

目录 摘要 一、引言 二、扩散模型简介 A.扩散模型的介绍 B.扩散模型的数学基础 C.扩散模型的变体 D.三维视觉中的生成过程 三、三维视觉基础 A.三维表示 B.三维视觉中的深度学习方法 C.3D视觉中的挑战 四、三维扩散生成任务 A.无条件生成 B.图像到三维 C.文本到…

JavaSE:运算符 (学习笔记)

目录 一&#xff0c;算术运算符 【1】 共同点&#xff1a; 【2】 不同点&#xff1a; 二&#xff0c;关系运算符 三&#xff0c;逻辑运算符 2&#xff0c;&和&&的区别和联系 { |和||的区别和联系 }---两题类似 四&#xff0c;赋值运算符 五&#xff0c;拓展…

strtok函数详解

strtok函数 strtok 函数是一个字符串分割函数&#xff0c;用于将字符串分割成一系列的标记。这个函数通过一组分隔符字符来确定标记的边界&#xff0c;每次调用都会返回字符串中的下一个标记&#xff0c;并且将原始字符串中的分隔符替换为空字符‘\0’&#xff0c;从而实际上是…

题目练习之二叉树那些事儿(续集)

♥♥♥~~~~~~欢迎光临知星小度博客空间~~~~~~♥♥♥ ♥♥♥零星地变得优秀~也能拼凑出星河~♥♥♥ ♥♥♥我们一起努力成为更好的自己~♥♥♥ ♥♥♥如果这一篇博客对你有帮助~别忘了点赞分享哦~♥♥♥ ♥♥♥如果有什么问题可以评论区留言或者私信我哦~♥♥♥ ✨✨✨✨✨✨个人…

【入门篇】确定字符串是否包含唯一字符——多语言版本

题目跳转&#xff1a;确定字符串是否包含唯一字符 题目解析 这个问题要求我们判断一个字符串中的字符是否唯一&#xff08;忽略字母的大小写&#xff09;&#xff0c;并输出相应的结果。如果字符串中所有的字符都是唯一的&#xff0c;输出 YES&#xff1b;否则&#xff0c;输…

ConcurrentModificationException:检测到并发修改完美解决方法

&#x1f6a6; ConcurrentModificationException&#xff1a;检测到并发修改完美解决方法 &#x1f4a1; &#x1f6a6; ConcurrentModificationException&#xff1a;检测到并发修改完美解决方法 &#x1f4a1;摘要1. 什么是ConcurrentModificationException&#xff1f;&…

并查集 How many tables(hdu 1213) How many answers are wrong(hdu 3038)

目录 前言 并查集 并查集的初始化 并查集的合并 并查集合并的优化&#xff0c;路径压缩 How many tables(hdu 1213) 问题描述 输入 输出 问题分析 代码 带权并查集 How many answers are wrong(hdu 3038) 问题描述 输入 输出 问题分析 代码 前言 感觉并查集总共有两个应…

JavaScript day01 笔记

一、引入方式 JavaScript 程序不能独立运行&#xff0c;它需要被嵌入 HTML 中&#xff0c;然后浏览器才能执行 JavaScript 代码。通过 script 标签将 JavaScript 代码引入到 HTML 中 1️⃣内部 通过 script 标签包裹 JavaScript 代码&#xff08;一般就写在</script>的…