在这个示例中,我们将使用 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 中显示。
播放控制:实现了播放和暂停控制,同时可以通过定时器 (`