基于qt vs下的视频播放

**在 VS 2022 和 Qt 环境下利用 FFmpeg 实现一个基础视频播放器,需要完成以下几个步骤:

准备工作:

下载并配置 FFmpeg。确保 FFmpeg 的库和头文件可供 VS 2022 项目使用。

配置 Qt 项目,并导入 FFmpeg 库。

项目结构:

创建一个 Qt 项目,可以选择 Qt Widgets Application 类型,用于构建基本的 GUI 界面。

设计 GUI 界面:

添加视频显示区域(可以使用 QLabel 来显示视频帧)。

添加播放控制按钮(如播放、暂停按钮)。

添加进度条(QSlider)和全屏切换按钮。

初始化 FFmpeg:

使用 FFmpeg 的 API,初始化解码器并打开视频文件。

从视频流中提取音频和视频流。

视频解码和显示:

使用 FFmpeg 解码视频帧,然后将解码后的 YUV 数据转换为 RGB 格式,以便使用 Qt 显示。

将 RGB 格式的视频帧显示在 QLabel 上,可以使用 QImage 和 QPixmap 来显示帧图像。

音频解码和播放:

解码音频流并播放,可以使用 SDL 库来处理音频播放。

需要确保音频和视频的同步,通常通过音视频时间戳(PTS)来同步。

播放控制:

为播放、暂停、全屏等功能创建槽函数,控制视频解码和显示。

使用 QSlider 控制进度条的拖动,并同步视频播放进度。

在开始之前,确保已经安装并配置了以下库:

FFmpeg:用于视频和音频解码。

SDL2:用于音频播放。

代码示例如下:

cpp 复制代码
#include <QMainWindow>
#include <QTimer>
#include <QSlider>
#include <QLabel>
#include <QPushButton>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QWidget>
#include <QImage>
#include <QPixmap>
#include <QFileDialog>

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

// SDL2 头文件
#include <SDL2/SDL.h>

class VideoPlayer : public QMainWindow {
    Q_OBJECT

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

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

private:
    void initializeFFmpeg();
    void initializeSDL();
    void decodeVideo();
    void decodeAudio();
    void displayFrame(AVFrame *frame);
    static void audioCallback(void *userdata, Uint8 *stream, int len);

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

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

    // SDL相关
    SDL_AudioSpec wantedSpec, obtainedSpec;
    uint8_t *audioBuffer;
    int audioBufferSize;
    int audioBufferIndex;

    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);

    // 初始化 FFmpeg 和 SDL
    initializeFFmpeg();
    initializeSDL();

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

void VideoPlayer::initializeFFmpeg() {
    av_register_all();
    formatContext = avformat_alloc_context();
    // 打开视频文件并找到流
    avformat_open_input(&formatContext, "path/to/video.mp4", 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::initializeSDL() {
    SDL_Init(SDL_INIT_AUDIO);

    wantedSpec.freq = audioCodecContext->sample_rate;
    wantedSpec.format = AUDIO_S16SYS;
    wantedSpec.channels = 2;
    wantedSpec.silence = 0;
    wantedSpec.samples = 1024;
    wantedSpec.callback = audioCallback;
    wantedSpec.userdata = this;

    SDL_OpenAudio(&wantedSpec, &obtainedSpec);
}

void VideoPlayer::play() {
    isPlaying = true;
    SDL_PauseAudio(0); // 开始播放音频
    timer->start(30);  // 30ms刷新一次
}

void VideoPlayer::pause() {
    isPlaying = false;
    SDL_PauseAudio(1); // 暂停音频
    timer->stop();
}

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

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);
        } else if (packet.stream_index == audioStreamIndex) {
            decodeAudio();
        }
        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 dataSize = av_samples_get_buffer_size(nullptr, obtainedSpec.channels,
                                                          frame->nb_samples, AV_SAMPLE_FMT_S16, 1);
                audioBuffer = (uint8_t *)av_malloc(dataSize);
                swr_convert(swrContext, &audioBuffer, frame->nb_samples,
                            (const uint8_t **)frame->data, frame->nb_samples);
                audioBufferSize = dataSize;
                audioBufferIndex = 0;
            }
            av_frame_free(&frame);
        }
        av_packet_unref(&packet);
    }
}

void VideoPlayer::displayFrame(AVFrame *frame) {
    // 转换YUV为RGB
    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);

    // 将RGB帧转化为QImage并显示
    QImage img(rgbFrame->data[0], videoCodecContext->width, videoCodecContext->height,
               QImage::Format_RGB888);
    videoLabel->setPixmap(QPixmap::fromImage(img));

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

void VideoPlayer::audioCallback(void *userdata, Uint8 *stream, int len) {
    VideoPlayer *player = (VideoPlayer *)userdata;
    if (player->audioBufferSize - player->audioBufferIndex <= 0) {
        return;
    }

    len = (len > player->audioBufferSize - player->audioBufferIndex) ?
          player->audioBufferSize - player->audioBufferIndex : len;
    SDL_memcpy(stream, player->audioBuffer + player->audioBufferIndex, len);
    player->audioBufferIndex += len;
}

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

#include "main.moc"

说明

视频解码与显示:通过 sws_scale 函数将解码的 YUV 视频帧转换为 RGB 格式并显示在 QLabel 上。

音频解码与播放:使用 SDL_OpenAudio 打开音频设备,通过 audioCallback 回调函数播放解码后的音频数据。

音视频同步:基本通过 timer 和 audioCallback 函数保持音视频同步。

相关推荐
RTC实战笔记2 天前
实时互动数字人怎么做,才不是一个只会说话的视频?
音视频·数字人·rtc·数字人接入
用户805533698033 天前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
xcyxiner3 天前
DicomViewer (vcpkg Windows和ubuntu编译)7
qt
Quz8 天前
QML Hello World 入门示例
qt
xcyxiner11 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner12 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner12 天前
DicomViewer (添加模型类)3
qt
xcyxiner13 天前
DicomViewer (目录调整) 2
qt
xcyxiner13 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
RTC实战笔记14 天前
Android 实时音视频接入教程:媒体补充增强信息(SEI)
音视频·媒体·rtc