当前位置: 首页 > article >正文

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

在这个示例中,我们将使用 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.kler.cn/a/381741.html

相关文章:

  • HTML 基础标签——结构化标签<html>、<head>、<body>
  • 就业市场变革:AI时代,我们将如何评估人才?
  • Rust 力扣 - 1461. 检查一个字符串是否包含所有长度为 K 的二进制子串
  • 机器人零位、工作空间、坐标系及其变换,以UR5e机器人为例
  • 戴尔电脑 Bios 如何进入?Dell Bios 进入 Bios 快捷键是什么?
  • qt QWizard详解
  • 【C++的vector、list、stack、queue用法简单介绍】
  • Oracle OCP认证考试考点详解082系列09
  • 使用Centos搭建Rocket.Chat教程
  • 融合智能化和信息化的技术的智慧地产开源了。
  • shodan(五)连接Mongodb数据库Jenkinsorg、net、查看waf命令
  • HTMLCSS:3D 旋转卡片的炫酷动画
  • 传统运维往哪个方向发展比较好?这几个运维岗位趁早转型!
  • Hive操作库、操作表及数据仓库的简单介绍
  • 《手写Spring渐进式源码实践》实践笔记(第十六章 三级缓存解决循环依赖)
  • 认识微服务,微服务的拆分,服务治理(nacos注册中心,远程调用)
  • 纵然千万数据流逝,唯独vector长存
  • 解析 MySQL 数据库容量统计、存储限制与优化技巧
  • 【汇编语言】[BX]和loop指令(一)—— 初识[BX]和loop指令
  • 论文阅读- --DeepI2P:通过深度分类进行图像到点云配准
  • 软件测试基础:单元测试与集成测试
  • Flutter 鸿蒙next版本:自定义对话框与表单验证的动态反馈与错误处理
  • 鸿蒙进阶-List组件
  • STL 迭代器iteratior 详解
  • 面试高频问题:C/C++编译时内存五个分区
  • 基于springboot+vue实现的农产品物流系统