valbok / QtAVPlayer

Free and open-source Qt Media Player library based on FFmpeg, for Linux, Windows, macOS, iOS and Android.
MIT License
282 stars 54 forks source link
ffmpeg mediaplayer player qt

Qt AVPlayer

example workflow

Free and open-source Qt Media Player library based on FFmpeg.

Features

  1. QAVPlayer supports playing from an url or QIODevice or from avdevice:

    player.setSource("http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"); player.setSource("/home/lana/The Matrix Resurrections.mov");

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

    // Getting frames from the camera in Linux player.setSource("/dev/video0"); // Or Windows player.setInputFormat("dshow"); player.setSource("video=Integrated Camera"); // Or MacOS player.setInputFormat("avfoundation"); player.setSource("default"); // Or Android player.setInputFormat("android_camera"); player.setSource("0:0");

    player.setInputOptions({{"user_agent", "QAVPlayer"}});

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

  2. Easy getting video and audio frames:

    QObject::connect(&player, &QAVPlayer::videoFrame, [&](const QAVVideoFrame &frame) { // QAVVideoFrame is comppatible with QVideoFrame QVideoFrame videoFrame = frame;

       // QAVVideoFrame can be converted to various pixel formats
       auto convertedFrame = frame.convert(AV_PIX_FMT_YUV420P);
    
       // Easy getting data from video frame
       auto mapped = videoFrame.map(); // downloads data if it is in GPU
       qDebug() << mapped.format << mapped.size;
    
       // The frame might contain OpenGL or MTL textures, for copy-free rendering
       qDebug() << frame.handleType() << frame.handle();

    }, Qt::DirectConnection);

    // Audio frames could be played using QAVAudioOutput QAVAudioOutput audioOutput; QObject::connect(&player, &QAVPlayer::audioFrame, [&](const QAVAudioFrame &frame) { // Access to the data qDebug() << autioFrame.format() << autioFrame.data().size(); audioOutput.play(frame); }, Qt::DirectConnection);

    QObject::connect(&p, &QAVPlayer::subtitleFrame, &p, [](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);

  3. Each action is confirmed by a signal:

    // All signals are added to a queue and guaranteed to be emitted in proper order. QObject::connect(&player, &QAVPlayer::played, [&](qint64 pos) { qDebug() << "Playing started from pos" << pos; }); QObject::connect(&player, &QAVPlayer::paused, [&](qint64 pos) { qDebug() << "Paused at pos" << pos; }); QObject::connect(&player, &QAVPlayer::stopped, [&](qint64 pos) { qDebug() << "Stopped at pos" << pos; }); QObject::connect(&player, &QAVPlayer::seeked, [&](qint64 pos) { qDebug() << "Seeked to pos" << pos; }); QObject::connect(&player, &QAVPlayer::stepped, [&](qint64 pos) { qDebug() << "Made a step to pos" << pos; }); QObject::connect(&player, &QAVPlayer::mediaStatusChanged, [&](QAVPlayer::MediaStatus status) { switch (status) { case QAVplayer::EndOfMedia: qDebug() << "Finished to play, no frames in queue"; break; case QAVplayer::NoMedia: qDebug() << "Demuxer threads are finished"; break; default: break; } });

  4. Accurate seek:

    QObject::connect(&p, &QAVPlayer::seeked, &p, [&](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);

    If there is a frame with needed pts, it will be returned as first frame.

  5. FFmpeg filters:

    player.setFilter("crop=iw/2:ih:0:0,split[left][tmp];[tmp]hflip[right];[left][right] hstack"); // Render bundled subtitles player.setFilter("subtitles=file.mkv"); // Render subtitles from srt file player.setFilter("subtitles=file.srt"); // Multiple filters 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]" }); // Return frames from 3 filters with 5 outputs

  6. Step by step:

    // Pausing will always emit one frame QObject::connect(&player, &QAVPlayer::videoFrame, [&](const QAVVideoFrame &frame) { receivedFrame = frame; }); if (player.state() != QAVPlayer::PausedState) { // No frames if it is already paused player.pause(); QTRY_VERIFY(receivedFrame); }

    // Always makes a step forward and emits only one frame player.stepForward(); // the same here but backward player.stepBackward();

  7. Multiple streams:

    qDebug() << "Audio streams" << player.availableAudioStreams().size(); qDebug() << "Current audio stream" << player.currentAudioStreams().first().index() << player.currentAudioStreams().first().metadata(); player.setAudioStreams(player.availableAudioStreams()); // Return all frames for all available audio streams // Reports progress of playing per stream, like current pts, fps, frame rate, num of frames etc for (const auto &s : p.availableVideoStreams()) qDebug() << s << p.progress(s);

  8. HW accelerations:

    QT_AVPLAYER_NO_HWDEVICE can be used to force using software decoding. The video codec is negotiated automatically.

    • VA-API and VDPAU for Linux: the frames are returned with OpenGL textures.
    • Video Toolbox for macOS and iOS: the frames are returned with Metal Textures.
    • D3D11 for Windows: the frames are returned with D3D11Texture2D textures.
    • MediaCodec for Android: the frames are returned with OpenGL textures.
  9. QtMultimedia could be used to render video frames to QML or Widgets. See examples

  10. Qt 5.12 - 6.x is supported

How to build

QtAVPlayer should be directly bundled into an app. Some defines should be provided to opt some features.

QMake

Include QtAVPlayer.pri in your pro file:

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

FFmpeg on custom path:

$ 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:

# Path to QtAVPlayer
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})

FFmpeg on custom path:

% 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

Android:

Some exports could be also used: vars that point to libraries in armeabi-v7a, arm64-v8a, x86 and x86_64 target archs.

$ 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"

Don't forget to set extra libs in pro file for your app:

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