Skip to content

Repository files navigation

Qt AVPlayer

example workflow

A free, open-source media player library for Qt, powered by FFmpeg.

QtAVPlayer lets you decode, play, filter, and mux video/audio/subtitle streams in your Qt application — with hardware acceleration, accurate seeking, and full control over how frames are rendered.

Works with Qt 5.6 through Qt 6.x, on Linux, Windows, macOS, iOS, and Android.


Table of Contents


Why QtAVPlayer?

  • Demuxes and decodes video / audio / subtitle frames.
  • Supports FFmpeg Bitstream Filters and FFmpeg Filters, including filter_complex. It runs multiple parallel filters on one input — one input frame can produce several output frames.
  • Hardware-accelerated decoding or encoding. Also for filters.
  • Hardware-accelerated rendering via QAVWidget_OpenGL, with support for Windows as well.
  • You decide how frames are processed:
    • Experimental support for converting frames to QVideoFrame for copy-free rendering (note: not all Qt renderers support this, and best performance still depends on both decode and render being hardware-accelerated).
    • Audio playback via QAVAudioOutput, a thin wrapper around Qt's QAudioSink.
  • Muxes and encodes streams from multiple sources into a single output file — or re-muxes the current streams into an output file without re-encoding them.
  • Decodes all available streams simultaneously — for example, frames from multiple audio streams at once.
  • Accurate seeking — jumps straight to the closest matching frame.
  • Designed to be bundled directly into your app (via CMake or QMake), or built as a standalone shared library.
  • Great fit for media analytics tools like qctools or dvrescue.

In short, QtAVPlayer replaces a typical FFmpeg + FFplay pipeline — but integrated natively into Qt (QML or Widgets):

$ ./examples/qml_video :/valbok "if:you:like[cats];remove[this-sentence]"

Quick Start

The simplest possible playback example:

player->setSource(rtsp);
QObject::connect(player, &QAVPlayer::videoFrame, player,
    [&](const QAVVideoFrame &frame) {
        videoSink->setVideoFrame(frame);
    }, Qt::DirectConnection);
player->play();

Check out the examples folder for full QML and Widgets sample apps.


Usage Examples

Opening a source

QAVPlayer can open a URL, a local file, a QIODevice, or a device/camera:

player->setSource("~/Videos/Generative-Pre-trained-Transformers-and-WW3.mkv");

// Adaptive streaming (DASH/HLS)
player->setSource("https://bitdash-a.akamaihd.net/content/MI201109210084_1/m3u8s/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.m3u8");

// Playing from a Qt resource (qrc)
QSharedPointer<QIODevice> file(new QFile(":/alarm.wav"));
file->open(QIODevice::ReadOnly);
QSharedPointer<QAVIODevice> dev(new QAVIODevice(file));
player->setSource("alarm", dev);

// Camera input
player->setSource("/dev/video0");                 // Linux
player->setInputFormat("dshow");                  // Windows
player->setSource("video=Integrated Camera");
player->setInputFormat("avfoundation");            // macOS
player->setSource("default");
player->setInputFormat("android_camera");          // Android
player->setSource("0:0");

// Options for AVFormatContext
player->setInputOptions({{"user_agent", "QAVPlayer"}});
// Options for AVCodecContext
player->setVideoCodecOptions({{"fflags", "nobuffer"}, {"flags", "low_delay"}});

// Using FFmpeg protocols
player->setSource("subfile,,start,0,end,0,,:/root/Downloads/why-qtmm-must-die.mkv");

Getting video, audio & subtitle frames

QObject::connect(player, &QAVPlayer::videoFrame, player,
    [&](const QAVVideoFrame &frame) {
        // Compatible with QVideoFrame and copy-free
        QVideoFrame videoFrame = frame; // or frame.toQVideoFrame();

        // Convert to a different pixel format on CPU
        auto convertedFrame = frame.convertTo(AV_PIX_FMT_YUV420P);
    
        // Map the frame to access raw data (downloads from GPU if needed)
        auto mapped = videoFrame.map();
        qDebug() << mapped.format << mapped.size;
    
        // Frames may carry OpenGL or Metal textures for copy-free rendering
        qDebug() << frame.handleType() << frame.handle();
    }, Qt::DirectConnection);

// Audio
QObject::connect(player, &QAVPlayer::audioFrame, player,
    [&](const QAVAudioFrame &frame) {
        qDebug() << frame.format() << frame.data().size();
    }, Qt::DirectConnection);

// Subtitles
QObject::connect(player, &QAVPlayer::subtitleFrame, player,
    [&](const QAVSubtitleFrame &frame) {
        for (unsigned i = 0; i < frame.subtitle()->num_rects; ++i) {
            if (frame.subtitle()->rects[i]->type == SUBTITLE_TEXT)
                qDebug() << "text:" << frame.subtitle()->rects[i]->text;
            else
                qDebug() << "ass:" << frame.subtitle()->rects[i]->ass;
        }
    }, Qt::DirectConnection);

// Returns available list of AVChapter's after the source is loaded
qDebug() << player->chapters();

Hardware accelerated decoding

Hardware decoding is automatically negotiated based on the platform:

Platform Backend Frame type
Linux VA-API / VDPAU OpenGL
macOS / iOS Video Toolbox Metal
Windows D3D11 D3D11Texture2D
Android MediaCodec OpenGL

Most platforms expose only a single device context, but enabling CUDA support adds more options to choose from. In that case, the platform's native device context is prioritized by default. If you want CUDA to be used instead, you can force a CUDA-based codec, which selects the CUDA device context:

player->setInputVideoCodec("h264_cuvid");
player->setSource(file);

Notes:

  • Set the QT_AVPLAYER_NO_HWDEVICE environment variable to force software decoding.
  • You can also call player.setInputVideoCodec("software") to force software decoding for a specific player.
  • Set the QT_AVPLAYER_MAX_QUEUED_BYTES or QT_AVPLAYER_MAX_QUEUED_SEC environment variables to limit the amount of data buffered in the audio and video queues while demuxing. Once the configured limit is reached, demuxing pauses until packets are consumed by the decoder.
  • Not every FFmpeg decoder/filter supports hardware acceleration — QtAVPlayer falls back to software decoding automatically when needed.

Rendering

Video

auto w = new QAVWidget_OpenGL(mainWidget);
QObject::connect(player, &QAVPlayer::videoFrame, w,
    [w](const QAVVideoFrame &frame) {
        w->setVideoFrame(frame);
    }, Qt::DirectConnection);
  • Since QAVVideoFrame is compatible with QVideoFrame, QtMultimedia can render frames directly to QML or Widgets — see the examples. Converting to QVideoFrame is copy-free.
QObject::connect(player, &QAVPlayer::videoFrame, this,
    [this](const QAVVideoFrame &frame) {
        if (videoSink) {
            // If the non-copy-free render is requested
            // map the frame before converting to QVideoFrame.
            // This will force rendering mapped data instead of texture handles.
            if (!m_copyFreeRender)
                frame.map();
            videoSink->setVideoFrame(frame);
        }
    }, Qt::DirectConnection);

Audio

The audio frames could be played using QAVAudioOutput:

auto audioOutput = new QAVAudioOutput(&mainWidget);
auto audioDevices = QMediaDevices::audioOutputs();
if (!audioDevices.isEmpty())
    audioOutput->setAudioDevice(audioDevices.first());
QObject::connect(player, &QAVPlayer::audioFrame, audioOutput,
    [&](const QAVAudioFrame &frame) {
        audioOutput->play(frame);
    }, Qt::DirectConnection);

Subtitles

  • Subtitles could be rendered directly to the video frames using subtitles filter, but requires to have software decoders:
// Render bundled subtitles (requires not using hw_device_ctx — this is a software filter)
player->setFilter("subtitles=file.mkv");

// Render subtitles from an external .srt file
player->setFilter("subtitles=file.srt");
  • Subtitles could be parsed and extracted from QAVSubtitleFrame:
QAVSubtitleTextParser subtitleParser;
subtitleParser.load(player.currentSubtitleStreams().first());
QObject::connect(player, &QAVPlayer::subtitleFrame, player,
    [this](const QAVSubtitleFrame &frame) {
        QString text;
        if (subtitleParser.parseText(frame, text) >= 0)
            emit subtitleTextChanged(text, frame.duration() * 1000);
    }, Qt::DirectConnection);
  • Subtitles could be rendered to QImage:
QAVASSRenderer subtitleRenderer;
subtitleRenderer.load(player.currentSubtitleStreams().first());
QObject::connect(player, &QAVPlayer::subtitleFrame, player,
    [this](const QAVSubtitleFrame &frame) {
        auto img = subtitleRenderer.toImage(frame, size.width(), size.height());
        if (!img.isNull())
            emit subtitleImageChanged(img, frame.duration() * 1000);
    }, Qt::DirectConnection);

See the qml_player.

FFmpeg filters

player1->setFilter("crop=iw/2:ih:0:0,split[left][tmp];[tmp]hflip[right];[left][right] hstack");
player2->setFilter("scale=iw/2:-1");
  • Apply multiple filters at once, QAVVideoFrame::filterName() returns the filter name if any:
player->setFilters({
    "drawtext=text=%{pts\\:hms}:x=(w-text_w)/2:y=(h-text_h)*(4/5):box=1:boxcolor=gray@0.5:fontsize=36[drawtext]",
    "negate[negate]",
    "[0:v]split=3[in1][in2][in3];[in1]boxblur[out1];[in2]negate[out2];[in3]drawtext=text=%{pts\\:hms}:x=(w-text_w)/2:y=(h-text_h)*(4/5):box=1:boxcolor=gray@0.5:fontsize=36[out3]"
});
QObject::connect(player, &QAVPlayer::videoFrame, player,
    [&](const QAVVideoFrame &frame) {
        qDebug() << frame.pts() << frame.filterName();
    });
  • Some filters could be hardware accelerated, like scale_cuda or scale_vaapi:
// Requires to force cuda based codec
player->setInputVideoCodec("h264_cuvid");
player->setFilter("scale_cuda=1920:1080");
// Applying the parallel filters returns the one original frame and the scaled one using `scale_cuda`
player->setFilter("[0:v]split=2[orig][toscale];[toscale]scale_cuda=160:120[scaled]");

Multiple streams

  • QAVPlayer::availableStreams() returns all available streams in the source.
  • QAVPlayer::setAudioStream(), QAVPlayer::setSubtitleStream(), QAVPlayer::setVideoStream() can change current decoding streams on fly.
qDebug() << "Audio streams:" << player->availableAudioStreams().size();
qDebug() << "Current stream:" << player->currentAudioStreams().first().index()
         << player->currentAudioStreams().first().metadata();

player->setAudioStreams(player->availableAudioStreams()); // Decode all available audio streams

// Per-stream progress: pts, fps, frame rate, frame count, etc.
for (const auto &stream : player->availableVideoStreams())
    qDebug() << stream << player->progress(stream);

Muxing streams

  • Mux all streams to a file without re-encoding. It will use the same codecs without decoding the frames.
player->setOutput("output.mkv");
  • Combine streams from multiple players into a single file. This will decode frames first and then encode using the same codecs:
QAVPlayer p1, p2;
QAVMuxerFrames muxer;

// The players must be loaded before loading the muxer
QTRY_VERIFY(p1.mediaStatus() == QAVPlayer::LoadedMedia);
QTRY_VERIFY(p2.mediaStatus() == QAVPlayer::LoadedMedia);

auto streams = p1.availableStreams() + p2.availableStreams();
muxer.load(streams, "output.mkv");

QObject::connect(&p1, &QAVPlayer::videoFrame, &p1,
    [&](const QAVVideoFrame &f) { muxer.enqueue(f); }, Qt::DirectConnection);
QObject::connect(&p1, &QAVPlayer::audioFrame, &p1,
    [&](const QAVAudioFrame &f) { muxer.enqueue(f); }, Qt::DirectConnection);
QObject::connect(&p2, &QAVPlayer::videoFrame, &p2,
    [&](const QAVVideoFrame &f) { muxer.enqueue(f); }, Qt::DirectConnection);
QObject::connect(&p2, &QAVPlayer::audioFrame, &p2,
    [&](const QAVAudioFrame &f) { muxer.enqueue(f); }, Qt::DirectConnection);

p1.play();
p2.play();
  • QAVMuxerFrames allows scaling input frames before muxing them into the output file:
muxer.load({{videoStream, encoder, newSize}}, "output.mkv");

Accurate seeking

If a frame exists at the requested timestamp, it's returned first.

QObject::connect(&player, &QAVPlayer::seeked, &player,
    [&](qint64 pos) { seekPosition = pos; });
QObject::connect(&player, &QAVPlayer::videoFrame,
    [&](const QAVVideoFrame &frame) { seekFrame = frame; });

player.seek(5000);
QTRY_COMPARE(seekPosition, 5000);
QTRY_COMPARE(seekFrame.pts(), 5.0);

Stepping frame by frame

Stepping emits exactly one frame.

QObject::connect(&player, &QAVPlayer::videoFrame, [&](const QAVVideoFrame &frame) { receivedFrame = frame; });

if (player.state() != QAVPlayer::PausedState) {
    player.pause(); // Pausing always emits exactly one frame
    QTRY_VERIFY(receivedFrame);
}

player.stepForward();  // Advances and emits exactly one frame
player.stepBackward(); // Same, but backward

Listening to player signals

Every action is confirmed with a signal, delivered in the correct order. If play(), pause(), and seek() is called, then played(), paused() and seeked() is emitted accordinally.

QObject::connect(p, &QAVPlayer::played,
    [&](qint64 pos) { qDebug() << "Playing started at" << pos; });
QObject::connect(p, &QAVPlayer::paused,
    [&](qint64 pos) { qDebug() << "Paused at" << pos; });
QObject::connect(p, &QAVPlayer::stopped,
    [&](qint64 pos) { qDebug() << "Stopped at" << pos; });
QObject::connect(p, &QAVPlayer::seeked,
    [&](qint64 pos) { qDebug() << "Seeked to" << pos; });
QObject::connect(p, &QAVPlayer::stepped,
    [&](qint64 pos) { qDebug() << "Stepped to" << pos; });
QObject::connect(p, &QAVPlayer::mediaStatusChanged,
    [&](QAVPlayer::MediaStatus status) {
        switch (status) {
        case QAVPlayer::EndOfMedia:
            qDebug() << "Playback finished, no frames left in queue";
            break;
        case QAVPlayer::NoMedia:
            qDebug() << "Demuxer threads finished";
            break;
        default:
            break;
        }
    });

Installation & Build Options

QtAVPlayer is meant to be bundled directly into your application. A few compile-time defines let you opt into extra features.

Build Flags

Define Enables
QT_AVPLAYER_MULTIMEDIA QtMultimedia support (requires QtGUI, QtQuick, etc.)
QT_AVPLAYER_CUDA CUDA support
QT_AVPLAYER_VA_X11 libva-x11 hardware acceleration (Linux only)
QT_AVPLAYER_VA_DRM libva-drm hardware acceleration (Linux only)
QT_AVPLAYER_VDPAU libvdpau hardware acceleration (Linux only)
QT_AVPLAYER_WIDGET_OPENGL The OpenGL-based widget
QT_AVPLAYER_LIBASS libass support and QAVASSRenderer

QMake

Add QtAVPlayer.pri to your .pro file:

DEFINES += "QT_AVPLAYER_MULTIMEDIA"
INCLUDEPATH += ../../src/
include(../../src/QtAVPlayer/QtAVPlayer.pri)

If FFmpeg is in a custom location:

qmake INCLUDEPATH+="/usr/local/Cellar/ffmpeg/6.0/include" LIBS="-L/usr/local/Cellar/ffmpeg/6.0/lib"

CMake

Include QtAVPlayer.cmake in your project:

include_directories(QtAVPlayer/src)
set(QT_AVPLAYER_DIR QtAVPlayer/src/QtAVPlayer)
include(QtAVPlayer/src/QtAVPlayer/QtAVPlayer.cmake)
add_executable(${PROJECT_NAME} ${QtAVPlayer_SOURCES})
target_link_libraries(${PROJECT_NAME} ${QtAVPlayer_LIBS})

If FFmpeg is in a custom location:

cmake ../ -DQT_AVPLAYER_MULTIMEDIA=ON -DCMAKE_PREFIX_PATH=/opt/Qt/6.7.1/macos/lib/cmake -DCMAKE_LIBRARY_PATH=/opt/homebrew/Cellar/ffmpeg/7.0_1/lib

Building as a Shared Library (libQtAVPlayer)

If bundling directly isn't convenient, build QtAVPlayer as a standalone shared library instead:

1. Build and install:

cmake ../src/QtAVPlayer \
  -DCMAKE_PREFIX_PATH=/opt/Qt/6.8.2/gcc_64/lib/cmake/Qt6 \
  -DCMAKE_INSTALL_PREFIX=/opt/QtAVPlayer/install \
  -DCMAKE_LIBRARY_PATH="/opt/ffmpeg/install/lib;/opt/Qt/6.8.2/gcc_64/lib" \
  -DCMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES=/opt/ffmpeg/install/include \
  -DQT_AVPLAYER_MULTIMEDIA=On \
  -DQT_AVPLAYER_VDPAU=ON
make -j32
make install

2. Link against it in your CMakeLists.txt:

find_package(QtAVPlayer REQUIRED)
target_link_libraries(${PROJECT_NAME} QtAVPlayer)

3. Point CMake to the install location if needed:

cmake ../ -DCMAKE_PREFIX_PATH="/opt/QtAVPlayer/install/lib/cmake;/opt/Qt/6.8.2/gcc_64/lib/cmake/Qt6" -DCMAKE_LIBRARY_PATH="/opt/Qt/6.8.2/gcc_64/lib"

Android Setup

Point these environment variables to your prebuilt FFmpeg libraries for each target architecture:

export AVPLAYER_ANDROID_LIB_ARMEABI_V7A=/opt/mobile-ffmpeg/prebuilt/android-arm/ffmpeg/lib
export AVPLAYER_ANDROID_LIB_ARMEABI_V8A=/opt/mobile-ffmpeg/prebuilt/android-arm64/ffmpeg/lib
export AVPLAYER_ANDROID_LIB_X86=/opt/mobile-ffmpeg/prebuilt/android-x86/ffmpeg/lib
export AVPLAYER_ANDROID_LIB_X86_64=/opt/mobile-ffmpeg/prebuilt/android-x86_64/ffmpeg/lib
export CPLUS_INCLUDE_PATH=/opt/mobile-ffmpeg/prebuilt/android-arm64/ffmpeg/include:$CPLUS_INCLUDE_PATH

qmake DEFINES+="QT_AVPLAYER_MULTIMEDIA"

Then add the extra libraries to your app's .pro file:

ANDROID_EXTRA_LIBS += \
    /opt/mobile-ffmpeg/prebuilt/android-arm/ffmpeg/lib/libavdevice.so \
    /opt/mobile-ffmpeg/prebuilt/android-arm/ffmpeg/lib/libavformat.so \
    /opt/mobile-ffmpeg/prebuilt/android-arm/ffmpeg/lib/libavutil.so \
    /opt/mobile-ffmpeg/prebuilt/android-arm/ffmpeg/lib/libavcodec.so \
    /opt/mobile-ffmpeg/prebuilt/android-arm/ffmpeg/lib/libavfilter.so \
    /opt/mobile-ffmpeg/prebuilt/android-arm/ffmpeg/lib/libswscale.so \
    /opt/mobile-ffmpeg/prebuilt/android-arm/ffmpeg/lib/libswresample.so

License

See the QtAVPlayer repository for license details.

About

A free, open-source media player library for Qt, powered by FFmpeg for Linux, Windows, macOS, iOS and Android

Topics

Resources

Stars

446 stars

Watchers

8 watching

Forks

Releases

Contributors

Languages