**在 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:用于音频播放。
代码示例如下:

#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 函数保持音视频同步。

11-07 19:16