副标题:基于QtAV源码剖析 + 200+行完整项目代码,手把手实现一个支持多格式、硬解码与自定义渲染的工业级播放器
QtAV是开源社区最成熟的Qt音视频处理库之一,支持FFmpeg解码、VAAPI/DXVA2/VideoToolbox硬件加速、内置GPU滤镜管道,以及灵活的自定义渲染接口。它的设计哲学与Qt的信号槽和元对象系统高度融合,使得C++开发者无需深入理解FFmpeg API细节,也能构建功能完整的播放器。
本文将从QtAV的架构设计入手,剖析其解码管线、渲染管道、音频输出三个核心模块的源码实现,并提供可直接编译运行的完整播放器项目代码。
一、QtAV整体架构
1.1 模块层次
QtAV分为四个核心层:
┌─────────────────────────────────────────────────────┐
│ 应用层 (Qt Quick/C++ UI) │
├─────────────────────────────────────────────────────┤
│ AVPlayer ←→ AudioOutput ←→ VideoRenderer │
│ │ │
│ ┌──────┴──────────────────────────────────┐ │
│ │ 解码层 (Decoder) │ │
│ │ FFmpegDecoder → Software │ │
│ │ VAAPIDecoder → Linux VA-API │ │
│ │ DXVA2Decoder → Windows DirectX │ │
│ │ VTBDecoder → macOS VideoToolbox │ │
│ └────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────┤
│ FFmpeg (libavformat/libavcodec) │
├─────────────────────────────────────────────────────┤
│ 系统层 (操作系统 + 硬件) │
└─────────────────────────────────────────────────────┘
1.2 核心类层次
QObject
└── AVPlayer // 主播放器:播放控制、时间管理、信号发射
├── AudioOutput // 音频输出:OpenAL/SDL/QTAudioOutput
└── VideoRenderer // 视频渲染:基类,多种实现
├── VideoRendererGL // OpenGL纹理渲染(推荐)
├── VideoRendererSW // 软件渲染(QImage/QPainter)
└── VideoRendererQt // Qt Widget渲染
二、AVPlayer主播放器类
2.1 核心接口
AVPlayer是QtAV的入口类,封装了所有播放控制逻辑:
cpp
// QtAV核心头文件
#include <QtAV/AVPlayer.h>
#include <QtAV/VideoRenderer.h>
#include <QtAV/AudioOutput.h>
AVPlayer player;
player.setRenderer(videoWidget); // 设置渲染目标
player.setAsyncLoad(true); // 异步加载媒体,避免卡UI
player.setBufferMode(BufferSeconds); // 缓冲模式
player.setBufferValue(5.0); // 缓冲5秒
2.2 播放控制信号
AVPlayer继承自QObject,通过信号槽与UI层通信:
cpp
// QtAV/AVPlayer.h 关键信号
signals:
void started(); // 播放开始
void stopped(); // 播放停止
void paused(bool); // 暂停状态变更
void positionChanged(qint64); // 当前播放位置(ms)
void durationChanged(qint64); // 总时长(ms)
void mediaStatusChanged(MediaStatus); // 媒体状态
void error(const QtAV::AVError&); // 错误通知
void bufferProgressChanged(qreal); // 缓冲进度
void speedChanged(qreal); // 播放速度
2.3 时间管理机制
AVPlayer内部维护一个精确的时间轴系统,这是实现音视频同步的基础:
cpp
// QtAV/AVPlayer.cpp 简化版
void AVPlayer::seek(qint64 ms)
{
QMutexLocker locker(&seek_mutex);
m_seek_target = ms;
// 设置标志位,下一帧解码时应用seek
m_seek_flag = true;
// 刷新解码器缓冲
if (m_packet_buffer)
m_packet_buffer->flush();
}
qint64 AVPlayer::position() const
{
// 返回当前播放位置(毫秒)
// 由音频时钟驱动,保证音视频同步
return qint64(m_clock->value() * 1000.0);
}
关键设计:AVPlayer使用"音频时钟优先"的同步策略------以音频时间戳为主时钟,视频和字幕与之同步。这比"视频时钟优先"能提供更平滑的播放体验,避免音画漂移。
三、解码器管线
3.1 解码器工厂与策略模式
QtAV使用工厂模式管理多种解码器:
cpp
// QtAV/Decoder.h
class Decoder : public QObject
{
Q_OBJECT
public:
static QStringList registeredDecoderTypes(); // 获取所有注册解码器
static Decoder* create(const QString& type); // 创建指定类型解码器
static bool registerDecoder(const QString& type, DecoderCreateFunc factory);
// 子类实现
virtual bool open(); // 打开解码器(分配硬件资源)
virtual void close(); // 关闭
virtual bool decode(const QByteArray& encoded, QByteArray& decoded) = 0;
};
自动选择最佳解码器的策略:
cpp
// 自动选择策略:优先硬解
QString AVPlayer::autoSelectDecoder()
{
// 1. 检查系统支持
if (VAAPIDecoder::isSupported())
return "VAAPI";
if (DXVA2Decoder::isSupported())
return "DXVA2";
if (VTBDecoder::isSupported())
return "VideoToolbox";
// 2. 降级到FFmpeg软解
return "FFmpeg";
}
3.2 帧管理:VideoFrame
解码后的视频帧被封装为VideoFrame:
cpp
// QtAV/VideoFrame.h
class VideoFrame
{
public:
VideoFrame();
VideoFrame(const QSize& size, int fourcc); // 四字符编码标识格式
VideoFrame(const QByteArray& data, const QSize& size, int fourcc);
// 关键方法
void* bits() const; // 像素数据指针
QSize size() const; // 帧尺寸
int pixelFormat() const; // 像素格式(YUV420P/RGB24/NV12...)
qint64 timestamp() const; // 时间戳(微秒)
// 格式转换
VideoFrame to(VideoFormat::PixelFormat fmt) const;
// GPU上传
void uploadToTexture(GLuint texture) const;
};
VideoFrame支持多种像素格式的自动转换,这是QtAV跨平台兼容性的基础:
输入格式(由解码器决定)→ VideoFrame内部存储 → 输出格式(由渲染器决定)
↓ ↓ ↓
YUV420P/NV12 统一VideoFrame表示 RGB32/OpenGL纹理
四、渲染管道:VideoRenderer体系
4.1 渲染器基类
cpp
// QtAV/VideoRenderer.h
class VideoRenderer : public QObject
{
Q_OBJECT
Q_PROPERTY(QRectF sourceRect READ sourceRect WRITE setSourceRect)
Q_PROPERTY(QRectF targetRect READ targetRect WRITE setTargetRect)
Q_PROPERTY(Qt::AspectRatioMode aspectRatioMode READ aspectRatioMode)
public:
// 核心方法
virtual bool isSupported(VideoFormat::PixelFormat pixfmt) const = 0;
virtual void process(VideoFrame& frame) = 0; // 处理每一帧
virtual void drawFrame() = 0; // 绘制到目标
signals:
void frameChanged(); // 帧更新信号,可用于通知统计
};
4.2 OpenGL渲染器实现(推荐)
QtAV的OpenGL渲染器是最强大的实现,支持GPU滤镜链:
cpp
// QtAV/VideoRendererGL.h 关键接口
class VideoRendererGL : public VideoRenderer
{
Q_OBJECT
public:
VideoRendererGL(QWidget* parent = nullptr);
~VideoRendererGL() override;
// 设置GPU滤镜链
void addFilter(VideoFilter* filter);
void removeFilter(VideoFilter* filter);
// OpenGL上下文管理
QOpenGLContext* openglContext() const;
void setOpenGLContext(QOpenGLContext* ctx);
protected:
// 子类重写
void drawFrame() override;
void setupBuffer(); // 创建FBO
void uploadFrame(const VideoFrame& frame); // CPU→GPU上传
private:
// 滤镜管道
QList<VideoFilter*> m_filters;
// OpenGL资源
GLuint m_textureIds[3]; // 支持最多3个纹理(Y/U/V或R/G/B)
GLuint m_fbo; // 帧缓冲对象
QOpenGLFunctions* m_gl; // OpenGL函数指针
};
drawFrame的实现流程(核心渲染循环):
cpp
void VideoRendererGL::drawFrame()
{
m_gl->glClear(GL_COLOR_BUFFER_BIT);
m_gl->glClearColor(0.0, 0.0, 0.0, 1.0); // 黑色背景
// 1. 绑定YUV纹理并上传数据
uploadTextures(m_currentFrame);
// 2. 依次应用滤镜链(每个滤镜读写FBO)
QOpenGLFramebufferObject *srcFBO = m_renderFBO;
for (VideoFilter* filter : m_filters) {
filter->setInPlace(false);
filter->process(srcFBO, dstFBO);
std::swap(srcFBO, dstFBO);
}
// 3. 最终渲染到屏幕
renderToScreen(srcFBO->texture());
}
五、音频输出
5.1 音频输出架构
cpp
// QtAV/AudioOutput.h
class AudioOutput : public QObject
{
Q_OBJECT
Q_PROPERTY(qreal volume READ volume WRITE setVolume)
Q_PROPERTY(bool muted READ isMuted WRITE setMuted)
public:
// 配置
bool open(); // 打开音频设备
void close(); // 关闭
// 播放控制
int write(const QByteArray& data); // 写入音频数据
void pause(); // 暂停
void resume(); // 恢复
// 时钟接口
qint64 queuedBytes() const; // 缓冲队列字节数
qint64 currentAudioPts() const; // 当前音频pts
};
5.2 音频时钟与音视频同步
音频输出维护一个独立时钟,但这个时钟被AVPlayer用作主时钟源:
cpp
// QtAV/AudioOutput.cpp
qint64 AudioOutput::currentAudioPts() const
{
// 根据当前播放位置计算pts
// = 音频缓冲队列总时长 - 还未播放的缓冲时长
const qint64 buffered_us = bytesToDuration(queuedBytes());
const qint64 current_us = durationOfBytes(writtenBytes() - queuedBytes());
return current_us;
}
AVPlayer使用这个时钟同步视频:
cpp
// QtAV/AVPlayer.cpp 视频同步逻辑
void AVPlayer::videoThreadLoop()
{
while (!m_stop_flag) {
VideoFrame frame = m_decoder->decode();
if (!frame.isValid()) continue;
// 获取主时钟(音频时钟)
const double clock_ms = m_audio_clock->value() * 1000.0;
const double frame_ms = frame.timestamp() / 1000.0;
// 计算延迟
double delay = frame_ms - clock_ms;
if (delay > 40) {
// 帧太早,等一下
QThread::msleep(int(delay - 40));
} else if (delay < -40) {
// 帧太晚,跳帧
continue;
}
// -40ms~+40ms范围内正常显示
m_renderer->process(frame);
m_renderer->drawFrame();
}
}
六、实战:构建完整播放器
6.1 项目结构
player/
├── player.pro # Qt项目文件
├── main.cpp
├── mainwindow.h/cpp # 主窗口
├── playercontrols.h/cpp # 播放控制面板
├── videowidget.h/cpp # 视频渲染控件
└── audiothread.h/cpp # 音频线程管理
6.2 项目文件
qmake
# player.pro
QT += core gui avwidgets
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
QTAV_ROOT = /path/to/QtAV # QtAV安装路径
INCLUDEPATH += $${QTAV_ROOT}/include
LIBS += -L$${QTAV_ROOT}/lib -lQtAV -lQtAVWidgets
6.3 主窗口实现
cpp
// mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QtAV/AVPlayer.h>
#include <QtAV/VideoRenderer.h>
#include <QtAV/AudioOutput.h>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class VideoWidget;
class PlayerControls;
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow() override;
private slots:
void openFile();
void onPlayerStarted();
void onPlayerStopped();
void onPositionChanged(qint64 ms);
void onDurationChanged(qint64 ms);
void onError(const QString &msg);
private:
void setupConnections();
Ui::MainWindow *ui;
VideoWidget *m_videoWidget;
PlayerControls *m_controls;
QtAV::AVPlayer *m_player;
QtAV::AudioOutput *m_audio;
};
#endif // MAINWINDOW_H
cpp
// mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "videowidget.h"
#include "playercontrols.h"
#include <QFileDialog>
#include <QMessageBox>
#include <QVBoxLayout>
#include <QSlider>
#include <QLabel>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
, m_controls(new PlayerControls(this))
{
ui->setupUi(this);
// 初始化AVPlayer
m_player = new QtAV::AVPlayer(this);
m_player->setFrameRate(0); // 自动检测帧率
// 初始化音频输出
m_audio = new QtAV::AudioOutput(this);
m_player->setAudioOutput(m_audio);
// 初始化视频渲染控件
m_videoWidget = new VideoWidget(this);
setCentralWidget(m_videoWidget);
m_player->setRenderer(m_videoWidget->renderer());
// 工具栏
auto toolbar = addToolBar("Controls");
toolbar->addWidget(m_controls);
// 信号连接
setupConnections();
setWindowTitle("QtAV Player");
resize(960, 640);
}
void MainWindow::setupConnections()
{
// 播放控制
connect(m_controls, &PlayerControls::playClicked, this, [this] {
if (m_player->isPlaying()) {
m_player->pause(!m_player->isPaused());
} else {
m_player->play();
}
});
connect(m_controls, &PlayerControls::stopClicked, this, [this] {
m_player->stop();
});
connect(m_controls, &PlayerControls::openClicked, this, &MainWindow::openFile);
// 播放位置
connect(m_controls, &PlayerControls::seekRequested, this, [this](qint64 ms) {
m_player->seek(ms);
});
// 速度控制
connect(m_controls, &PlayerControls::speedChanged, this, [this](qreal speed) {
m_player->setSpeed(speed);
});
// 音量
connect(m_controls, &PlayerControls::volumeChanged, this, [this](qreal vol) {
m_player->audio()->setVolume(vol);
});
// 播放器信号
connect(m_player, &QtAV::AVPlayer::started, this, &MainWindow::onPlayerStarted);
connect(m_player, &QtAV::AVPlayer::stopped, this, &MainWindow::onPlayerStopped);
connect(m_player, &QtAV::AVPlayer::positionChanged, this, &MainWindow::onPositionChanged);
connect(m_player, &QtAV::AVPlayer::durationChanged, this, &MainWindow::onDurationChanged);
// 错误处理
connect(m_player, &QtAV::AVPlayer::error, this, [this](QtAV::AVError& err) {
onError(err.errorString());
});
}
void MainWindow::openFile()
{
QString file = QFileDialog::getOpenFileName(
this,
"打开媒体文件",
QString(),
"视频文件 (*.mp4 *.avi *.mkv *.mov *.flv *.wmv);;"
"音频文件 (*.mp3 *.wav *.flac *.aac *.ogg);;"
"所有文件 (*.*)"
);
if (!file.isEmpty()) {
m_player->setFile(file);
m_player->play();
}
}
void MainWindow::onPlayerStarted()
{
m_controls->setPlayingState(true);
setWindowTitle(QString("QtAV Player - %1").arg(m_player->file()));
}
void MainWindow::onPlayerStopped()
{
m_controls->setPlayingState(false);
m_controls->setPosition(0);
m_controls->setDuration(0);
setWindowTitle("QtAV Player");
}
void MainWindow::onPositionChanged(qint64 ms)
{
m_controls->setPosition(ms);
}
void MainWindow::onDurationChanged(qint64 ms)
{
m_controls->setDuration(ms);
}
void MainWindow::onError(const QString &msg)
{
QMessageBox::critical(this, "播放错误", msg);
}
6.4 视频渲染控件
cpp
// videowidget.h
#ifndef VIDEOWIDGET_H
#define VIDEOWIDGET_H
#include <QWidget>
#include <QtAV/VideoRenderer.h>
class VideoWidget : public QWidget
{
Q_OBJECT
public:
explicit VideoWidget(QWidget *parent = nullptr);
~VideoWidget() override;
QtAV::VideoRenderer *renderer() const { return m_renderer; }
protected:
// 重写paintEvent,用QPainter绑定OpenGL渲染
void paintEvent(QPaintEvent *event) override;
QPaintEngine *paintEngine() const override { return nullptr; }
// 响应窗口resize,调整渲染区域
void resizeEvent(QResizeEvent *event) override;
private:
QtAV::VideoRenderer *m_renderer;
};
#endif // VIDEOWIDGET_H
cpp
// videowidget.cpp
#include "videowidget.h"
#include <QtAV/VideoOutput.h>
#include <QResizeEvent>
VideoWidget::VideoWidget(QWidget *parent)
: QWidget(parent)
{
// 使用OpenGL渲染器(推荐:支持GPU滤镜和硬件加速)
m_renderer = QtAV::VideoRendererGL::create();
if (!m_renderer) {
// 降级到软件渲染
m_renderer = QtAV::VideoRenderer::create(QtAV::VideoRendererSW_Id);
}
m_renderer->widget()->setParent(this);
// 设置渲染器的源和目标区域
m_renderer->setOutAspectRatioMode(Qt::KeepAspectRatio);
}
VideoWidget::~VideoWidget()
{
delete m_renderer;
}
void VideoWidget::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
if (m_renderer) {
// 通知渲染器更新目标区域
m_renderer->setOutRect(rect());
}
}
void VideoWidget::paintEvent(QPaintEvent *event)
{
// QtAV的VideoRenderer会自己处理OpenGL绑定
// 这里只需要让Qt不要用软件渲染重绘
QWidget::paintEvent(event);
}
6.5 播放控制面板
cpp
// playercontrols.h
#ifndef PLAYERCONTROLS_H
#define PLAYERCONTROLS_H
#include <QWidget>
#include <QtAV/AVPlayer.h>
class QPushButton;
class QSlider;
class QLabel;
class PlayerControls : public QWidget
{
Q_OBJECT
public:
explicit PlayerControls(QWidget *parent = nullptr);
void setPlayingState(bool playing);
void setPosition(qint64 ms);
void setDuration(qint64 ms);
signals:
void playClicked();
void stopClicked();
void openClicked();
void seekRequested(qint64 ms);
void speedChanged(qreal speed);
void volumeChanged(qreal volume);
private:
QPushButton *m_playBtn;
QPushButton *m_stopBtn;
QPushButton *m_openBtn;
QSlider *m_positionSlider;
QSlider *m_volumeSlider;
QLabel *m_positionLabel;
QLabel *m_durationLabel;
bool m_seeking = false;
};
#endif // PLAYERCONTROLS_H
cpp
// playercontrols.cpp
#include "playercontrols.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QPushButton>
#include <QSlider>
#include <QLabel>
#include <QStyle>
PlayerControls::PlayerControls(QWidget *parent)
: QWidget(parent)
{
// 按钮
m_openBtn = new QPushButton("打开");
m_playBtn = new QPushButton;
m_playBtn->setIcon(style()->standardIcon(QStyle::SP_MediaPlay));
m_stopBtn = new QPushButton;
m_stopBtn->setIcon(style()->standardIcon(QStyle::SP_MediaStop));
// 进度滑块
m_positionSlider = new QSlider(Qt::Horizontal);
m_positionSlider->setRange(0, 1000);
m_positionLabel = new QLabel("00:00");
m_durationLabel = new QLabel("00:00");
// 音量
m_volumeSlider = new QSlider(Qt::Horizontal);
m_volumeSlider->setRange(0, 100);
m_volumeSlider->setValue(80);
// 布局
auto *mainLayout = new QVBoxLayout(this);
auto *controlLayout = new QHBoxLayout;
controlLayout->addWidget(m_openBtn);
controlLayout->addWidget(m_playBtn);
controlLayout->addWidget(m_stopBtn);
controlLayout->addWidget(m_positionLabel);
controlLayout->addWidget(m_positionSlider);
controlLayout->addWidget(m_durationLabel);
controlLayout->addWidget(new QLabel("音量:"));
controlLayout->addWidget(m_volumeSlider);
mainLayout->addLayout(controlLayout);
// 信号连接
connect(m_playBtn, &QPushButton::clicked, this, &PlayerControls::playClicked);
connect(m_stopBtn, &QPushButton::clicked, this, &PlayerControls::stopClicked);
connect(m_openBtn, &QPushButton::clicked, this, &PlayerControls::openClicked);
connect(m_positionSlider, &QSlider::sliderPressed, this, [this] { m_seeking = true; });
connect(m_positionSlider, &QSlider::sliderReleased, this, [this] {
m_seeking = false;
qint64 ms = m_positionSlider->value();
emit seekRequested(ms);
});
connect(m_positionSlider, &QSlider::valueChanged, this, [this](int value) {
if (!m_seeking) return;
emit seekRequested(value);
});
connect(m_volumeSlider, &QSlider::valueChanged, this, [this](int value) {
emit volumeChanged(value / 100.0);
});
}
void PlayerControls::setPlayingState(bool playing)
{
m_playBtn->setIcon(style()->standardIcon(
playing ? QStyle::SP_MediaPause : QStyle::SP_MediaPlay));
}
void PlayerControls::setPosition(qint64 ms)
{
if (m_seeking) return;
m_positionSlider->setValue(int(ms));
m_positionLabel->setText(QTime(0,0).addMSecs(int(ms)).toString("mm:ss"));
}
void PlayerControls::setDuration(qint64 ms)
{
m_positionSlider->setMaximum(int(ms));
m_durationLabel->setText(QTime(0,0).addMSecs(int(ms)).toString("mm:ss"));
}
七、高级特性:GPU滤镜链
QtAV支持在渲染管道中插入自定义GPU滤镜:
cpp
// 自定义亮度调节滤镜
class BrightnessFilter : public QtAV::VideoFilter
{
Q_OBJECT
Q_PROPERTY(qreal brightness READ brightness WRITE setBrightness)
public:
BrightnessFilter(QObject *parent = nullptr)
: QtAV::VideoFilter(parent), m_brightness(0.0)
{}
qreal brightness() const { return m_brightness; }
void setBrightness(qreal v) { m_brightness = v; }
protected:
void process() override {
QVideoFrame src = currentFrame();
if (!src.isValid()) return;
QVideoFrame dst;
// 应用亮度调节(使用GLSL shader)
processGLSL(src, dst, [this](const QRectF& target) {
return QString(
"uniform sampler2D texture;"
"uniform float brightness;"
"void main() {"
" vec4 c = texture2D(texture, targetCoord);"
" gl_FragColor = vec4(c.rgb + brightness, c.a);"
"}"
);
});
setNextFrame(dst);
}
private:
qreal m_brightness;
};
// 使用滤镜
QtAV::VideoRendererGL *glRenderer = static_cast<QtAV::VideoRendererGL*>(renderer());
BrightnessFilter *bf = new BrightnessFilter(this);
bf->setBrightness(0.2f);
glRenderer->addFilter(bf);
八、性能调优
8.1 硬件加速配置
cpp
// 启用硬件解码
m_player->setVideoDecoderPriority({"VAAPI", "DXVA2", "VideoToolbox", "FFmpeg"});
// 或在播放前设置
QtAV::MediaConfig<QtAV::VAAPIDecoderConfig> vaapi;
vaapi.setSurfaceType(QtAV::VAAPISurface_DRM);
m_player->setOptionsForVideoDecoder(vaapi.toMap());
8.2 缓冲策略
cpp
// 直播模式:最小缓冲降低延迟
m_player->setBufferMode(QtAV::BufferBytes);
m_player->setBufferValue(1024 * 1024); // 1MB
// 点播模式:较大缓冲保证流畅
m_player->setBufferMode(QtAV::BufferSeconds);
m_player->setBufferValue(10.0); // 10秒
// 低延迟模式(直播/会议)
m_player->setInterruptTimeout(1000); // 1秒超时
m_player->setBufferMode(QtAV::BufferPackets);
m_player->setBufferValue(30); // 仅缓冲30个包
九、总结
QtAV的设计将FFmpeg的复杂性封装在解码器工厂、VideoFrame统一格式和VideoRenderer抽象层之下,开发者只需关注AVPlayer的高级接口、信号槽的连接和渲染器的选择,就能构建功能完整的跨平台播放器。
核心经验总结:
- 渲染器选择:OpenGL渲染器是性能最优解,支持GPU滤镜链;软件渲染仅用于调试
- 音视频同步:音频时钟优先,确保音画同步,避免使用外部时钟导致的漂移
- 硬件加速 :通过
setVideoDecoderPriority配置,让QtAV自动检测并启用系统最优解 - 滤镜链:基于FBO的链式处理,可叠加多个GPU滤镜,适合实时图像处理
- 缓冲控制:根据场景选择缓冲模式------直播追求低延迟,点播追求流畅
以上仅为技术分享参考,不构成投资建议》
《注:若有发现问题欢迎大家提出来纠正》