New upstream version 1.13.0+ds
Steven Robbins
4 years ago
0 | ### QtAV, Qt version and platform | |
1 | ||
2 | or commit id if not using release version | |
3 | ||
4 | ### Reproduction steps | |
5 | ||
6 | ### Expected behavior | |
7 | ||
8 | ### Actual behavior | |
9 | ||
10 | ### Log file | |
11 | ||
12 | set environment var `QTAV_LOG=all` or C++ api `QtAV::setLogLevel(All)` to enable log. | |
13 | ||
14 | For Player and QMLPlayer example app, choose log level `all` in config page. | |
15 | ||
16 | ### Sample files (optional) |
0 | 0 | CVS |
1 | 1 | .#* |
2 | ||
3 | *.patch | |
4 | *.diff | |
5 | *.orig | |
6 | #*.rej | |
7 | contrib/include | |
8 | *~ | |
9 | *.mak | |
2 | 10 | |
3 | 11 | # Hidden files are ignored by default with exceptions |
4 | 12 | .* |
5 | 13 | !/.gitignore |
14 | !/.github | |
6 | 15 | !/.gitmodules |
7 | 16 | !/.qmake.conf |
8 | 17 | !/.travis.yml |
60 | 69 | *.Debug |
61 | 70 | *.Release |
62 | 71 | |
72 | # Ignore temporary Python files | |
73 | python/PyQtAV.api | |
74 | python/PyQtAV.pro | |
75 | python/QtAV.pyi | |
76 | python/QtAVWidgets.pyi | |
77 | python/QtAV | |
78 | python/QtAVWidgets | |
79 | ||
63 | 80 | *.fuse* |
64 | 81 | |
65 | 82 | #qt |
6 | 6 | [submodule "contrib/uchardet"] |
7 | 7 | path = contrib/uchardet |
8 | 8 | url = https://github.com/BYVoid/uchardet.git |
9 | [submodule "src/jmi"] | |
10 | path = src/jmi | |
11 | url = https://github.com/wang-bin/JMI.git |
0 | 0 | QTAV_MAJOR_VERSION = 1 |
1 | QTAV_MINOR_VERSION = 12 | |
1 | QTAV_MINOR_VERSION = 13 | |
2 | 2 | QTAV_PATCH_VERSION = 0 |
3 | 3 | |
4 | 4 | QTAV_VERSION = $${QTAV_MAJOR_VERSION}.$${QTAV_MINOR_VERSION}.$${QTAV_PATCH_VERSION} |
0 | 0 | cmake_minimum_required(VERSION 2.8.11 FATAL_ERROR) |
1 | ||
2 | project(QTAV) | |
3 | ||
1 | 4 | set(QTAV_MAJOR 1) |
2 | set(QTAV_MINOR 11) | |
5 | set(QTAV_MINOR 13) | |
3 | 6 | set(QTAV_PATCH 0) |
4 | 7 | set(PROJECT_VERSION ${QTAV_MAJOR}.${QTAV_MINOR}.${QTAV_PATCH}) |
5 | 8 | set(SO_VERSION ${QTAV_MAJOR}) |
9 | 12 | |
10 | 13 | if(POLICY CMP0063) # visibility. since 3.3 |
11 | 14 | cmake_policy(SET CMP0063 NEW) |
15 | endif() | |
16 | if(POLICY CMP0071) | |
17 | cmake_policy(SET CMP0071 NEW) | |
12 | 18 | endif() |
13 | 19 | set(CMAKE_CXX_VISIBILITY_PRESET hidden) #use with -fdata-sections -ffunction-sections to reduce target size |
14 | 20 | set(CMAKE_VISIBILITY_INLINES_HIDDEN ON) |
0 | version 1.13.0 2019-07-11 | |
1 | ||
2 | - add python bindings | |
3 | - more apis for qml player | |
4 | - auto rotate video | |
5 | - apple store | |
6 | - fix ios plugin not found | |
7 | - support chapters | |
8 | - muxer, encoder, transcoder improvements | |
9 | - compatible with new ffmpeg | |
10 | - videotoolbox: hevc, | |
11 | - cuda: new devices | |
12 | - android: no longer depends on private qt module | |
13 | - fix opensl error | |
14 | - mediacodec: 0-copy via a plugin from https://github.com/wang-bin/mdk-sdk | |
15 | ||
16 | ||
0 | 17 | version 1.12.0 2017-06-20 |
1 | 18 | |
2 | 19 | Changelog |
32 | 32 | - Multiple video outputs for 1 player |
33 | 33 | - Video eq(software and OpenGL): brightness, contrast, saturation, hue |
34 | 34 | - QML support. Most playback APIs are compatible with QtMultimedia module |
35 | - Compatiblity: QtAV can be built with both Qt4 and Qt5, FFmpeg(>=1.0) and [Libav](http://libav.org) (>=9.0). Latest FFmpeg release is recommended. | |
35 | - Compatibility: QtAV can be built with both Qt4 and Qt5, FFmpeg(>=1.0) and [Libav](http://libav.org) (>=9.0). Latest FFmpeg release is recommended. | |
36 | 36 | |
37 | 37 | |
38 | 38 | ### Extensible Framework |
154 | 154 | |
155 | 155 | > Copyright © Wang Bin wbsecg1@gmail.com |
156 | 156 | |
157 | > Shanghai University->S3 Graphics->Deepin, Shanghai, China | |
158 | ||
159 | 157 | > 2013-01-21 |
7 | 7 | environment: |
8 | 8 | |
9 | 9 | matrix: |
10 | - arch: x64 | |
11 | qt: 5.9 | |
12 | cc: VS2017 | |
13 | mode: release | |
14 | QTDIR: C:\Qt\5.9\msvc2017_64 | |
15 | APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 | |
16 | ||
10 | 17 | - arch: x86 |
11 | 18 | cc: VS2015 |
12 | 19 | qt: 5.9 |
51 | 58 | init: |
52 | 59 | - set vcarch=%arch% |
53 | 60 | - if "%arch%" == "x64" set vcarch=amd64 |
54 | - if not %cc%==MinGW call "C:\Program Files (x86)\Microsoft Visual Studio %toolchain_version%.0\VC\vcvarsall.bat" %vcarch% | |
61 | - if %cc%==VS2017 ( | |
62 | call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvarsall.bat" %vcarch% | |
63 | ) else if not %cc%==MinGW ( | |
64 | call "C:\Program Files (x86)\Microsoft Visual Studio %toolchain_version%.0\VC\vcvarsall.bat" %vcarch% | |
65 | ) | |
55 | 66 | - echo NUMBER_OF_PROCESSORS=%NUMBER_OF_PROCESSORS% |
56 | 67 | - echo PROCESSOR_IDENTIFIER=%PROCESSOR_IDENTIFIER% |
57 | 68 | - echo QTDIR=%QTDIR% |
89 | 100 | active_mode: false |
90 | 101 | on: |
91 | 102 | mode: release |
92 | cc: VS2015 | |
103 | cc: VS2017 |
18 | 18 | BEGIN |
19 | 19 | BLOCK "000004b0" |
20 | 20 | BEGIN |
21 | VALUE "CompanyName", "Shanghai University->S3 Graphics->Deepin->PPTV | wbsecg1@gmail.com" | |
21 | VALUE "CompanyName", "wbsecg1@gmail.com" | |
22 | 22 | VALUE "FileDescription", "QtAV Multimedia framework. http://qtav.org" |
23 | 23 | VALUE "FileVersion", QTAV_VERSION_STR ".0" |
24 | VALUE "LegalCopyright", "Copyright (C) 2012-2017 WangBin, wbsecg1@gmail.com" | |
24 | VALUE "LegalCopyright", "Copyright (C) 2012-2019 WangBin, wbsecg1@gmail.com" | |
25 | 25 | VALUE "InternalName", "@MODULE@" |
26 | 26 | VALUE "ProductName", "@MODULE@" |
27 | 27 | VALUE "ProductVersion", QTAV_VERSION_STR ".0" |
27 | 27 | } else:win* { |
28 | 28 | QMAKE_EXTENSION_SHLIB = dll |
29 | 29 | } |
30 | } | |
31 | ||
32 | CONFIG += profile | |
33 | #profiling, -pg is not supported for msvc | |
34 | debug:!ios:!android:!*msvc*:profile { | |
35 | QMAKE_CXXFLAGS_DEBUG += -pg | |
36 | QMAKE_LFLAGS_DEBUG += -pg | |
37 | QMAKE_CXXFLAGS_DEBUG = $$unique(QMAKE_CXXFLAGS_DEBUG) | |
38 | QMAKE_LFLAGS_DEBUG = $$unique(QMAKE_LFLAGS_DEBUG) | |
39 | 30 | } |
40 | 31 | |
41 | 32 | #$$[TARGET_PLATFORM] |
0 | 0 | /****************************************************************************** |
1 | 1 | QtAV: Media play library based on Qt and FFmpeg |
2 | Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com> | |
2 | Copyright (C) 2012-2018 Wang Bin <wbsecg1@gmail.com> | |
3 | 3 | |
4 | 4 | * This file is part of QtAV |
5 | 5 | |
17 | 17 | License along with this library; if not, write to the Free Software |
18 | 18 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
19 | 19 | ******************************************************************************/ |
20 | #ifndef __APPLE__ | |
21 | #error apple only | |
22 | #endif | |
20 | 23 | extern "C" { |
21 | 24 | #include <libavcodec/videotoolbox.h> |
22 | 25 | } |
23 | 26 | int main() |
24 | 27 | { |
25 | av_videotoolbox_alloc_context(); | |
28 | //av_videotoolbox_alloc_context(); | |
26 | 29 | return 0; |
27 | 30 | } |
100 | 100 | set(MODULE QMLPlayer) |
101 | 101 | if(WIN32) |
102 | 102 | set(RC_FILE ${CMAKE_CURRENT_BINARY_DIR}/${MODULE}.rc) |
103 | configure_file(${CMAKE_SOURCE_DIR}/cmake/QtAV.rc.in ${RC_FILE}) | |
103 | configure_file(${QTAV_SOURCE_DIR}/cmake/QtAV.rc.in ${RC_FILE}) | |
104 | 104 | endif() |
105 | 105 | add_executable(QMLPlayer ${EXE_TYPE} |
106 | 106 | QMLPlayer/main.cpp |
119 | 119 | set(MODULE Player) |
120 | 120 | if(WIN32) |
121 | 121 | set(RC_FILE ${CMAKE_CURRENT_BINARY_DIR}/${MODULE}.rc) |
122 | configure_file(${CMAKE_SOURCE_DIR}/cmake/QtAV.rc.in ${RC_FILE}) | |
122 | configure_file(${QTAV_SOURCE_DIR}/cmake/QtAV.rc.in ${RC_FILE}) | |
123 | 123 | endif() |
124 | 124 | add_executable(Player ${EXE_TYPE} ${PLAYER_SRC} ${PLAYER_HEADERS} ${PLAYER_RES} ${RC_FILE}) |
125 | 125 | target_link_libraries(Player QtAVWidgets common) |
55 | 55 | RC_ICONS = $$PROJECTROOT/src/QtAV.ico |
56 | 56 | QMAKE_TARGET_COMPANY = "Shanghai University->S3 Graphics->Deepin | wbsecg1@gmail.com" |
57 | 57 | QMAKE_TARGET_DESCRIPTION = "QtAV Multimedia playback framework. http://qtav.org" |
58 | QMAKE_TARGET_COPYRIGHT = "Copyright (C) 2012-2017 WangBin, wbsecg1@gmail.com" | |
58 | QMAKE_TARGET_COPYRIGHT = "Copyright (C) 2012-2019 WangBin, wbsecg1@gmail.com" | |
59 | 59 | QMAKE_TARGET_PRODUCT = "QtAV $$1" |
60 | 60 | export(RC_ICONS) |
61 | 61 | export(QMAKE_TARGET_COMPANY) |
0 | 0 | <?xml version="1.0"?> |
1 | <manifest package="org.qtav.qmlplayer" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="1.12.0" android:versionCode="7" android:installLocation="auto"> | |
1 | <manifest package="org.qtav.qmlplayer" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="1.13.0" android:versionCode="16" android:installLocation="auto"> | |
2 | 2 | <application android:hardwareAccelerated="true" android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="QMLPlayer" android:icon="@drawable/icon"> |
3 | 3 | <activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|locale|fontScale|keyboard|keyboardHidden|navigation" android:name="org.qtav.qmlplayer.QMLPlayerActivity" android:label="QtAV QMLPlayer" android:screenOrientation="unspecified" android:launchMode="singleTop"> |
4 | 4 | <intent-filter> |
47 | 47 | <!-- Background running --> |
48 | 48 | </activity> |
49 | 49 | </application> |
50 | <uses-sdk android:minSdkVersion="16" android:targetSdkVersion="19"/> | |
50 | <uses-sdk android:minSdkVersion="16" android:targetSdkVersion="23"/> | |
51 | 51 | <supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/> |
52 | 52 | |
53 | 53 | <!-- The following comment will be replaced upon deployment with default permissions based on the dependencies of the application. |
61 | 61 | <!-- %%INSERT_FEATURES --> |
62 | 62 | <uses-permission android:name="android.permission.INTERNET"/> |
63 | 63 | <uses-permission android:name="android.permission.WAKE_LOCK"/> |
64 | <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> | |
65 | <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> | |
64 | 66 | </manifest> |
Binary diff not shown
3 | 3 | import android.content.Intent; |
4 | 4 | import android.app.PendingIntent; |
5 | 5 | import android.util.Log; |
6 | import android.os.Build; | |
6 | 7 | import android.os.Bundle; |
7 | 8 | import android.view.WindowManager; |
8 | 9 | import android.content.pm.ActivityInfo; |
10 | import android.content.pm.PackageManager; | |
11 | import android.support.v4.content.ContextCompat; | |
12 | import android.support.v4.app.ActivityCompat; | |
13 | import android.widget.Toast; | |
9 | 14 | |
10 | 15 | public class QMLPlayerActivity extends QtActivity |
11 | 16 | { |
22 | 27 | @Override |
23 | 28 | public void onCreate(Bundle savedInstanceState) { |
24 | 29 | super.onCreate(savedInstanceState); |
30 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { | |
31 | if (!checkPermission()) | |
32 | requestPermission(); | |
33 | } | |
25 | 34 | //getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); |
26 | 35 | getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); |
27 | 36 | Intent intent = getIntent(); |
34 | 43 | } |
35 | 44 | setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); |
36 | 45 | } |
46 | ||
47 | protected boolean checkPermission() { | |
48 | int result = ContextCompat.checkSelfPermission(this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE); | |
49 | if (result == PackageManager.PERMISSION_GRANTED) | |
50 | return true; | |
51 | return false; | |
52 | } | |
53 | protected void requestPermission() { | |
54 | if (ActivityCompat.shouldShowRequestPermissionRationale(this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE)) { | |
55 | Toast.makeText(this, "Please allow Write External Storage permission to play your local videos", Toast.LENGTH_LONG).show(); | |
56 | } else { | |
57 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) | |
58 | requestPermissions(new String[]{android.Manifest.permission.WRITE_EXTERNAL_STORAGE}, 100); | |
59 | } | |
60 | } | |
37 | 61 | } |
18 | 18 | <key>CFBundleName</key> |
19 | 19 | <string>${PRODUCT_NAME}</string> |
20 | 20 | <key>CFBundleShortVersionString</key> |
21 | <string>1.12</string> | |
21 | <string>1.13</string> | |
22 | 22 | <key>CFBundleVersion</key> |
23 | <string>1.12.0</string> | |
23 | <string>1.13.0</string> | |
24 | 24 | <key>LSRequiresIPhoneOS</key> |
25 | 25 | <true/> |
26 | 26 | <key>UIFileSharingEnabled</key> |
40 | 40 | options.add(QLatin1String("QMLPlayer options")) |
41 | 41 | ("scale", 1.0, QLatin1String("scale of graphics context. 0: auto")) |
42 | 42 | ; |
43 | qputenv("QTAV_MEDIACODEC_KEY", ")A0aZ!WIgo2DdCsc#EnR?NAsFtWrnENONeeiiED^OM@6gI+Hew6s5)77p^>$K(Fe"); | |
43 | 44 | options.parse(argc, argv); |
44 | 45 | Config::setName(QString::fromLatin1("QMLPlayer")); |
45 | 46 | do_common_options_before_qapp(options); |
158 | 159 | if (player && !file.isEmpty()) { |
159 | 160 | if (!file.startsWith(QLatin1String("file:")) && QFile(file).exists()) |
160 | 161 | file.prepend(QLatin1String("file:")); //qml use url and will add qrc: if no scheme |
162 | #ifdef Q_OS_WIN | |
161 | 163 | file.replace(QLatin1String("\\"), QLatin1String("/")); //qurl |
164 | #endif | |
162 | 165 | //QMetaObject::invokeMethod(player, "play", Q_ARG(QUrl, QUrl(file))); |
163 | player->setProperty("source", QUrl(file)); | |
166 | QUrl url; | |
167 | player->setProperty("source", QUrl(file.toUtf8().toPercentEncoding())); | |
164 | 168 | } |
165 | 169 | #endif |
166 | 170 | QObject::connect(&Config::instance(), SIGNAL(changed()), &Config::instance(), SLOT(save())); |
18 | 18 | font.pixelSize: Utils.scaled(15) |
19 | 19 | onContentHeightChanged: parent.contentHeight = contentHeight + 2*anchors.margins |
20 | 20 | onLinkActivated: Qt.openUrlExternally(link) |
21 | text: "<img src='qrc:/QtAV.svg'><h3>QMLPlayer for " + Qt.platform.os + " " + qsTr("based on") + " QtAV 1.11.0</h3>" | |
21 | text: "<img src='qrc:/QtAV.svg'><h3>QMLPlayer for " + Qt.platform.os + " " + qsTr("based on") + " QtAV 1.12.0</h3>" | |
22 | 22 | + "<p>" + qsTr("QtAV is a cross platform, high performance multimedia framework") + "</p>" |
23 | 23 | + "<p>Distributed under the terms of LGPLv2.1 or later.</p>" |
24 | + "<p>Copyright (C) 2012-2016 Wang Bin (aka. Lucas Wang) <a href='mailto:wbsecg1@gmail.com'>wbsecg1@gmail.com</a></p><p>" | |
24 | + "<p>Copyright (C) 2012-2017 Wang Bin (aka. Lucas Wang) <a href='mailto:wbsecg1@gmail.com'>wbsecg1@gmail.com</a></p><p>" | |
25 | 25 | + qsTr("Home page") + ": <a href='http://qtav.org'>http://qtav.org</a></p>" |
26 | 26 | + "<p><a href='http://qtav.org/install.html'>"+qsTr("Install QtAV for Windows desktop/store, macOS, Linux and Android")+"</a></p>" |
27 | 27 | + "\n<p>" + qsTr("Double click") + ": " + qsTr("show/hide control bar") + "</p><p>" |
5 | 5 | title: qsTr("Video Codec") |
6 | 6 | height: titleHeight + detail.height + listView.height + copyMode.height + Utils.kSpacing*4 |
7 | 7 | signal zeroCopyChanged(bool value) |
8 | property var defaultDecoders: [] | |
8 | 9 | |
9 | 10 | QtObject { |
10 | 11 | id: d |
48 | 49 | |
49 | 50 | ListModel { |
50 | 51 | id: codecMode |
52 | ListElement { name: "Auto"; hardware: true; zcopy: true; description: qsTr("Try hardware decoders first") } | |
51 | 53 | ListElement { name: "FFmpeg"; hardware: false; zcopy: false; description: "FFmpeg/Libav" } |
52 | 54 | } |
53 | 55 | |
71 | 73 | onStateChanged: { |
72 | 74 | if (state != "selected") |
73 | 75 | return |
76 | if (name === "Auto") { | |
77 | PlayerConfig.decoderPriorityNames = defaultDecoders | |
78 | d.detail = description | |
79 | return | |
80 | } | |
74 | 81 | d.detail = description + " " + (hardware ? qsTr("hardware decoding") : qsTr("software decoding")) |
75 | 82 | if (name === "FFmpeg") { |
76 | 83 | copyMode.visible = false |
94 | 101 | } |
95 | 102 | Component.onCompleted: { |
96 | 103 | if (Qt.platform.os == "windows") { |
104 | defaultDecoders.push("DXVA") | |
105 | defaultDecoders.push("D3D11") | |
106 | defaultDecoders.push("CUDA") | |
97 | 107 | codecMode.append({ name: "DXVA", hardware: true, zcopy: true, description: "DirectX Video Acceleration (Windows)\nUse OpenGLES(ANGLE) + D3D to support 0-copy" }) |
98 | 108 | codecMode.append({ name: "D3D11", hardware: true, zcopy: true, description: "D3D11 Video Acceleration\n0-copy is supported under OpenGLES(ANGLE)" }) |
99 | 109 | codecMode.append({ name: "CUDA", hardware: true, zcopy: true, description: "NVIDIA CUDA (Windows, Linux).\nH264 10bit support."}) |
100 | 110 | } else if (Qt.platform.os == "winrt" || Qt.platform.os == "winphone") { |
111 | defaultDecoders.push("D3D11") | |
101 | 112 | codecMode.append({ name: "D3D11", hardware: true, zcopy: true, description: "D3D11 Video Acceleration" }) |
102 | 113 | } else if (Qt.platform.os == "osx") { |
114 | defaultDecoders.push("VideoToolbox") | |
115 | defaultDecoders.push("VDA") | |
116 | codecMode.append({ name: "VideoToolbox", hardware: true, zcopy: true, description: "VideoToolbox (OSX)" }) | |
103 | 117 | codecMode.append({ name: "VDA", hardware: true, zcopy: true, description: "VDA (OSX)" }) |
104 | codecMode.append({ name: "VideoToolbox", hardware: true, zcopy: true, description: "VideoToolbox (OSX)" }) | |
105 | 118 | } else if (Qt.platform.os == "ios") { |
119 | defaultDecoders.append("VideoToolbox") | |
106 | 120 | codecMode.append({ name: "VideoToolbox", hardware: true, zcopy: true, description: "VideoToolbox (iOS)" }) |
107 | 121 | } else if(Qt.platform.os == "android") { |
108 | codecMode.append({ name: "MediaCodec", hardware: true, zcopy: false, description: "Android 5.0 MediaCodec (H.264)" }) | |
122 | defaultDecoders.push("MediaCodec") | |
123 | codecMode.append({ name: "MediaCodec", hardware: true, zcopy: false, description: "Android(>=4.1) MediaCodec (H.264)" }) | |
109 | 124 | } else if (Qt.platform.os == "linux") { |
125 | defaultDecoders.push("VAAPI") | |
126 | defaultDecoders.push("CUDA") | |
110 | 127 | codecMode.append({ name: "VAAPI", hardware: true, zcopy: true, description: "VA-API (Linux) " }) |
111 | 128 | codecMode.append({ name: "CUDA", hardware: true, zcopy: true, description: "NVIDIA CUDA (Windows, Linux)"}) |
112 | 129 | } |
113 | for (var i = 0; i < codecMode.count; ++i) { | |
130 | defaultDecoders.push("FFmpeg") | |
131 | if (PlayerConfig.decoderPriorityNames.length > 1) { | |
132 | listView.currentIndex = 0; | |
133 | d.selectedItem = listView.currentItem | |
134 | listView.currentItem.state = "selected" | |
135 | return | |
136 | } | |
137 | for (var i = 1; i < codecMode.count; ++i) { | |
114 | 138 | if (codecMode.get(i).name === PlayerConfig.decoderPriorityNames[0]) { |
115 | 139 | listView.currentIndex = i; |
116 | 140 | d.selectedItem = listView.currentItem |
3 | 3 | Name=\"org.qtav.qmlplayer\" |
4 | 4 | ProcessorArchitecture=\"$$lower($$VCPROJ_ARCH)\" |
5 | 5 | Publisher=\"CN=930FB10A-C50C-4801-84BA-255229D27D44\" |
6 | Version=\"1.12.0.0\" /> | |
6 | Version=\"1.13.0.0\" /> | |
7 | 7 | <mp:PhoneIdentity |
8 | 8 | PhoneProductId=\"e556656c-d8e8-49f9-8148-26506e94575a\" |
9 | 9 | PhonePublisherId=\"00000000-0000-0000-0000-000000000000\" /> |
9 | 9 | <Identity |
10 | 10 | Name=\"org.qtav.qmlplayer\" |
11 | 11 | Publisher=\"CN=930FB10A-C50C-4801-84BA-255229D27D44\" |
12 | Version=\"1.12.0.0\" | |
12 | Version=\"1.13.0.0\" | |
13 | 13 | ProcessorArchitecture=\"$$lower($$VCPROJ_ARCH)\" /> <!--replaced by qmake--> |
14 | 14 | <mp:PhoneIdentity |
15 | 15 | PhoneProductId=\"e556656c-d8e8-49f9-8148-26506e94575a\" |
3 | 3 | Name=\"org.qtav.qmlplayer\" |
4 | 4 | ProcessorArchitecture=\"$$lower($$VCPROJ_ARCH)\" |
5 | 5 | Publisher=\"CN=930FB10A-C50C-4801-84BA-255229D27D44\" |
6 | Version=\"1.12.0.0\" /> | |
6 | Version=\"1.13.0.0\" /> | |
7 | 7 | <Properties> |
8 | 8 | <DisplayName>QtAV Video Player</DisplayName> |
9 | 9 | <PublisherDisplayName>Lucas Wang</PublisherDisplayName> |
0 | 0 | /****************************************************************************** |
1 | 1 | QtAV Player Demo: this file is part of QtAV examples |
2 | Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com> | |
2 | Copyright (C) 2012-2017 Wang Bin <wbsecg1@gmail.com> | |
3 | 3 | |
4 | 4 | * This file is part of QtAV (from 2014) |
5 | 5 | |
303 | 303 | setForceFrameRate(settings.value(QString::fromLatin1("force_fps"), 0.0).toReal()); |
304 | 304 | settings.beginGroup(QString::fromLatin1("decoder")); |
305 | 305 | settings.beginGroup(QString::fromLatin1("video")); |
306 | QString decs_default(QString::fromLatin1("FFmpeg")); | |
306 | QString decs_default(QString::fromLatin1("HW FFmpeg")); // HW is ignored | |
307 | 307 | setDecoderPriorityNames(settings.value(QString::fromLatin1("priority"), decs_default).toString().split(QString::fromLatin1(" "), QString::SkipEmptyParts)); |
308 | 308 | setZeroCopy(settings.value(QString::fromLatin1("zeroCopy"), true).toBool()); |
309 | 309 | settings.endGroup(); //video |
20 | 20 | static QLibrary xlib; |
21 | 21 | #endif //Q_OS_LINUX |
22 | 22 | #if defined(Q_OS_MAC) && !defined(Q_OS_IOS) |
23 | #include <Availability.h> | |
24 | # if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_8 | |
23 | 25 | //http://www.cocoachina.com/macdev/cocoa/2010/0201/453.html |
24 | 26 | #include <CoreServices/CoreServices.h> |
27 | # else | |
28 | /* MAC OSX 10.8+ has deprecated the UpdateSystemActivity stuff in favor of a new API.. */ | |
29 | # include <IOKit/pwr_mgt/IOPMLib.h> | |
30 | # endif | |
25 | 31 | #endif //Q_OS_MAC |
26 | 32 | #ifdef Q_OS_WIN |
27 | 33 | #include <QAbstractEventDispatcher> |
148 | 154 | } |
149 | 155 | #endif //Q_OS_LINUX |
150 | 156 | ssTimerId = 0; |
157 | osxIOPMAssertionId = 0U; // mac >=10.8 only | |
151 | 158 | retrieveState(); |
152 | 159 | } |
153 | 160 | |
157 | 164 | #ifdef Q_OS_LINUX |
158 | 165 | if (xlib.isLoaded()) |
159 | 166 | xlib.unload(); |
167 | #elif defined(Q_OS_MAC) && !defined(Q_OS_IOS) | |
168 | # if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_8 | |
169 | if (osxIOPMAssertionId) { | |
170 | IOPMAssertionRelease((IOPMAssertionID)osxIOPMAssertionId); | |
171 | osxIOPMAssertionId = 0U; | |
172 | } | |
173 | # endif | |
160 | 174 | #endif |
161 | 175 | } |
162 | 176 | |
309 | 323 | if (e->timerId() != ssTimerId) |
310 | 324 | return; |
311 | 325 | #if defined(Q_OS_MAC) && !defined(Q_OS_IOS) |
326 | # if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_8 | |
312 | 327 | UpdateSystemActivity(OverallAct); |
328 | # else // OSX >= 10.8, use new API | |
329 | IOPMAssertionID assertionId = osxIOPMAssertionId; | |
330 | IOReturn r = IOPMAssertionDeclareUserActivity(CFSTR("QtAVScreenSaver"),kIOPMUserActiveLocal,&assertionId); | |
331 | if (r == kIOReturnSuccess) { | |
332 | osxIOPMAssertionId = assertionId; | |
333 | } | |
334 | # endif | |
313 | 335 | return; |
314 | 336 | #endif //Q_OS_MAC |
315 | 337 | #ifdef Q_OS_LINUX |
32 | 32 | int allowExposures; |
33 | 33 | #endif //Q_OS_LINUX |
34 | 34 | int ssTimerId; //for mac |
35 | quint32 osxIOPMAssertionId; // for mac OSX >= 10.8 | |
35 | 36 | }; |
36 | 37 | |
37 | 38 | #endif // SCREENSAVER_H |
34 | 34 | #include <QtCore/QStandardPaths> |
35 | 35 | #endif |
36 | 36 | #include <QtDebug> |
37 | #include <QMutex> | |
37 | 38 | |
38 | 39 | #ifdef Q_OS_WINRT |
39 | 40 | #include <wrl.h> |
97 | 98 | return qInstallMsgHandler(MsgHandlerWrapper::handler); |
98 | 99 | } |
99 | 100 | #endif |
101 | ||
102 | QMutex loggerMutex; | |
100 | 103 | void Logger(QtMsgType type, const QMessageLogContext &, const QString& qmsg) |
101 | 104 | { |
105 | // QFile is not thread-safe | |
106 | QMutexLocker locker(&loggerMutex); | |
107 | ||
102 | 108 | const QByteArray msgArray = qmsg.toUtf8(); |
103 | 109 | const char* msg = msgArray.constData(); |
104 | 110 | switch (type) { |
296 | 296 | } |
297 | 297 | } |
298 | 298 | break; |
299 | ||
300 | case QEvent::MouseButtonDblClick: { | |
301 | QMouseEvent *me = static_cast<QMouseEvent*>(event); | |
302 | Qt::MouseButton mbt = me->button(); | |
303 | QWidget *mpWindow = static_cast<QWidget*>(player->parent()); | |
304 | if (mbt == Qt::LeftButton) { | |
305 | if (Qt::WindowFullScreen ==mpWindow->windowState()){ | |
306 | mpWindow->setWindowState(mpWindow->windowState() ^ Qt::WindowFullScreen); | |
307 | }else{ | |
308 | mpWindow->showFullScreen(); | |
309 | } | |
310 | } | |
311 | break; | |
312 | } | |
313 | ||
299 | 314 | case QEvent::GraphicsSceneContextMenu: { |
300 | 315 | QGraphicsSceneContextMenuEvent *e = static_cast<QGraphicsSceneContextMenuEvent*>(event); |
301 | 316 | showMenu(e->screenPos()); |
345 | 360 | } |
346 | 361 | return false; |
347 | 362 | } |
363 | ||
348 | 364 | if (event->type() == QEvent::MouseButtonPress) { |
349 | 365 | QMouseEvent *me = static_cast<QMouseEvent*>(event); |
350 | 366 | Qt::MouseButton mbt = me->button(); |
110 | 110 | , mpSubtitle(0) |
111 | 111 | , m_preview(0) |
112 | 112 | , m_shader(NULL) |
113 | , m_glsl(NULL) | |
113 | 114 | { |
114 | 115 | #if defined(Q_OS_MACX) && QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) |
115 | 116 | QApplication::setStyle(QStyleFactory::create("Fusion")); |
548 | 549 | connect(mpTimeSlider, SIGNAL(onLeave()), SLOT(onTimeSliderLeave())); |
549 | 550 | connect(mpTimeSlider, SIGNAL(onHover(int,int)), SLOT(onTimeSliderHover(int,int))); |
550 | 551 | connect(&Config::instance(), SIGNAL(userShaderEnabledChanged()), SLOT(onUserShaderChanged())); |
552 | connect(&Config::instance(), SIGNAL(intermediateFBOChanged()), SLOT(onUserShaderChanged())); | |
551 | 553 | connect(&Config::instance(), SIGNAL(fragHeaderChanged()), SLOT(onUserShaderChanged())); |
552 | 554 | connect(&Config::instance(), SIGNAL(fragSampleChanged()), SLOT(onUserShaderChanged())); |
553 | 555 | connect(&Config::instance(), SIGNAL(fragPostProcessChanged()), SLOT(onUserShaderChanged())); |
1324 | 1326 | m_preview->preview(); |
1325 | 1327 | const int w = Config::instance().previewWidth(); |
1326 | 1328 | const int h = Config::instance().previewHeight(); |
1327 | m_preview->setWindowFlags(m_preview->windowFlags() |Qt::FramelessWindowHint|Qt::WindowStaysOnTopHint); | |
1329 | m_preview->setWindowFlags(Qt::Tool |Qt::FramelessWindowHint|Qt::WindowStaysOnTopHint); | |
1328 | 1330 | m_preview->resize(w, h); |
1329 | 1331 | m_preview->move(gpos - QPoint(w/2, h)); |
1330 | 1332 | m_preview->show(); |
1332 | 1334 | |
1333 | 1335 | void MainWindow::onTimeSliderLeave() |
1334 | 1336 | { |
1335 | if (m_preview && m_preview->isVisible()) | |
1336 | m_preview->hide(); | |
1337 | /*if (m_preview && m_preview->isVisible()) | |
1338 | m_preview->hide();*/ | |
1339 | if (!m_preview) | |
1340 | { | |
1341 | return; | |
1342 | } | |
1343 | if (m_preview->isVisible()) | |
1344 | { | |
1345 | m_preview->close(); | |
1346 | } | |
1347 | delete m_preview; | |
1348 | m_preview = NULL; | |
1337 | 1349 | } |
1338 | 1350 | |
1339 | 1351 | void MainWindow::handleError(const AVError &e) |
1532 | 1544 | return; |
1533 | 1545 | #ifndef QT_NO_OPENGL |
1534 | 1546 | if (Config::instance().userShaderEnabled()) { |
1547 | if (Config::instance().intermediateFBO()) { | |
1548 | if (!m_glsl) | |
1549 | m_glsl = new GLSLFilter(this); | |
1550 | m_glsl->installTo(mpRenderer); | |
1551 | } else { | |
1552 | if (m_glsl) | |
1553 | m_glsl->uninstall(); | |
1554 | } | |
1535 | 1555 | if (!m_shader) |
1536 | 1556 | m_shader = new DynamicShaderObject(this); |
1537 | 1557 | m_shader->setHeader(Config::instance().fragHeader()); |
38 | 38 | class SubtitleFilter; |
39 | 39 | class VideoPreviewWidget; |
40 | 40 | class DynamicShaderObject; |
41 | class GLSLFilter; | |
41 | 42 | } |
42 | 43 | QT_BEGIN_NAMESPACE |
43 | 44 | class QMenu; |
212 | 213 | QtAV::SubtitleFilter *mpSubtitle; |
213 | 214 | QtAV::VideoPreviewWidget *m_preview; |
214 | 215 | QtAV::DynamicShaderObject *m_shader; |
216 | QtAV::GLSLFilter *m_glsl; | |
215 | 217 | }; |
216 | 218 | |
217 | 219 |
18 | 18 | <key>CFBundleName</key> |
19 | 19 | <string>QtAV Player</string> |
20 | 20 | <key>CFBundleShortVersionString</key> |
21 | <string>1.12</string> | |
21 | <string>1.13</string> | |
22 | 22 | <key>CFBundleVersion</key> |
23 | <string>1.12.0</string> | |
23 | <string>1.13.0</string> | |
24 | 24 | <key>LSRequiresIPhoneOS</key> |
25 | 25 | <true/> |
26 | 26 | <key>UIFileSharingEnabled</key> |
30 | 30 | RC_ICONS = $$PROJECTROOT/src/QtAV.ico |
31 | 31 | QMAKE_TARGET_COMPANY = "Shanghai University->S3 Graphics->Deepin | wbsecg1@gmail.com" |
32 | 32 | QMAKE_TARGET_DESCRIPTION = "QtAV Multimedia framework. http://qtav.org" |
33 | QMAKE_TARGET_COPYRIGHT = "Copyright (C) 2012-2017 WangBin, wbsecg1@gmail.com" | |
33 | QMAKE_TARGET_COPYRIGHT = "Copyright (C) 2012-2019 WangBin, wbsecg1@gmail.com" | |
34 | 34 | QMAKE_TARGET_PRODUCT = "QtAV $$1" |
35 | 35 | export(RC_ICONS) |
36 | 36 | export(QMAKE_TARGET_COMPANY) |
0 | #include "VUMeterFilter.h" | |
1 | #include <QtAV/AudioFrame.h> | |
2 | #include <cmath> | |
3 | ||
4 | VUMeterFilter::VUMeterFilter(QObject *parent) | |
5 | : AudioFilter(parent) | |
6 | , mLeft(0) | |
7 | , mRight(0) | |
8 | { | |
9 | } | |
10 | ||
11 | void VUMeterFilter::process(Statistics *statistics, AudioFrame *frame) | |
12 | { | |
13 | if (!frame) | |
14 | return; | |
15 | const AudioFormat& af = frame->format(); | |
16 | int step = frame->channelCount(); | |
17 | const quint8* data[2]; | |
18 | data[0] = frame->constBits(0); | |
19 | if (frame->planeCount() > 0) { | |
20 | step = 1; | |
21 | data[1] = frame->constBits(1); | |
22 | } else { | |
23 | data[1] = data[0] + step*af.sampleSize(); | |
24 | } | |
25 | const int s = frame->samplesPerChannel(); | |
26 | const int r = 1; | |
27 | float level[2]; | |
28 | if (af.isFloat()) { | |
29 | float max[2]; | |
30 | max[0] = max[1] = 0; | |
31 | const float *p0 = (float*)data[0]; | |
32 | const float *p1 = (float*)data[1]; | |
33 | for (int i = 0; i < s; i+=step) { | |
34 | max[0] = qMax(max[0], qAbs(p0[i])); | |
35 | max[1] = qMax(max[1], qAbs(p1[i])); | |
36 | } | |
37 | level[0] = 20.0f * log10(max[0]); | |
38 | level[1] = 20.0f * log10(max[1]); | |
39 | } else if (!af.isUnsigned()) { | |
40 | const int b = af.sampleSize(); | |
41 | if (b == 2) { | |
42 | qint16 max[2]; | |
43 | max[0] = max[1] = 0; | |
44 | const qint16 *p0 = (qint16*)data[0]; | |
45 | const qint16 *p1 = (qint16*)data[1]; | |
46 | for (int i = 0; i < s; i+=step) { | |
47 | max[0] = qMax(max[0], qAbs(p0[i])); | |
48 | max[1] = qMax(max[1], qAbs(p1[i])); | |
49 | } | |
50 | level[0] = 20.0f * log10((double)max[0]/(double)INT16_MAX); | |
51 | level[1] = 20.0f * log10((double)max[1]/(double)INT16_MAX); | |
52 | } | |
53 | } | |
54 | if (!qFuzzyCompare(level[0], mLeft)) { | |
55 | mLeft = level[0]; | |
56 | Q_EMIT leftLevelChanged(mLeft); | |
57 | } | |
58 | if (!qFuzzyCompare(level[1], mRight)) { | |
59 | mRight = level[1]; | |
60 | Q_EMIT rightLevelChanged(mRight); | |
61 | } | |
62 | //qDebug("db: %d --- %d", mLeft, mRight); | |
63 | } |
0 | #ifndef VUMeterFilter_H | |
1 | #define VUMeterFilter_H | |
2 | #include <QtAV/Filter.h> | |
3 | ||
4 | using namespace QtAV; | |
5 | class VUMeterFilter : public AudioFilter | |
6 | { | |
7 | Q_OBJECT | |
8 | Q_PROPERTY(int leftLevel READ leftLevel WRITE setLeftLevel NOTIFY leftLevelChanged) | |
9 | Q_PROPERTY(int rightLevel READ rightLevel WRITE setRightLevel NOTIFY rightLevelChanged) | |
10 | public: | |
11 | VUMeterFilter(QObject* parent = NULL); | |
12 | int leftLevel() const {return mLeft;} | |
13 | void setLeftLevel(int value) {mLeft = value;} | |
14 | int rightLevel() const {return mRight;} | |
15 | void setRightLevel(int value) {mRight = value;} | |
16 | Q_SIGNALS: | |
17 | void leftLevelChanged(int value); | |
18 | void rightLevelChanged(int value); | |
19 | protected: | |
20 | void process(Statistics* statistics, AudioFrame* frame = 0) Q_DECL_OVERRIDE; | |
21 | private: | |
22 | int mLeft; | |
23 | int mRight; | |
24 | }; | |
25 | ||
26 | #endif // VUMeterFilter_H |
0 | #include "VUMeterFilter.h" | |
1 | ||
2 | #include <QtGui/QGuiApplication> | |
3 | #include <QQuickItem> | |
4 | #include <QtQml/QQmlEngine> | |
5 | #include <QtQml/QQmlContext> | |
6 | #include <QtQml/QQmlApplicationEngine> | |
7 | int main(int argc, char** argv) | |
8 | { | |
9 | qmlRegisterType<VUMeterFilter>("com.qtav.vumeter", 1, 0, "VUMeterFilter"); | |
10 | QGuiApplication app(argc, argv); | |
11 | ||
12 | QQmlApplicationEngine engine; | |
13 | engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); | |
14 | ||
15 | return app.exec(); | |
16 | } |
0 | import QtQuick 2.6 | |
1 | import QtQuick.Window 2.2 | |
2 | import QtQuick.Dialogs 1.0 | |
3 | import QtAV 1.7 | |
4 | import com.qtav.vumeter 1.0 | |
5 | Window { | |
6 | visible: true | |
7 | width: 640 | |
8 | height: 480 | |
9 | title: qsTr("QtAV VU Meter Filter. Press key 'O' to open a file") | |
10 | ||
11 | VUMeterFilter { | |
12 | id: vu | |
13 | } | |
14 | ||
15 | AudioFilter { | |
16 | id: afilter | |
17 | type: AudioFilter.UserFilter | |
18 | userFilter: vu | |
19 | } | |
20 | ||
21 | Video { | |
22 | id: video | |
23 | autoPlay: true | |
24 | anchors.fill: parent | |
25 | audioFilters: [afilter] | |
26 | Text { | |
27 | anchors.fill: parent | |
28 | color: "white" | |
29 | text: "dB left=" + vu.leftLevel + " right=" + vu.rightLevel | |
30 | } | |
31 | } | |
32 | ||
33 | Item { | |
34 | anchors.fill: parent | |
35 | focus: true | |
36 | Keys.onPressed: { | |
37 | switch (event.key) { | |
38 | case Qt.Key_O: | |
39 | fileDialog.open() | |
40 | break | |
41 | } | |
42 | } | |
43 | } | |
44 | FileDialog { | |
45 | id: fileDialog | |
46 | onAccepted: video.source = fileUrl | |
47 | } | |
48 | } |
0 | TEMPLATE = app | |
1 | CONFIG -= app_bundle | |
2 | ||
3 | QT += av qml quick | |
4 | CONFIG += c++11 | |
5 | ||
6 | HEADERS = VUMeterFilter.h | |
7 | SOURCES = main.cpp VUMeterFilter.cpp | |
8 | ||
9 | RESOURCES += qml.qrc | |
10 | ||
11 | # Additional import path used to resolve QML modules in Qt Creator's code model | |
12 | QML_IMPORT_PATH = | |
13 | ||
14 | # Additional import path used to resolve QML modules just for Qt Quick Designer | |
15 | QML_DESIGNER_IMPORT_PATH = | |
16 | ||
17 | # The following define makes your compiler emit warnings if you use | |
18 | # any feature of Qt which as been marked deprecated (the exact warnings | |
19 | # depend on your compiler). Please consult the documentation of the | |
20 | # deprecated API in order to know how to port your code away from it. | |
21 | DEFINES += QT_DEPRECATED_WARNINGS | |
22 | ||
23 | # You can also make your code fail to compile if you use deprecated APIs. | |
24 | # In order to do so, uncomment the following line. | |
25 | # You can also select to disable deprecated APIs only up to a certain version of Qt. | |
26 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 | |
27 | ||
28 | # Default rules for deployment. | |
29 | qnx: target.path = /tmp/$${TARGET}/bin | |
30 | else: unix:!android: target.path = /opt/$${TARGET}/bin | |
31 | !isEmpty(target.path): INSTALLS += target |
0 | # Copyright (c) 2017, Riverbank Computing Limited | |
1 | # All rights reserved. | |
2 | # | |
3 | # Redistribution and use in source and binary forms, with or without | |
4 | # modification, are permitted provided that the following conditions are met: | |
5 | # | |
6 | # 1. Redistributions of source code must retain the above copyright notice, | |
7 | # this list of conditions and the following disclaimer. | |
8 | # | |
9 | # 2. Redistributions in binary form must reproduce the above copyright notice, | |
10 | # this list of conditions and the following disclaimer in the documentation | |
11 | # and/or other materials provided with the distribution. | |
12 | # | |
13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
14 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
15 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
16 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE | |
17 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
18 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
19 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
20 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
21 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
22 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
23 | # POSSIBILITY OF SUCH DAMAGE. | |
24 | ||
25 | # This is v2.3 of this boilerplate. | |
26 | ||
27 | ||
28 | from distutils import sysconfig | |
29 | import glob | |
30 | import os | |
31 | import optparse | |
32 | import sys | |
33 | ||
34 | ||
35 | ############################################################################### | |
36 | # You shouldn't need to modify anything above this line. | |
37 | ############################################################################### | |
38 | ||
39 | ||
40 | class QtAVConfiguration(object): | |
41 | """ This class encapsulates all the module specific information needed by | |
42 | the rest of this script to implement a configure.py script for modules that | |
43 | build on top of PyQt. Functions implemented by the rest of this script | |
44 | that begin with an underscore are considered internal and shouldn't be | |
45 | called from here. | |
46 | """ | |
47 | ||
48 | # The name of the module as it would be used in an import statement. | |
49 | name = 'QtAV' | |
50 | ||
51 | # Set if support for C++ exceptions can be disabled. | |
52 | no_exceptions = True | |
53 | ||
54 | # The name (without the .pyi extension) of the name of the PEP 484 stub | |
55 | # file to be generated. If it is None or an empty string then a stub file | |
56 | # is not generated. | |
57 | pep484_stub_file = 'QtAV' | |
58 | ||
59 | @staticmethod | |
60 | def get_sip_file(target_configuration): | |
61 | """ Return the name of the module's .sip file. target_configuration is | |
62 | the target configuration. | |
63 | """ | |
64 | ||
65 | return 'sip/QtAV/QtAVmod.sip' | |
66 | ||
67 | @staticmethod | |
68 | def get_sip_installs(target_configuration): | |
69 | """ Return a tuple of the installation directory of the module's .sip | |
70 | files and a sequence of the names of each of the .sip files relative to | |
71 | the directory containing this configuration script. None is returned | |
72 | if the module's .sip files are not to be installed. | |
73 | target_configuration is the target configuration. | |
74 | """ | |
75 | ||
76 | if target_configuration.qtav_sip_dir == '': | |
77 | return None | |
78 | ||
79 | path = os.path.join(target_configuration.qtav_sip_dir, 'QtAV') | |
80 | files = glob.glob('sip/QtAV/*.sip') | |
81 | ||
82 | return path, files | |
83 | ||
84 | @staticmethod | |
85 | def get_qmake_configuration(target_configuration): | |
86 | """ Return a dict of qmake configuration values for CONFIG, DEFINES, | |
87 | INCLUDEPATH, LIBS and QT. If value names (i.e. dict keys) have either | |
88 | 'Qt4' or 'Qt5' prefixes then they are specific to the corresponding | |
89 | version of Qt. target_configuration is the target configuration. | |
90 | """ | |
91 | libs = r"-L%s/lib " % (target_configuration.qtav_base_dir) | |
92 | if sys.platform == "win32": | |
93 | libs += "-lQtAV1 -lQtAVWidgets1" | |
94 | else: | |
95 | libs += "-lQtAV -lQtAVWidgets" | |
96 | ||
97 | return {'QT': 'widgets opengl', | |
98 | 'INCLUDEPATH': os.path.join(target_configuration.qtav_base_dir, "include"), | |
99 | 'LIBS': libs} | |
100 | ||
101 | @staticmethod | |
102 | def get_mac_wrapped_library_file(target_configuration): | |
103 | """ Return the full pathname of the file that implements the library | |
104 | being wrapped by the module as it would be called on OS/X so that the | |
105 | module will reference it explicitly without DYLD_LIBRARY_PATH being | |
106 | set. If it is None or an empty string then the default is used. | |
107 | target_configuration is the target configuration. | |
108 | """ | |
109 | ||
110 | return None | |
111 | ||
112 | ||
113 | class QtAVWidgetsConfiguration(object): | |
114 | """ This class encapsulates all the module specific information needed by | |
115 | the rest of this script to implement a configure.py script for modules that | |
116 | build on top of PyQt. Functions implemented by the rest of this script | |
117 | that begin with an underscore are considered internal and shouldn't be | |
118 | called from here. | |
119 | """ | |
120 | ||
121 | # The name of the module as it would be used in an import statement. | |
122 | name = 'QtAVWidgets' | |
123 | ||
124 | # Set if support for C++ exceptions can be disabled. | |
125 | no_exceptions = True | |
126 | ||
127 | # The name (without the .pyi extension) of the name of the PEP 484 stub | |
128 | # file to be generated. If it is None or an empty string then a stub file | |
129 | # is not generated. | |
130 | pep484_stub_file = 'QtAVWidgets' | |
131 | ||
132 | @staticmethod | |
133 | def get_sip_file(target_configuration): | |
134 | """ Return the name of the module's .sip file. target_configuration is | |
135 | the target configuration. | |
136 | """ | |
137 | ||
138 | return 'sip/QtAVWidgets/QtAVWidgetsmod.sip' | |
139 | ||
140 | @staticmethod | |
141 | def get_sip_installs(target_configuration): | |
142 | """ Return a tuple of the installation directory of the module's .sip | |
143 | files and a sequence of the names of each of the .sip files relative to | |
144 | the directory containing this configuration script. None is returned | |
145 | if the module's .sip files are not to be installed. | |
146 | target_configuration is the target configuration. | |
147 | """ | |
148 | ||
149 | if target_configuration.qtav_sip_dir == '': | |
150 | return None | |
151 | ||
152 | path = os.path.join(target_configuration.qtav_sip_dir, 'QtAVWidgets') | |
153 | files = glob.glob('sip/QtAVWidgets/*.sip') | |
154 | ||
155 | return path, files | |
156 | ||
157 | @staticmethod | |
158 | def get_qmake_configuration(target_configuration): | |
159 | """ Return a dict of qmake configuration values for CONFIG, DEFINES, | |
160 | INCLUDEPATH, LIBS and QT. If value names (i.e. dict keys) have either | |
161 | 'Qt4' or 'Qt5' prefixes then they are specific to the corresponding | |
162 | version of Qt. target_configuration is the target configuration. | |
163 | """ | |
164 | libs = r"-L%s/lib " % (target_configuration.qtav_base_dir) | |
165 | if sys.platform == "win32": | |
166 | libs += "-lQtAV1 -lQtAVWidgets1" | |
167 | else: | |
168 | libs += "-lQtAV -lQtAVWidgets" | |
169 | ||
170 | return {'QT': 'widgets opengl', | |
171 | 'INCLUDEPATH': os.path.join(target_configuration.qtav_base_dir, "include"), | |
172 | 'LIBS': libs} | |
173 | ||
174 | @staticmethod | |
175 | def get_mac_wrapped_library_file(target_configuration): | |
176 | """ Return the full pathname of the file that implements the library | |
177 | being wrapped by the module as it would be called on OS/X so that the | |
178 | module will reference it explicitly without DYLD_LIBRARY_PATH being | |
179 | set. If it is None or an empty string then the default is used. | |
180 | target_configuration is the target configuration. | |
181 | """ | |
182 | ||
183 | return None | |
184 | ||
185 | ||
186 | class PackageConfiguration(object): | |
187 | """ This class encapsulates all the package specific information needed by | |
188 | the rest of this script to implement a configure.py script for modules that | |
189 | build on top of PyQt. Functions implemented by the rest of this script | |
190 | that begin with an underscore are considered internal and shouldn't be | |
191 | called from here. | |
192 | """ | |
193 | ||
194 | # The descriptive name of the module. This is used in help text and error | |
195 | # messages. | |
196 | descriptive_name = "PyQtAV" | |
197 | ||
198 | # The version of the module as a string. Set it to None if you don't | |
199 | # provide version information. | |
200 | version = '1.12.0' | |
201 | ||
202 | # The sequence of module configurations that make up the package. | |
203 | modules = (QtAVConfiguration(), QtAVWidgetsConfiguration()) | |
204 | ||
205 | # Set if a configuration script is provided that handles versions of PyQt4 | |
206 | # prior to v4.10 (i.e. versions where the pyqtconfig.py module is | |
207 | # available). If provided the script must be called configure-old.py and | |
208 | # be in the same directory as this script. | |
209 | legacy_configuration_script = False | |
210 | ||
211 | # The minimum version of SIP that is required. This should be a | |
212 | # dot-separated string of two or three integers (e.g. '1.0', '4.10.3'). If | |
213 | # it is None or an empty string then the version is not checked. | |
214 | minimum_sip_version = '4.19.1' | |
215 | ||
216 | # Set if the module supports redefining 'protected' as 'public'. | |
217 | protected_is_public_is_supported = True | |
218 | ||
219 | # Set if the module supports PyQt4. | |
220 | pyqt4_is_supported = False | |
221 | ||
222 | # Set if the module supports PyQt5. | |
223 | pyqt5_is_supported = True | |
224 | ||
225 | # Set if the PyQt5 support is the default. It is ignored unless both | |
226 | # 'pyqt4_is_supported' and 'pyqt5_is_supported' are set. | |
227 | pyqt5_is_default = True | |
228 | ||
229 | # The name (without the .api extension) of the name of the QScintilla API | |
230 | # file to be generated. If it is None or an empty string then an API file | |
231 | # is not generated. | |
232 | qscintilla_api_file = 'PyQtAV' | |
233 | ||
234 | # The email address that will be included when an error in the script is | |
235 | # detected. Leave it blank if you don't want to include an address. | |
236 | support_email_address = 'support@riverbankcomputing.com' | |
237 | ||
238 | # Set if the user can provide a configuration file. It is normally only | |
239 | # used if cross-compilation is supported. | |
240 | user_configuration_file_is_supported = True | |
241 | ||
242 | # Set if the user is allowed to pass PyQt sip flags on the command line. | |
243 | # It is normally only used if cross-compilation is supported. It is | |
244 | # ignored unless at least one of 'pyqt4_is_supported' or | |
245 | # 'pyqt5_is_supported' is set. | |
246 | user_pyqt_sip_flags_is_supported = True | |
247 | ||
248 | @staticmethod | |
249 | def init_target_configuration(target_configuration): | |
250 | """ Perform any module specific initialisation of the target | |
251 | target configuration. Typically this is the initialisation of module | |
252 | specific attributes. To avoid name clashes attributes should be given | |
253 | a module specific prefix. target_configuration is the target | |
254 | configuration. | |
255 | """ | |
256 | ||
257 | target_configuration.qtav_version = None | |
258 | target_configuration.qtav_base_dir = None | |
259 | target_configuration.qtav_sip_dir = None | |
260 | ||
261 | @staticmethod | |
262 | def init_optparser(optparser, target_configuration): | |
263 | """ Perform any module specific initialisation of the command line | |
264 | option parser. To avoid name clashes destination attributes should be | |
265 | given a module specific prefix. optparser is the option parser. | |
266 | target_configuration is the target configuration. | |
267 | """ | |
268 | ||
269 | optparser.add_option('--qtav-version', dest='qtav_version', | |
270 | type='string', metavar="VERSION", | |
271 | help="the QtAV version number (eg. 1.12.0)") | |
272 | ||
273 | optparser.add_option('--qtav-base-dir', '-v', | |
274 | dest='qtav_base_dir', type='string', default=None, | |
275 | action='callback', callback=optparser_store_abspath_dir, | |
276 | metavar="DIR", | |
277 | help="the base directory that contains QtAV installation ") | |
278 | ||
279 | optparser.add_option("--no-sip-files", action="store_true", | |
280 | default=False, dest="qtav_no_sip_files", | |
281 | help="disable the installation of the .sip files " | |
282 | "[default: enabled]") | |
283 | ||
284 | @staticmethod | |
285 | def apply_options(target_configuration, options): | |
286 | """ Apply the module specific command line options to the target | |
287 | configuration. target_configuration is the target configuration. | |
288 | options are the parsed options. | |
289 | """ | |
290 | ||
291 | # Historically Digia have been incapable of remembering to update the | |
292 | # version number in a new release of add-on libraries so we allow the | |
293 | # user to specify it while defaulting to the Qt version. | |
294 | qtav_version_str = options.qtav_version | |
295 | if qtav_version_str is None: | |
296 | qtav_version_str = target_configuration.qt_version_str | |
297 | ||
298 | target_configuration.qtav_version = version_from_string( | |
299 | qtav_version_str) | |
300 | ||
301 | if target_configuration.qtav_version is None: | |
302 | error("%s is not a valid QtAV version." % qtav_version_str) | |
303 | ||
304 | if target_configuration.qtav_version >= 0x020000 and target_configuration.pyqt_package != 'PyQt5': | |
305 | error("QtAV v%s requires PyQt5." % options.qtav_version) | |
306 | ||
307 | if options.qtav_base_dir is not None: | |
308 | target_configuration.qtav_base_dir = options.qtav_base_dir | |
309 | else: | |
310 | error("--qtav-base-dir must be spcified") | |
311 | ||
312 | if options.qtav_no_sip_files: | |
313 | target_configuration.qtav_sip_dir = '' | |
314 | else: | |
315 | target_configuration.qtav_sip_dir = os.path.join(target_configuration.qtav_base_dir, 'share', 'sip') | |
316 | ||
317 | @staticmethod | |
318 | def check_package(target_configuration): | |
319 | """ Perform any package specific checks now that the target | |
320 | configuration is complete. target_configuration is the target | |
321 | configuration. | |
322 | """ | |
323 | ||
324 | # Nothing to do. | |
325 | ||
326 | @staticmethod | |
327 | def inform_user(target_configuration): | |
328 | """ Inform the user about module specific configuration information. | |
329 | target_configuration is the target configuration. | |
330 | """ | |
331 | ||
332 | major = (target_configuration.qtav_version >> 16) & 0xff | |
333 | minor = (target_configuration.qtav_version >> 8) & 0xff | |
334 | patch = target_configuration.qtav_version & 0xff | |
335 | version = '%d.%d.%d' % (major, minor, patch) | |
336 | ||
337 | inform("QtAV %s is being used." % version) | |
338 | ||
339 | if target_configuration.qtav_sip_dir != '': | |
340 | inform( | |
341 | "The QtAV .sip files will be installed in %s." % | |
342 | target_configuration.qtav_sip_dir) | |
343 | ||
344 | @staticmethod | |
345 | def pre_code_generation(target_config): | |
346 | """ Perform any module specific initialisation prior to generating the | |
347 | code. target_config is the target configuration. | |
348 | """ | |
349 | ||
350 | # Nothing to do. | |
351 | ||
352 | @staticmethod | |
353 | def get_sip_flags(target_configuration): | |
354 | """ Return the list of module-specific flags to pass to SIP. | |
355 | target_configuration is the target configuration. | |
356 | """ | |
357 | ||
358 | major = (target_configuration.qtav_version >> 16) & 0xff | |
359 | minor = (target_configuration.qtav_version >> 8) & 0xff | |
360 | patch = target_configuration.qtav_version & 0xff | |
361 | version_tag = 'QtAV_%d_%d_%d' % (major, minor, patch) | |
362 | ||
363 | return ['-t', version_tag] | |
364 | ||
365 | ||
366 | ############################################################################### | |
367 | # You shouldn't need to modify anything below this line. | |
368 | ############################################################################### | |
369 | ||
370 | ||
371 | def error(msg): | |
372 | """ Display an error message and terminate. msg is the text of the error | |
373 | message. | |
374 | """ | |
375 | ||
376 | sys.stderr.write(_format("Error: " + msg) + "\n") | |
377 | sys.exit(1) | |
378 | ||
379 | ||
380 | def inform(msg): | |
381 | """ Display an information message. msg is the text of the error message. | |
382 | """ | |
383 | ||
384 | sys.stdout.write(_format(msg) + "\n") | |
385 | ||
386 | ||
387 | def quote(path): | |
388 | """ Return a path with quotes added if it contains spaces. path is the | |
389 | path. | |
390 | """ | |
391 | ||
392 | if ' ' in path: | |
393 | path = '"%s"' % path | |
394 | ||
395 | return path | |
396 | ||
397 | ||
398 | def optparser_store_abspath(option, opt_str, value, parser): | |
399 | """ An optparser callback that saves an option as an absolute pathname. """ | |
400 | ||
401 | setattr(parser.values, option.dest, os.path.abspath(value)) | |
402 | ||
403 | ||
404 | def optparser_store_abspath_dir(option, opt_str, value, parser): | |
405 | """ An optparser callback that saves an option as the absolute pathname | |
406 | of an existing directory. | |
407 | """ | |
408 | ||
409 | if not os.path.isdir(value): | |
410 | raise optparse.OptionValueError("'%s' is not a directory" % value) | |
411 | ||
412 | setattr(parser.values, option.dest, os.path.abspath(value)) | |
413 | ||
414 | ||
415 | def optparser_store_abspath_exe(option, opt_str, value, parser): | |
416 | """ An optparser callback that saves an option as the absolute pathname | |
417 | of an existing executable. | |
418 | """ | |
419 | ||
420 | if not os.access(value, os.X_OK): | |
421 | raise optparse.OptionValueError("'%s' is not an executable" % value) | |
422 | ||
423 | setattr(parser.values, option.dest, os.path.abspath(value)) | |
424 | ||
425 | ||
426 | def read_define(filename, define): | |
427 | """ Read the value of a #define from a file. filename is the name of the | |
428 | file. define is the name of the #define. None is returned if there was no | |
429 | such #define. | |
430 | """ | |
431 | ||
432 | f = open(filename) | |
433 | ||
434 | for l in f: | |
435 | wl = l.split() | |
436 | if len(wl) >= 3 and wl[0] == "#define" and wl[1] == define: | |
437 | # Take account of embedded spaces. | |
438 | value = ' '.join(wl[2:])[1:-1] | |
439 | break | |
440 | else: | |
441 | value = None | |
442 | ||
443 | f.close() | |
444 | ||
445 | return value | |
446 | ||
447 | ||
448 | def version_from_string(version_str): | |
449 | """ Convert a version string of the form m, m.n or m.n.o to an encoded | |
450 | version number (or None if it was an invalid format). version_str is the | |
451 | version string. | |
452 | """ | |
453 | ||
454 | parts = version_str.split('.') | |
455 | if not isinstance(parts, list): | |
456 | return None | |
457 | ||
458 | if len(parts) == 1: | |
459 | parts.append('0') | |
460 | ||
461 | if len(parts) == 2: | |
462 | parts.append('0') | |
463 | ||
464 | if len(parts) != 3: | |
465 | return None | |
466 | ||
467 | version = 0 | |
468 | ||
469 | for part in parts: | |
470 | try: | |
471 | v = int(part) | |
472 | except ValueError: | |
473 | return None | |
474 | ||
475 | version = (version << 8) + v | |
476 | ||
477 | return version | |
478 | ||
479 | ||
480 | def _format(msg, left_margin=0, right_margin=78): | |
481 | """ Format a message by inserting line breaks at appropriate places. msg | |
482 | is the text of the message. left_margin is the position of the left | |
483 | margin. right_margin is the position of the right margin. Returns the | |
484 | formatted message. | |
485 | """ | |
486 | ||
487 | curs = left_margin | |
488 | fmsg = " " * left_margin | |
489 | ||
490 | for w in msg.split(): | |
491 | l = len(w) | |
492 | if curs != left_margin and curs + l > right_margin: | |
493 | fmsg = fmsg + "\n" + (" " * left_margin) | |
494 | curs = left_margin | |
495 | ||
496 | if curs > left_margin: | |
497 | fmsg = fmsg + " " | |
498 | curs = curs + 1 | |
499 | ||
500 | fmsg = fmsg + w | |
501 | curs = curs + l | |
502 | ||
503 | return fmsg | |
504 | ||
505 | ||
506 | class _ConfigurationFileParser: | |
507 | """ A parser for configuration files. """ | |
508 | ||
509 | def __init__(self, config_file): | |
510 | """ Read and parse a configuration file. """ | |
511 | ||
512 | self._config = {} | |
513 | self._extrapolating = [] | |
514 | ||
515 | cfg = open(config_file) | |
516 | line_nr = 0 | |
517 | last_name = None | |
518 | ||
519 | section = '' | |
520 | section_config = {} | |
521 | self._config[section] = section_config | |
522 | ||
523 | for l in cfg: | |
524 | line_nr += 1 | |
525 | ||
526 | # Strip comments. | |
527 | l = l.split('#')[0] | |
528 | ||
529 | # See if this might be part of a multi-line. | |
530 | multiline = (last_name is not None and len(l) != 0 and l[0] == ' ') | |
531 | ||
532 | l = l.strip() | |
533 | ||
534 | if l == '': | |
535 | last_name = None | |
536 | continue | |
537 | ||
538 | # See if this is a new section. | |
539 | if l[0] == '[' and l[-1] == ']': | |
540 | section = l[1:-1].strip() | |
541 | if section == '': | |
542 | error( | |
543 | "%s:%d: Empty section name." % ( | |
544 | config_file, line_nr)) | |
545 | ||
546 | if section in self._config: | |
547 | error( | |
548 | "%s:%d: Section '%s' defined more than once." % ( | |
549 | config_file, line_nr, section)) | |
550 | ||
551 | section_config = {} | |
552 | self._config[section] = section_config | |
553 | ||
554 | last_name = None | |
555 | continue | |
556 | ||
557 | parts = l.split('=', 1) | |
558 | if len(parts) == 2: | |
559 | name = parts[0].strip() | |
560 | value = parts[1].strip() | |
561 | elif multiline: | |
562 | name = last_name | |
563 | value = section_config[last_name] | |
564 | value += ' ' + l | |
565 | else: | |
566 | name = value = '' | |
567 | ||
568 | if name == '' or value == '': | |
569 | error("%s:%d: Invalid line." % (config_file, line_nr)) | |
570 | ||
571 | section_config[name] = value | |
572 | last_name = name | |
573 | ||
574 | cfg.close() | |
575 | ||
576 | def sections(self): | |
577 | """ Return the list of sections, excluding the default one. """ | |
578 | ||
579 | return [s for s in self._config.keys() if s != ''] | |
580 | ||
581 | def preset(self, name, value): | |
582 | """ Add a preset value to the configuration. """ | |
583 | ||
584 | self._config[''][name] = value | |
585 | ||
586 | def get(self, section, name, default=None): | |
587 | """ Get a configuration value while extrapolating. """ | |
588 | ||
589 | # Get the name from the section, or the default section. | |
590 | value = self._config[section].get(name) | |
591 | if value is None: | |
592 | value = self._config[''].get(name) | |
593 | if value is None: | |
594 | if default is None: | |
595 | error( | |
596 | "Configuration file references non-existent name " | |
597 | "'%s'." % name) | |
598 | ||
599 | return default | |
600 | ||
601 | # Handle any extrapolations. | |
602 | parts = value.split('%(', 1) | |
603 | while len(parts) == 2: | |
604 | prefix, tail = parts | |
605 | ||
606 | parts = tail.split(')', 1) | |
607 | if len(parts) != 2: | |
608 | error( | |
609 | "Configuration file contains unterminated " | |
610 | "extrapolated name '%s'." % tail) | |
611 | ||
612 | xtra_name, suffix = parts | |
613 | ||
614 | if xtra_name in self._extrapolating: | |
615 | error( | |
616 | "Configuration file contains a recursive reference to " | |
617 | "'%s'." % xtra_name) | |
618 | ||
619 | self._extrapolating.append(xtra_name) | |
620 | xtra_value = self.get(section, xtra_name) | |
621 | self._extrapolating.pop() | |
622 | ||
623 | value = prefix + xtra_value + suffix | |
624 | ||
625 | parts = value.split('%(', 1) | |
626 | ||
627 | return value | |
628 | ||
629 | def getboolean(self, section, name, default): | |
630 | """ Get a boolean configuration value while extrapolating. """ | |
631 | ||
632 | value = self.get(section, name, default) | |
633 | ||
634 | # In case the default was returned. | |
635 | if isinstance(value, bool): | |
636 | return value | |
637 | ||
638 | if value in ('True', 'true', '1'): | |
639 | return True | |
640 | ||
641 | if value in ('False', 'false', '0'): | |
642 | return False | |
643 | ||
644 | error( | |
645 | "Configuration file contains invalid boolean value for " | |
646 | "'%s'." % name) | |
647 | ||
648 | def getlist(self, section, name, default): | |
649 | """ Get a list configuration value while extrapolating. """ | |
650 | ||
651 | value = self.get(section, name, default) | |
652 | ||
653 | # In case the default was returned. | |
654 | if isinstance(value, list): | |
655 | return value | |
656 | ||
657 | return value.split() | |
658 | ||
659 | ||
660 | class _HostPythonConfiguration: | |
661 | """ A container for the host Python configuration. """ | |
662 | ||
663 | def __init__(self): | |
664 | """ Initialise the configuration. """ | |
665 | ||
666 | self.platform = sys.platform | |
667 | self.version = sys.hexversion >> 8 | |
668 | ||
669 | self.inc_dir = sysconfig.get_python_inc() | |
670 | self.venv_inc_dir = sysconfig.get_python_inc(prefix=sys.prefix) | |
671 | self.module_dir = sysconfig.get_python_lib(plat_specific=1) | |
672 | self.debug = hasattr(sys, 'gettotalrefcount') | |
673 | ||
674 | if sys.platform == 'win32': | |
675 | try: | |
676 | # Python v3.3 and later. | |
677 | base_prefix = sys.base_prefix | |
678 | ||
679 | except AttributeError: | |
680 | try: | |
681 | # virtualenv for Python v2. | |
682 | base_prefix = sys.real_prefix | |
683 | ||
684 | except AttributeError: | |
685 | # We can't detect the base prefix in Python v3 prior to | |
686 | # v3.3. | |
687 | base_prefix = sys.prefix | |
688 | ||
689 | self.data_dir = sys.prefix | |
690 | self.lib_dir = base_prefix + '\\libs' | |
691 | else: | |
692 | self.data_dir = sys.prefix + '/share' | |
693 | self.lib_dir = sys.prefix + '/lib' | |
694 | ||
695 | ||
696 | class _TargetQtConfiguration: | |
697 | """ A container for the target Qt configuration. """ | |
698 | ||
699 | def __init__(self, qmake): | |
700 | """ Initialise the configuration. qmake is the full pathname of the | |
701 | qmake executable that will provide the configuration. | |
702 | """ | |
703 | ||
704 | pipe = os.popen(quote(qmake) + ' -query') | |
705 | ||
706 | for l in pipe: | |
707 | l = l.strip() | |
708 | ||
709 | tokens = l.split(':', 1) | |
710 | if isinstance(tokens, list): | |
711 | if len(tokens) != 2: | |
712 | error("Unexpected output from qmake: '%s'\n" % l) | |
713 | ||
714 | name, value = tokens | |
715 | else: | |
716 | name = tokens | |
717 | value = None | |
718 | ||
719 | name = name.replace('/', '_') | |
720 | ||
721 | setattr(self, name, value) | |
722 | ||
723 | pipe.close() | |
724 | ||
725 | ||
726 | class _TargetConfiguration: | |
727 | """ A container for the target configuration. """ | |
728 | ||
729 | def __init__(self, pkg_config): | |
730 | """ Initialise the configuration with default values. pkg_config is | |
731 | the package configuration. | |
732 | """ | |
733 | ||
734 | # Values based on the host Python configuration. | |
735 | py_config = _HostPythonConfiguration() | |
736 | self.py_debug = py_config.debug | |
737 | self.py_platform = py_config.platform | |
738 | self.py_version = py_config.version | |
739 | self.py_module_dir = py_config.module_dir | |
740 | self.py_inc_dir = py_config.inc_dir | |
741 | self.py_venv_inc_dir = py_config.venv_inc_dir | |
742 | self.py_pylib_dir = py_config.lib_dir | |
743 | self.py_sip_dir = os.path.join(py_config.data_dir, 'sip') | |
744 | self.sip_inc_dir = py_config.venv_inc_dir | |
745 | ||
746 | # Remaining values. | |
747 | self.debug = False | |
748 | self.pyqt_sip_flags = None | |
749 | self.pyqt_version_str = '' | |
750 | self.qmake = self._find_exe('qmake') | |
751 | self.qmake_spec = '' | |
752 | self.qt_version = 0 | |
753 | self.qt_version_str = '' | |
754 | self.sip = self._find_exe('sip5', 'sip') | |
755 | self.sip_version = None | |
756 | self.sip_version_str = None | |
757 | self.sysroot = '' | |
758 | self.stubs_dir = '' | |
759 | ||
760 | self.prot_is_public = (self.py_platform.startswith('linux') or self.py_platform == 'darwin') | |
761 | ||
762 | if pkg_config.pyqt5_is_supported and pkg_config.pyqt4_is_supported: | |
763 | pyqt = 'PyQt5' if pkg_config.pyqt5_is_default else 'PyQt4' | |
764 | elif pkg_config.pyqt5_is_supported and not pkg_config.pyqt4_is_supported: | |
765 | pyqt = 'PyQt5' | |
766 | elif not pkg_config.pyqt5_is_supported and pkg_config.pyqt4_is_supported: | |
767 | pyqt = 'PyQt4' | |
768 | else: | |
769 | pyqt = None | |
770 | ||
771 | if pyqt is not None: | |
772 | self.module_dir = os.path.join(py_config.module_dir, pyqt) | |
773 | self.pyqt_sip_dir = os.path.join(self.py_sip_dir, pyqt) | |
774 | else: | |
775 | self.module_dir = py_config.module_dir | |
776 | self.pyqt_sip_dir = None | |
777 | ||
778 | self.pyqt_package = pyqt | |
779 | ||
780 | pkg_config.init_target_configuration(self) | |
781 | ||
782 | def update_from_configuration_file(self, config_file): | |
783 | """ Update the configuration with values from a file. config_file | |
784 | is the name of the configuration file. | |
785 | """ | |
786 | ||
787 | inform("Reading configuration from %s..." % config_file) | |
788 | ||
789 | parser = _ConfigurationFileParser(config_file) | |
790 | ||
791 | # Populate some presets from the command line. | |
792 | parser.preset('py_major', str(self.py_version >> 16)) | |
793 | parser.preset('py_minor', str((self.py_version >> 8) & 0xff)) | |
794 | parser.preset('sysroot', self.sysroot) | |
795 | ||
796 | if self.pyqt_package is None: | |
797 | section = '' | |
798 | else: | |
799 | # At the moment we only need to distinguish between PyQt4 and | |
800 | # PyQt5. If that changes we may need a --target-pyqt-version | |
801 | # command line option. | |
802 | pyqt_version = 0x050000 if self.pyqt_package == 'PyQt5' else 0x040000 | |
803 | ||
804 | # Find the section corresponding to the version of PyQt. | |
805 | section = None | |
806 | latest_section = -1 | |
807 | ||
808 | for name in parser.sections(): | |
809 | parts = name.split() | |
810 | if len(parts) != 2 or parts[0] != 'PyQt': | |
811 | continue | |
812 | ||
813 | section_pyqt_version = version_from_string(parts[1]) | |
814 | if section_pyqt_version is None: | |
815 | continue | |
816 | ||
817 | # Major versions must match. | |
818 | if section_pyqt_version >> 16 != pyqt_version >> 16: | |
819 | continue | |
820 | ||
821 | # It must be no later that the version of PyQt. | |
822 | if section_pyqt_version > pyqt_version: | |
823 | continue | |
824 | ||
825 | # Save it if it is the latest so far. | |
826 | if section_pyqt_version > latest_section: | |
827 | section = name | |
828 | latest_section = section_pyqt_version | |
829 | ||
830 | if section is None: | |
831 | error( | |
832 | "%s does not define a section that covers PyQt " | |
833 | "v%s." % (config_file, self.pyqt_version_str)) | |
834 | ||
835 | self.py_platform = parser.get(section, 'py_platform', self.py_platform) | |
836 | self.py_inc_dir = parser.get(section, 'py_inc_dir', self.py_inc_dir) | |
837 | self.py_venv_inc_dir = self.py_inc_dir | |
838 | self.py_pylib_dir = parser.get(section, 'py_pylib_dir', | |
839 | self.py_pylib_dir) | |
840 | ||
841 | self.sip_inc_dir = self.py_venv_inc_dir | |
842 | ||
843 | self.module_dir = parser.get(section, 'module_dir', self.module_dir) | |
844 | ||
845 | if self.pyqt_package is not None: | |
846 | self.py_sip_dir = parser.get(section, 'py_sip_dir', | |
847 | self.py_sip_dir) | |
848 | ||
849 | # Construct the SIP flags. | |
850 | flags = [] | |
851 | ||
852 | flags.append('-t') | |
853 | flags.append(self._get_platform_tag()) | |
854 | ||
855 | if self.pyqt_package == 'PyQt5': | |
856 | if self.qt_version < 0x050000: | |
857 | error("PyQt5 requires Qt v5.0 or later.") | |
858 | ||
859 | if self.qt_version > 0x060000: | |
860 | self.qt_version = 0x060000 | |
861 | else: | |
862 | if self.qt_version > 0x050000: | |
863 | self.qt_version = 0x050000 | |
864 | ||
865 | major = (self.qt_version >> 16) & 0xff | |
866 | minor = (self.qt_version >> 8) & 0xff | |
867 | patch = self.qt_version & 0xff | |
868 | ||
869 | flags.append('-t') | |
870 | flags.append('Qt_%d_%d_%d' % (major, minor, patch)) | |
871 | ||
872 | for feat in parser.getlist(section, 'pyqt_disabled_features', []): | |
873 | flags.append('-x') | |
874 | flags.append(feat) | |
875 | ||
876 | self.pyqt_sip_flags = ' '.join(flags) | |
877 | ||
878 | def _get_platform_tag(self): | |
879 | """ Return the tag for the target platform. """ | |
880 | ||
881 | # This replicates the logic in PyQt's configure scripts. | |
882 | if self.py_platform == 'win32': | |
883 | plattag = 'WS_WIN' | |
884 | elif self.py_platform == 'darwin': | |
885 | plattag = 'WS_MACX' | |
886 | else: | |
887 | plattag = 'WS_X11' | |
888 | ||
889 | return plattag | |
890 | ||
891 | def introspect_pyqt(self, pkg_config): | |
892 | """ Introspect PyQt to determine the sip flags required. pkg_config | |
893 | is the package configuration. | |
894 | """ | |
895 | ||
896 | if self.pyqt_package == 'PyQt5': | |
897 | try: | |
898 | from PyQt5 import QtCore | |
899 | except ImportError: | |
900 | error( | |
901 | "Unable to import PyQt5.QtCore. Make sure PyQt5 is " | |
902 | "installed.") | |
903 | else: | |
904 | try: | |
905 | from PyQt4 import QtCore | |
906 | except ImportError: | |
907 | error( | |
908 | "Unable to import PyQt4.QtCore. Make sure PyQt4 is " | |
909 | "installed.") | |
910 | ||
911 | self.pyqt_version_str = QtCore.PYQT_VERSION_STR | |
912 | self.qt_version_str = QtCore.qVersion() | |
913 | ||
914 | # See if we have a PyQt that embeds its configuration. | |
915 | try: | |
916 | pyqt_config = QtCore.PYQT_CONFIGURATION | |
917 | except AttributeError: | |
918 | pyqt_config = None | |
919 | ||
920 | if pyqt_config is None: | |
921 | if pkg_config.legacy_configuration_script: | |
922 | # Fallback to the old configuration script. | |
923 | config_script = sys.argv[0].replace('configure', 'configure-old') | |
924 | args = [sys.executable, config_script] + sys.argv[1:] | |
925 | ||
926 | try: | |
927 | os.execv(sys.executable, args) | |
928 | except OSError: | |
929 | pass | |
930 | ||
931 | error("Unable to execute '%s'" % config_script) | |
932 | ||
933 | error("PyQt v4.10 or later is required.") | |
934 | ||
935 | self.pyqt_sip_flags = pyqt_config['sip_flags'] | |
936 | ||
937 | def apply_sysroot(self): | |
938 | """ Apply sysroot where necessary. """ | |
939 | ||
940 | if self.sysroot != '': | |
941 | self.py_inc_dir = self._apply_sysroot(self.py_inc_dir) | |
942 | self.py_venv_inc_dir = self._apply_sysroot(self.py_venv_inc_dir) | |
943 | self.py_pylib_dir = self._apply_sysroot(self.py_pylib_dir) | |
944 | self.py_sip_dir = self._apply_sysroot(self.py_sip_dir) | |
945 | self.module_dir = self._apply_sysroot(self.module_dir) | |
946 | self.sip_inc_dir = self._apply_sysroot(self.sip_inc_dir) | |
947 | ||
948 | def _apply_sysroot(self, dir_name): | |
949 | """ Replace any leading sys.prefix of a directory name with sysroot. | |
950 | """ | |
951 | ||
952 | if dir_name.startswith(sys.prefix): | |
953 | dir_name = self.sysroot + dir_name[len(sys.prefix):] | |
954 | ||
955 | return dir_name | |
956 | ||
957 | def get_qt_configuration(self, opts): | |
958 | """ Get the Qt configuration that can be extracted from qmake. opts | |
959 | are the command line options. | |
960 | """ | |
961 | ||
962 | # Query qmake. | |
963 | qt_config = _TargetQtConfiguration(self.qmake) | |
964 | ||
965 | self.qt_version_str = getattr(qt_config, 'QT_VERSION', '') | |
966 | self.qt_version = version_from_string(self.qt_version_str) | |
967 | if self.qt_version is None: | |
968 | error("Unable to determine the version of Qt.") | |
969 | ||
970 | # On Windows for Qt versions prior to v5.9.0 we need to be explicit | |
971 | # about the qmake spec. | |
972 | if self.qt_version < 0x050900 and self.py_platform == 'win32': | |
973 | if self.py_version >= 0x030500: | |
974 | self.qmake_spec = 'win32-msvc2015' | |
975 | elif self.py_version >= 0x030300: | |
976 | self.qmake_spec = 'win32-msvc2010' | |
977 | elif self.py_version >= 0x020600: | |
978 | self.qmake_spec = 'win32-msvc2008' | |
979 | elif self.py_version >= 0x020400: | |
980 | self.qmake_spec = 'win32-msvc.net' | |
981 | else: | |
982 | self.qmake_spec = 'win32-msvc' | |
983 | else: | |
984 | # Otherwise use the default. | |
985 | self.qmake_spec = '' | |
986 | ||
987 | # The binary MacOS/X Qt installer used to default to XCode. If so then | |
988 | # use macx-clang (Qt v5) or macx-g++ (Qt v4). | |
989 | if sys.platform == 'darwin': | |
990 | try: | |
991 | # Qt v5. | |
992 | if qt_config.QMAKE_SPEC == 'macx-xcode': | |
993 | # This will exist (and we can't check anyway). | |
994 | self.qmake_spec = 'macx-clang' | |
995 | else: | |
996 | # No need to explicitly name the default. | |
997 | self.qmake_spec = '' | |
998 | except AttributeError: | |
999 | # Qt v4. | |
1000 | self.qmake_spec = 'macx-g++' | |
1001 | ||
1002 | self.api_dir = os.path.join(qt_config.QT_INSTALL_DATA, 'qsci') | |
1003 | self.qt_inc_dir = qt_config.QT_INSTALL_HEADERS | |
1004 | self.qt_lib_dir = qt_config.QT_INSTALL_LIBS | |
1005 | ||
1006 | if self.sysroot == '': | |
1007 | self.sysroot = getattr(qt_config, 'QT_SYSROOT', '') | |
1008 | ||
1009 | def apply_pre_options(self, opts): | |
1010 | """ Apply options from the command line that influence subsequent | |
1011 | configuration. opts are the command line options. | |
1012 | """ | |
1013 | ||
1014 | # On Windows the interpreter must be a debug build if a debug version | |
1015 | # is to be built and vice versa. | |
1016 | if sys.platform == 'win32': | |
1017 | if opts.debug: | |
1018 | if not self.py_debug: | |
1019 | error( | |
1020 | "A debug version of Python must be used when " | |
1021 | "--debug is specified.") | |
1022 | elif self.py_debug: | |
1023 | error( | |
1024 | "--debug must be specified when a debug version of " | |
1025 | "Python is used.") | |
1026 | ||
1027 | self.debug = opts.debug | |
1028 | ||
1029 | # Get the system root. | |
1030 | if opts.sysroot is not None: | |
1031 | self.sysroot = opts.sysroot | |
1032 | ||
1033 | # Determine how to run qmake. | |
1034 | if opts.qmake is not None: | |
1035 | self.qmake = opts.qmake | |
1036 | ||
1037 | # On Windows add the directory that probably contains the Qt DLLs | |
1038 | # to PATH. | |
1039 | if sys.platform == 'win32': | |
1040 | path = os.environ['PATH'] | |
1041 | path = os.path.dirname(self.qmake) + ';' + path | |
1042 | os.environ['PATH'] = path | |
1043 | ||
1044 | if self.qmake is None: | |
1045 | error( | |
1046 | "Use the --qmake argument to explicitly specify a working " | |
1047 | "Qt qmake.") | |
1048 | ||
1049 | if opts.qmakespec is not None: | |
1050 | self.qmake_spec = opts.qmakespec | |
1051 | ||
1052 | if self.pyqt_package is not None: | |
1053 | try: | |
1054 | self.pyqt_package = opts.pyqt_package | |
1055 | except AttributeError: | |
1056 | # Multiple PyQt versions are not supported. | |
1057 | pass | |
1058 | ||
1059 | self.module_dir = os.path.join(self.py_module_dir, | |
1060 | self.pyqt_package) | |
1061 | ||
1062 | def apply_post_options(self, opts, pkg_config): | |
1063 | """ Apply options from the command line that override the previous | |
1064 | configuration. opts are the command line options. pkg_config is the | |
1065 | package configuration. | |
1066 | """ | |
1067 | ||
1068 | if self.pyqt_package is not None: | |
1069 | if pkg_config.user_pyqt_sip_flags_is_supported: | |
1070 | if opts.pyqt_sip_flags is not None: | |
1071 | self.pyqt_sip_flags = opts.pyqt_sip_flags | |
1072 | ||
1073 | if opts.pyqt_sip_dir is not None: | |
1074 | self.pyqt_sip_dir = opts.pyqt_sip_dir | |
1075 | else: | |
1076 | self.pyqt_sip_dir = os.path.join(self.py_sip_dir, | |
1077 | self.pyqt_package) | |
1078 | ||
1079 | if _has_stubs(pkg_config): | |
1080 | if opts.stubsdir is not None: | |
1081 | self.stubs_dir = opts.stubsdir | |
1082 | ||
1083 | if opts.no_stubs: | |
1084 | self.stubs_dir = '' | |
1085 | elif self.stubs_dir == '': | |
1086 | self.stubs_dir = self.module_dir | |
1087 | ||
1088 | if pkg_config.qscintilla_api_file: | |
1089 | if opts.apidir is not None: | |
1090 | self.api_dir = opts.apidir | |
1091 | ||
1092 | if opts.no_qsci_api: | |
1093 | self.api_dir = '' | |
1094 | ||
1095 | if opts.destdir is not None: | |
1096 | self.module_dir = opts.destdir | |
1097 | ||
1098 | if pkg_config.protected_is_public_is_supported: | |
1099 | if opts.prot_is_public is not None: | |
1100 | self.prot_is_public = opts.prot_is_public | |
1101 | else: | |
1102 | self.prot_is_public = False | |
1103 | ||
1104 | if opts.sip_inc_dir is not None: | |
1105 | self.sip_inc_dir = opts.sip_inc_dir | |
1106 | ||
1107 | if opts.sip is not None: | |
1108 | self.sip = opts.sip | |
1109 | ||
1110 | pkg_config.apply_options(self, opts) | |
1111 | ||
1112 | @staticmethod | |
1113 | def _find_exe(*exes): | |
1114 | """ Find an executable, ie. the first on the path. """ | |
1115 | ||
1116 | path_dirs = os.environ.get('PATH', '').split(os.pathsep) | |
1117 | ||
1118 | for exe in exes: | |
1119 | # Strip any surrounding quotes. | |
1120 | if exe.startswith('"') and exe.endswith('"'): | |
1121 | exe = exe[1:-1] | |
1122 | ||
1123 | if sys.platform == 'win32': | |
1124 | exe = exe + '.exe' | |
1125 | ||
1126 | for d in path_dirs: | |
1127 | exe_path = os.path.join(d, exe) | |
1128 | ||
1129 | if os.access(exe_path, os.X_OK): | |
1130 | return exe_path | |
1131 | ||
1132 | return None | |
1133 | ||
1134 | ||
1135 | def _create_optparser(target_config, pkg_config): | |
1136 | """ Create the parser for the command line. target_config is the target | |
1137 | configuration containing default values. pkg_config is the package | |
1138 | configuration. | |
1139 | """ | |
1140 | ||
1141 | pkg_name = pkg_config.descriptive_name | |
1142 | ||
1143 | p = optparse.OptionParser(usage="python %prog [options]", | |
1144 | version=pkg_config.version) | |
1145 | ||
1146 | p.add_option('--spec', dest='qmakespec', default=None, action='store', | |
1147 | metavar="SPEC", | |
1148 | help="pass -spec SPEC to qmake") | |
1149 | ||
1150 | if _has_stubs(pkg_config): | |
1151 | p.add_option('--stubsdir', dest='stubsdir', type='string', | |
1152 | default=None, action='callback', | |
1153 | callback=optparser_store_abspath, metavar="DIR", | |
1154 | help="the PEP 484 stubs will be installed in DIR [default: " | |
1155 | "with the module]") | |
1156 | p.add_option('--no-stubs', dest='no_stubs', default=False, | |
1157 | action='store_true', | |
1158 | help="disable the installation of the PEP 484 stubs " | |
1159 | "[default: enabled]") | |
1160 | ||
1161 | if pkg_config.qscintilla_api_file: | |
1162 | p.add_option('--apidir', '-a', dest='apidir', type='string', | |
1163 | default=None, action='callback', | |
1164 | callback=optparser_store_abspath, metavar="DIR", | |
1165 | help="the QScintilla API file will be installed in DIR " | |
1166 | "[default: QT_INSTALL_DATA/qsci]") | |
1167 | p.add_option('--no-qsci-api', dest='no_qsci_api', default=False, | |
1168 | action='store_true', | |
1169 | help="disable the installation of the QScintilla API file " | |
1170 | "[default: enabled]") | |
1171 | ||
1172 | if pkg_config.user_configuration_file_is_supported: | |
1173 | p.add_option('--configuration', dest='config_file', type='string', | |
1174 | default=None, action='callback', | |
1175 | callback=optparser_store_abspath, metavar="FILE", | |
1176 | help="FILE defines the target configuration") | |
1177 | ||
1178 | p.add_option('--destdir', '-d', dest='destdir', type='string', | |
1179 | default=None, action='callback', callback=optparser_store_abspath, | |
1180 | metavar="DIR", | |
1181 | help="install %s in DIR [default: %s]" % | |
1182 | (pkg_name, target_config.module_dir)) | |
1183 | ||
1184 | if pkg_config.protected_is_public_is_supported: | |
1185 | p.add_option('--protected-is-public', dest='prot_is_public', | |
1186 | default=None, action='store_true', | |
1187 | help="enable building with 'protected' redefined as 'public' " | |
1188 | "[default: %s]" % target_config.prot_is_public) | |
1189 | p.add_option('--protected-not-public', dest='prot_is_public', | |
1190 | action='store_false', | |
1191 | help="disable building with 'protected' redefined as 'public'") | |
1192 | ||
1193 | if target_config.pyqt_package is not None: | |
1194 | pyqt = target_config.pyqt_package | |
1195 | ||
1196 | if pkg_config.pyqt5_is_supported and pkg_config.pyqt4_is_supported: | |
1197 | p.add_option('--pyqt', dest='pyqt_package', type='choice', | |
1198 | choices=['PyQt4', 'PyQt5'], default=pyqt, | |
1199 | action='store', metavar="PyQtn", | |
1200 | help="configure for PyQt4 or PyQt5 [default: %s]" % pyqt) | |
1201 | ||
1202 | if pkg_config.user_pyqt_sip_flags_is_supported: | |
1203 | p.add_option('--pyqt-sip-flags', dest='pyqt_sip_flags', | |
1204 | default=None, action='store', metavar="FLAGS", | |
1205 | help="the sip flags used to build PyQt [default: query PyQt]") | |
1206 | ||
1207 | p.add_option('--qmake', '-q', dest='qmake', type='string', default=None, | |
1208 | action='callback', callback=optparser_store_abspath_exe, | |
1209 | metavar="FILE", | |
1210 | help="the pathname of qmake is FILE [default: %s]" % ( | |
1211 | target_config.qmake or "search PATH")) | |
1212 | ||
1213 | p.add_option('--sip', dest='sip', type='string', default=None, | |
1214 | action='callback', callback=optparser_store_abspath_exe, | |
1215 | metavar="FILE", | |
1216 | help="the pathname of sip is FILE [default: " | |
1217 | "%s]" % (target_config.sip or "None")) | |
1218 | p.add_option('--sip-incdir', dest='sip_inc_dir', type='string', | |
1219 | default=None, action='callback', | |
1220 | callback=optparser_store_abspath_dir, metavar="DIR", | |
1221 | help="the directory containing the sip.h header file file is DIR " | |
1222 | "[default: %s]" % target_config.sip_inc_dir) | |
1223 | ||
1224 | if target_config.pyqt_package is not None: | |
1225 | p.add_option('--pyqt-sipdir', dest='pyqt_sip_dir', type='string', | |
1226 | default=None, action='callback', | |
1227 | callback=optparser_store_abspath_dir, metavar="DIR", | |
1228 | help="the directory containing the PyQt .sip files is DIR " | |
1229 | "[default: %s]" % target_config.pyqt_sip_dir) | |
1230 | ||
1231 | p.add_option('--concatenate', '-c', dest='concat', default=False, | |
1232 | action='store_true', | |
1233 | help="concatenate the C++ source files") | |
1234 | p.add_option('--concatenate-split', '-j', dest='split', type='int', | |
1235 | default=1, metavar="N", | |
1236 | help="split the concatenated C++ source files into N pieces " | |
1237 | "[default: 1]") | |
1238 | p.add_option('--static', '-k', dest='static', default=False, | |
1239 | action='store_true', | |
1240 | help="build a static %s" % pkg_name) | |
1241 | p.add_option("--sysroot", dest='sysroot', type='string', action='callback', | |
1242 | callback=optparser_store_abspath_dir, metavar="DIR", | |
1243 | help="DIR is the target system root directory") | |
1244 | p.add_option('--no-docstrings', dest='no_docstrings', default=False, | |
1245 | action='store_true', | |
1246 | help="disable the generation of docstrings") | |
1247 | p.add_option('--trace', '-r', dest='tracing', default=False, | |
1248 | action='store_true', | |
1249 | help="build %s with tracing enabled" % pkg_name) | |
1250 | p.add_option('--debug', '-u', default=False, action='store_true', | |
1251 | help="build %s with debugging symbols" % pkg_name) | |
1252 | p.add_option('--verbose', '-w', dest='verbose', default=False, | |
1253 | action='store_true', | |
1254 | help="enable verbose output during configuration") | |
1255 | ||
1256 | pkg_config.init_optparser(p, target_config) | |
1257 | ||
1258 | return p | |
1259 | ||
1260 | ||
1261 | def _has_stubs(pkg_config): | |
1262 | """ See if a stub file for any of the modules will be generated. | |
1263 | pkg_config is the package configuration. | |
1264 | """ | |
1265 | ||
1266 | for module_config in pkg_config.modules: | |
1267 | if module_config.pep484_stub_file: | |
1268 | return True | |
1269 | ||
1270 | return False | |
1271 | ||
1272 | ||
1273 | def _inform_user(target_config, pkg_config): | |
1274 | """ Tell the user the values that are going to be used. target_config is | |
1275 | the target configuration. pkg_config is the package configuration. | |
1276 | """ | |
1277 | ||
1278 | pkg_name = pkg_config.descriptive_name | |
1279 | ||
1280 | inform("Configuring %s %s..." % (pkg_name, pkg_config.version)) | |
1281 | ||
1282 | pkg_config.inform_user(target_config) | |
1283 | ||
1284 | inform("%s will be installed in %s." % | |
1285 | (pkg_name, target_config.module_dir)) | |
1286 | ||
1287 | if target_config.debug: | |
1288 | inform("A debug version of %s will be built." % pkg_name) | |
1289 | ||
1290 | if target_config.py_debug: | |
1291 | inform("A debug build of Python is being used.") | |
1292 | ||
1293 | if target_config.pyqt_version_str != '': | |
1294 | inform("PyQt %s is being used." % target_config.pyqt_version_str) | |
1295 | else: | |
1296 | inform("%s is being used." % target_config.pyqt_package) | |
1297 | ||
1298 | if target_config.qt_version_str != '': | |
1299 | inform("Qt %s is being used." % target_config.qt_version_str) | |
1300 | ||
1301 | if target_config.sysroot != '': | |
1302 | inform("The system root directory is %s." % target_config.sysroot) | |
1303 | ||
1304 | inform("sip %s is being used." % target_config.sip_version_str) | |
1305 | inform("The sip executable is %s." % target_config.sip) | |
1306 | ||
1307 | if target_config.prot_is_public: | |
1308 | inform("%s is being built with 'protected' redefined as 'public'." % | |
1309 | pkg_name) | |
1310 | ||
1311 | if target_config.stubs_dir != '': | |
1312 | inform("The PEP 484 stubs will be installed in %s." % | |
1313 | target_config.stubs_dir) | |
1314 | ||
1315 | if pkg_config.qscintilla_api_file and target_config.api_dir != '': | |
1316 | inform("The QScintilla API file will be installed in %s." % | |
1317 | os.path.join(target_config.api_dir, 'api', 'python')) | |
1318 | ||
1319 | ||
1320 | def _generate_code(target_config, opts, pkg_config, module_config): | |
1321 | """ Generate the code for the module. target_config is the target | |
1322 | configuration. opts are the command line options. pkg_config is the | |
1323 | package configuration. module_config is the module configuration. | |
1324 | """ | |
1325 | ||
1326 | inform( | |
1327 | "Generating the C++ source for the %s module..." % | |
1328 | module_config.name) | |
1329 | ||
1330 | # Generate the code in a module-specific sub-directory. | |
1331 | try: | |
1332 | os.mkdir(module_config.name) | |
1333 | except: | |
1334 | pass | |
1335 | ||
1336 | # Build the SIP command line. | |
1337 | argv = [quote(target_config.sip)] | |
1338 | ||
1339 | # Tell SIP if this is a debug build of Python (SIP v4.19.1 and later). | |
1340 | if target_config.sip_version >= 0x041301 and target_config.py_debug: | |
1341 | argv.append('-D') | |
1342 | ||
1343 | # Add the module-specific flags. | |
1344 | argv.extend(pkg_config.get_sip_flags(target_config)) | |
1345 | ||
1346 | if target_config.pyqt_package is not None: | |
1347 | # Get the flags used for the main PyQt module. | |
1348 | argv.extend(target_config.pyqt_sip_flags.split()) | |
1349 | ||
1350 | # Add the backstop version. | |
1351 | argv.append('-B') | |
1352 | argv.append('Qt_6_0_0' if target_config.pyqt_package == 'PyQt5' | |
1353 | else 'Qt_5_0_0') | |
1354 | ||
1355 | # Add PyQt's .sip files to the search path. | |
1356 | argv.append('-I') | |
1357 | argv.append(quote(target_config.pyqt_sip_dir)) | |
1358 | ||
1359 | if target_config.stubs_dir != '': | |
1360 | # Generate the stub file. | |
1361 | argv.append('-y') | |
1362 | argv.append(quote(module_config.pep484_stub_file + '.pyi')) | |
1363 | ||
1364 | if pkg_config.qscintilla_api_file and target_config.api_dir != '': | |
1365 | # Generate the API file. | |
1366 | argv.append('-a') | |
1367 | argv.append(quote(module_config.name + '.api')) | |
1368 | ||
1369 | if target_config.prot_is_public: | |
1370 | argv.append('-P'); | |
1371 | ||
1372 | if not opts.no_docstrings: | |
1373 | argv.append('-o'); | |
1374 | ||
1375 | if opts.concat: | |
1376 | argv.append('-j') | |
1377 | argv.append(str(opts.split)) | |
1378 | ||
1379 | if opts.tracing: | |
1380 | argv.append('-r') | |
1381 | ||
1382 | argv.append('-c') | |
1383 | argv.append(os.path.abspath(module_config.name)) | |
1384 | ||
1385 | # This assumes that, for multi-module packages, all modules's .sip files | |
1386 | # will be rooted in a common root directory. | |
1387 | sip_file = module_config.get_sip_file(target_config) | |
1388 | ||
1389 | head, tail = os.path.split(sip_file) | |
1390 | while head: | |
1391 | head, tail = os.path.split(head) | |
1392 | ||
1393 | if tail != sip_file: | |
1394 | argv.append('-I') | |
1395 | argv.append(quote(tail)) | |
1396 | ||
1397 | argv.append(sip_file) | |
1398 | ||
1399 | check_file = os.path.join(module_config.name, | |
1400 | 'sipAPI%s.h' % module_config.name) | |
1401 | _remove_file(check_file) | |
1402 | ||
1403 | _run_command(' '.join(argv), opts.verbose) | |
1404 | ||
1405 | if not os.access(check_file, os.F_OK): | |
1406 | error("Unable to create the C++ code.") | |
1407 | ||
1408 | # Generate the .pro file. | |
1409 | _generate_pro(target_config, opts, module_config) | |
1410 | ||
1411 | ||
1412 | def _get_qt_qmake_config(qmake_config, qt_version): | |
1413 | """ Return a dict of qmake configuration values for a specific Qt version. | |
1414 | """ | |
1415 | ||
1416 | qt_qmake_config = {} | |
1417 | ||
1418 | for name, value in qmake_config.items(): | |
1419 | name_parts = name.split(':') | |
1420 | if len(name_parts) == 2 and name_parts[0] == qt_version: | |
1421 | qt_qmake_config[name_parts[1]] = value | |
1422 | ||
1423 | return qt_qmake_config | |
1424 | ||
1425 | ||
1426 | def _write_qt_qmake_config(qt_qmake_config, pro): | |
1427 | """ Write the qmake configuration values to a .pro file. """ | |
1428 | ||
1429 | for name in ('QT', 'CONFIG', 'DEFINES', 'INCLUDEPATH', 'LIBS'): | |
1430 | value = qt_qmake_config.get(name) | |
1431 | if value: | |
1432 | pro.write(' %s += %s\n' % (name, value)) | |
1433 | ||
1434 | ||
1435 | def _generate_pro(target_config, opts, module_config): | |
1436 | """ Generate the .pro file for the module. target_config is the target | |
1437 | configuration. opts are the command line options. module_config is the | |
1438 | module configuration. | |
1439 | """ | |
1440 | ||
1441 | inform("Generating the .pro file for the %s module..." % module_config.name) | |
1442 | ||
1443 | # Without the 'no_check_exist' magic the target.files must exist when qmake | |
1444 | # is run otherwise the install and uninstall targets are not generated. | |
1445 | ||
1446 | qmake_config = module_config.get_qmake_configuration(target_config) | |
1447 | ||
1448 | pro = open(os.path.join(module_config.name, module_config.name + '.pro'), | |
1449 | 'w') | |
1450 | ||
1451 | pro.write('TEMPLATE = lib\n') | |
1452 | ||
1453 | qt = qmake_config.get('QT') | |
1454 | if qt: | |
1455 | pro.write('QT += %s\n' % qt) | |
1456 | ||
1457 | pro.write('CONFIG += %s\n' % ('debug' if target_config.debug else 'release')) | |
1458 | pro.write('CONFIG += %s\n' % ('staticlib' if opts.static else 'plugin plugin_bundle')) | |
1459 | ||
1460 | config = qmake_config.get('CONFIG') | |
1461 | if config: | |
1462 | pro.write('CONFIG += %s\n' % config) | |
1463 | ||
1464 | # Work around QTBUG-39300. | |
1465 | pro.write('CONFIG -= android_install\n') | |
1466 | ||
1467 | qt5_qmake_config = _get_qt_qmake_config(qmake_config, 'Qt5') | |
1468 | qt4_qmake_config = _get_qt_qmake_config(qmake_config, 'Qt4') | |
1469 | ||
1470 | if qt5_qmake_config or qt4_qmake_config: | |
1471 | pro.write(''' | |
1472 | greaterThan(QT_MAJOR_VERSION, 4) { | |
1473 | ''') | |
1474 | ||
1475 | if qt5_qmake_config: | |
1476 | _write_qt_qmake_config(qt5_qmake_config, pro) | |
1477 | ||
1478 | if qt4_qmake_config: | |
1479 | pro.write('} else {\n') | |
1480 | _write_qt_qmake_config(qt4_qmake_config, pro) | |
1481 | ||
1482 | pro.write('}\n') | |
1483 | ||
1484 | mname = module_config.name | |
1485 | ||
1486 | pro.write('TARGET = %s\n' % mname) | |
1487 | ||
1488 | if not opts.static: | |
1489 | pro.write(''' | |
1490 | win32 { | |
1491 | PY_MODULE = %s.pyd | |
1492 | PY_MODULE_SRC = $(DESTDIR_TARGET) | |
1493 | ||
1494 | LIBS += -L%s | |
1495 | } else { | |
1496 | PY_MODULE = %s.so | |
1497 | ||
1498 | macx { | |
1499 | PY_MODULE_SRC = $(TARGET).plugin/Contents/MacOS/$(TARGET) | |
1500 | ||
1501 | QMAKE_LFLAGS += "-undefined dynamic_lookup" | |
1502 | ||
1503 | equals(QT_MAJOR_VERSION, 5) { | |
1504 | equals(QT_MINOR_VERSION, 5) { | |
1505 | QMAKE_RPATHDIR += $$[QT_INSTALL_LIBS] | |
1506 | } | |
1507 | } | |
1508 | } else { | |
1509 | PY_MODULE_SRC = $(TARGET) | |
1510 | } | |
1511 | } | |
1512 | ||
1513 | QMAKE_POST_LINK = $(COPY_FILE) $$PY_MODULE_SRC $$PY_MODULE | |
1514 | ||
1515 | target.CONFIG = no_check_exist | |
1516 | target.files = $$PY_MODULE | |
1517 | ''' % (mname, quote(target_config.py_pylib_dir), mname)) | |
1518 | ||
1519 | pro.write(''' | |
1520 | target.path = %s | |
1521 | INSTALLS += target | |
1522 | ''' % quote(target_config.module_dir)) | |
1523 | ||
1524 | sip_installs = module_config.get_sip_installs(target_config) | |
1525 | if sip_installs is not None: | |
1526 | path, files = sip_installs | |
1527 | ||
1528 | pro.write(''' | |
1529 | sip.path = %s | |
1530 | sip.files =''' % quote(path)) | |
1531 | ||
1532 | for f in files: | |
1533 | pro.write(' \\\n ../%s' % f) | |
1534 | ||
1535 | pro.write(''' | |
1536 | INSTALLS += sip | |
1537 | ''') | |
1538 | ||
1539 | pro.write('\n') | |
1540 | ||
1541 | # These optimisations could apply to other platforms. | |
1542 | if module_config.no_exceptions: | |
1543 | if target_config.py_platform.startswith('linux') or target_config.py_platform == 'darwin': | |
1544 | pro.write('QMAKE_CXXFLAGS += -fno-exceptions\n') | |
1545 | ||
1546 | if target_config.py_platform.startswith('linux') and not opts.static: | |
1547 | if target_config.py_version >= 0x030000: | |
1548 | entry_point = 'PyInit_%s' % mname | |
1549 | else: | |
1550 | entry_point = 'init%s' % mname | |
1551 | ||
1552 | exp = open(os.path.join(mname, mname + '.exp'), 'wt') | |
1553 | exp.write('{ global: %s; local: *; };' % entry_point) | |
1554 | exp.close() | |
1555 | ||
1556 | pro.write('QMAKE_LFLAGS += -Wl,--version-script=%s.exp\n' % mname) | |
1557 | ||
1558 | if target_config.prot_is_public: | |
1559 | pro.write('DEFINES += SIP_PROTECTED_IS_PUBLIC protected=public\n') | |
1560 | ||
1561 | defines = qmake_config.get('DEFINES') | |
1562 | if defines: | |
1563 | pro.write('DEFINES += %s\n' % defines) | |
1564 | ||
1565 | includepath = qmake_config.get('INCLUDEPATH') | |
1566 | if includepath: | |
1567 | pro.write('INCLUDEPATH += %s\n' % includepath) | |
1568 | ||
1569 | # Make sure the SIP include directory is searched before the Python include | |
1570 | # directory if they are different. | |
1571 | pro.write('INCLUDEPATH += %s\n' % quote(target_config.sip_inc_dir)) | |
1572 | if target_config.py_inc_dir != target_config.sip_inc_dir: | |
1573 | pro.write('INCLUDEPATH += %s\n' % quote(target_config.py_inc_dir)) | |
1574 | ||
1575 | libs = qmake_config.get('LIBS') | |
1576 | if libs: | |
1577 | pro.write('LIBS += %s\n' % libs) | |
1578 | ||
1579 | if not opts.static: | |
1580 | dylib = module_config.get_mac_wrapped_library_file(target_config) | |
1581 | ||
1582 | if dylib: | |
1583 | pro.write(''' | |
1584 | macx { | |
1585 | QMAKE_POST_LINK = $$QMAKE_POST_LINK$$escape_expand(\\\\n\\\\t)$$quote(install_name_tool -change %s %s $$PY_MODULE) | |
1586 | } | |
1587 | ''' % (os.path.basename(dylib), dylib)) | |
1588 | ||
1589 | pro.write('\n') | |
1590 | pro.write('HEADERS = sipAPI%s.h\n' % mname) | |
1591 | ||
1592 | pro.write('SOURCES =') | |
1593 | for s in os.listdir(module_config.name): | |
1594 | if s.endswith('.cpp'): | |
1595 | pro.write(' \\\n %s' % s) | |
1596 | pro.write('\n') | |
1597 | ||
1598 | pro.close() | |
1599 | ||
1600 | ||
1601 | def _run_qmake(target_config, verbose, pro_name): | |
1602 | """ Run qmake against a .pro file. target_config is the target | |
1603 | configuration. verbose is set if the output is to be displayed. pro_name | |
1604 | is the name of the .pro file. | |
1605 | """ | |
1606 | ||
1607 | inform("Generating the Makefiles...") | |
1608 | ||
1609 | # qmake doesn't behave consistently if it is not run from the directory | |
1610 | # containing the .pro file - so make sure it is. | |
1611 | pro_dir, pro_file = os.path.split(pro_name) | |
1612 | if pro_dir != '': | |
1613 | cwd = os.getcwd() | |
1614 | os.chdir(pro_dir) | |
1615 | else: | |
1616 | cwd = None | |
1617 | ||
1618 | mf = 'Makefile' | |
1619 | ||
1620 | _remove_file(mf) | |
1621 | ||
1622 | args = [quote(target_config.qmake)] | |
1623 | ||
1624 | # Make sure all Makefiles are generated now in case qmake has been | |
1625 | # configured with environment variables. | |
1626 | args.append('-recursive') | |
1627 | ||
1628 | if target_config.qmake_spec != '': | |
1629 | args.append('-spec') | |
1630 | args.append(target_config.qmake_spec) | |
1631 | ||
1632 | args.append(pro_file) | |
1633 | ||
1634 | _run_command(' '.join(args), verbose) | |
1635 | ||
1636 | if not os.access(mf, os.F_OK): | |
1637 | error( | |
1638 | "%s failed to create a Makefile from %s." % | |
1639 | (target_config.qmake, pro_name)) | |
1640 | ||
1641 | # Restore the current directory. | |
1642 | if cwd is not None: | |
1643 | os.chdir(cwd) | |
1644 | ||
1645 | ||
1646 | def _run_command(cmd, verbose): | |
1647 | """ Run a command and display the output if requested. cmd is the command | |
1648 | to run. verbose is set if the output is to be displayed. | |
1649 | """ | |
1650 | ||
1651 | if verbose: | |
1652 | sys.stdout.write(cmd + "\n") | |
1653 | ||
1654 | fout = _get_command_output(cmd) | |
1655 | ||
1656 | # Read stdout and stderr until there is no more output. | |
1657 | lout = fout.readline() | |
1658 | while lout: | |
1659 | if verbose: | |
1660 | if sys.hexversion >= 0x03000000: | |
1661 | sys.stdout.write(str(lout, encoding=sys.stdout.encoding)) | |
1662 | else: | |
1663 | sys.stdout.write(lout) | |
1664 | ||
1665 | lout = fout.readline() | |
1666 | ||
1667 | fout.close() | |
1668 | ||
1669 | try: | |
1670 | os.wait() | |
1671 | except: | |
1672 | pass | |
1673 | ||
1674 | ||
1675 | def _get_command_output(cmd): | |
1676 | """ Return a pipe from which a command's output can be read. cmd is the | |
1677 | command. | |
1678 | """ | |
1679 | ||
1680 | try: | |
1681 | import subprocess | |
1682 | except ImportError: | |
1683 | _, sout = os.popen4(cmd) | |
1684 | ||
1685 | return sout | |
1686 | ||
1687 | p = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, | |
1688 | stdout=subprocess.PIPE, stderr=subprocess.STDOUT) | |
1689 | ||
1690 | return p.stdout | |
1691 | ||
1692 | ||
1693 | def _remove_file(fname): | |
1694 | """ Remove a file which may or may not exist. fname is the name of the | |
1695 | file. | |
1696 | """ | |
1697 | ||
1698 | try: | |
1699 | os.remove(fname) | |
1700 | except OSError: | |
1701 | pass | |
1702 | ||
1703 | ||
1704 | def _check_sip(target_config, pkg_config): | |
1705 | """ Check that the version of sip is good enough. target_config is the | |
1706 | target configuration. pkg_config is the package configuration. | |
1707 | """ | |
1708 | ||
1709 | if target_config.sip is None: | |
1710 | error( | |
1711 | "Make sure you have a working sip on your PATH or use the " | |
1712 | "--sip argument to explicitly specify a working sip.") | |
1713 | ||
1714 | pipe = os.popen(' '.join([quote(target_config.sip), '-V'])) | |
1715 | ||
1716 | for l in pipe: | |
1717 | version_str = l.strip() | |
1718 | break | |
1719 | else: | |
1720 | error("'%s -V' did not generate any output." % target_config.sip) | |
1721 | ||
1722 | pipe.close() | |
1723 | ||
1724 | if '.dev' in version_str or 'snapshot' in version_str: | |
1725 | version = 0 | |
1726 | else: | |
1727 | version = version_from_string(version_str) | |
1728 | if version is None: | |
1729 | error( | |
1730 | "'%s -V' generated unexpected output: '%s'." % ( | |
1731 | target_config.sip, version_str)) | |
1732 | ||
1733 | min_sip_version = pkg_config.minimum_sip_version | |
1734 | if min_sip_version: | |
1735 | min_version = version_from_string(min_sip_version) | |
1736 | if version < min_version: | |
1737 | error( | |
1738 | "This version of %s requires sip %s or later." % | |
1739 | (pkg_config.descriptive_name, min_sip_version)) | |
1740 | ||
1741 | target_config.sip_version = version | |
1742 | target_config.sip_version_str = version_str | |
1743 | ||
1744 | ||
1745 | def _main(argv, pkg_config): | |
1746 | """ Create the configured package. argv is the list of command line | |
1747 | arguments. pkg_config is the package configuration. | |
1748 | """ | |
1749 | ||
1750 | # Create the default target configuration. | |
1751 | target_config = _TargetConfiguration(pkg_config) | |
1752 | ||
1753 | # Parse the command line. | |
1754 | p = _create_optparser(target_config, pkg_config) | |
1755 | opts, args = p.parse_args() | |
1756 | ||
1757 | if args: | |
1758 | p.print_help() | |
1759 | sys.exit(2) | |
1760 | ||
1761 | target_config.apply_pre_options(opts) | |
1762 | ||
1763 | # Query qmake for the basic configuration information. | |
1764 | target_config.get_qt_configuration(opts) | |
1765 | ||
1766 | # Update the target configuration. | |
1767 | if pkg_config.user_configuration_file_is_supported: | |
1768 | config_file = opts.config_file | |
1769 | else: | |
1770 | config_file = None | |
1771 | ||
1772 | if config_file is not None: | |
1773 | target_config.update_from_configuration_file(config_file) | |
1774 | else: | |
1775 | target_config.apply_sysroot() | |
1776 | ||
1777 | target_config.apply_post_options(opts, pkg_config) | |
1778 | ||
1779 | if target_config.pyqt_package is not None: | |
1780 | if target_config.pyqt_sip_flags is None: | |
1781 | target_config.introspect_pyqt(pkg_config) | |
1782 | ||
1783 | # Check SIP is new enough. | |
1784 | _check_sip(target_config, pkg_config) | |
1785 | ||
1786 | # Perform any package specific checks now that all other information has | |
1787 | # been captured. | |
1788 | pkg_config.check_package(target_config) | |
1789 | ||
1790 | # Tell the user what's been found. | |
1791 | _inform_user(target_config, pkg_config) | |
1792 | ||
1793 | # Allow for module specific hacks. | |
1794 | pkg_config.pre_code_generation(target_config) | |
1795 | ||
1796 | # Generate the code. | |
1797 | for module_config in pkg_config.modules: | |
1798 | _generate_code(target_config, opts, pkg_config, module_config) | |
1799 | ||
1800 | # Concatenate any .api files. | |
1801 | if pkg_config.qscintilla_api_file and target_config.api_dir != '': | |
1802 | inform("Generating the QScintilla API file...") | |
1803 | f = open(pkg_config.qscintilla_api_file + '.api', 'w') | |
1804 | ||
1805 | for module_config in pkg_config.modules: | |
1806 | api = open(module_config.name + '.api') | |
1807 | ||
1808 | for l in api: | |
1809 | if target_config.pyqt_package is not None: | |
1810 | l = target_config.pyqt_package + '.' + l | |
1811 | ||
1812 | f.write(l) | |
1813 | ||
1814 | api.close() | |
1815 | os.remove(module_config.name + '.api') | |
1816 | ||
1817 | f.close() | |
1818 | ||
1819 | # Generate the top-level .pro file. | |
1820 | inform("Generating the top-level .pro file...") | |
1821 | ||
1822 | pro_name = pkg_config.descriptive_name + '.pro' | |
1823 | pro = open(pro_name, 'w') | |
1824 | ||
1825 | pro.write('''TEMPLATE = subdirs | |
1826 | CONFIG += ordered nostrip | |
1827 | SUBDIRS = %s | |
1828 | ''' % ' '.join([module.name for module in pkg_config.modules])) | |
1829 | ||
1830 | if target_config.stubs_dir != '': | |
1831 | stubs = [module.pep484_stub_file + '.pyi' for module in pkg_config.modules if module.pep484_stub_file] | |
1832 | ||
1833 | if stubs: | |
1834 | pro.write(''' | |
1835 | pep484_stubs.path = %s | |
1836 | pep484_stubs.files = %s | |
1837 | INSTALLS += pep484_stubs | |
1838 | ''' % (target_config.stubs_dir, ' '.join(stubs))) | |
1839 | ||
1840 | if pkg_config.qscintilla_api_file and target_config.api_dir != '': | |
1841 | pro.write(''' | |
1842 | api.path = %s/api/python | |
1843 | api.files = %s.api | |
1844 | INSTALLS += api | |
1845 | ''' % (target_config.api_dir, pkg_config.qscintilla_api_file)) | |
1846 | ||
1847 | pro.close() | |
1848 | ||
1849 | # Generate the Makefile. | |
1850 | _run_qmake(target_config, opts.verbose, pro_name) | |
1851 | ||
1852 | ||
1853 | ############################################################################### | |
1854 | # The script starts here. | |
1855 | ############################################################################### | |
1856 | ||
1857 | if __name__ == '__main__': | |
1858 | # Assume the product is a package containing multiple modules. If it isn't | |
1859 | # then create a dummy package containing the single module. | |
1860 | try: | |
1861 | pkg_config_type = PackageConfiguration | |
1862 | except NameError: | |
1863 | pkg_config_type = type('PackageConfiguration', (object, ), {}) | |
1864 | ||
1865 | if not hasattr(pkg_config_type, 'modules'): | |
1866 | mod_config_type = ModuleConfiguration | |
1867 | ||
1868 | # Extract the package-specific attributes and methods. | |
1869 | pkg_config_type.descriptive_name = mod_config_type.descriptive_name | |
1870 | pkg_config_type.legacy_configuration_script = mod_config_type.legacy_configuration_script | |
1871 | pkg_config_type.minimum_sip_version = mod_config_type.minimum_sip_version | |
1872 | pkg_config_type.protected_is_public_is_supported = mod_config_type.protected_is_public_is_supported | |
1873 | pkg_config_type.pyqt4_is_supported = mod_config_type.pyqt4_is_supported | |
1874 | pkg_config_type.pyqt5_is_supported = mod_config_type.pyqt5_is_supported | |
1875 | pkg_config_type.pyqt5_is_default = mod_config_type.pyqt5_is_default | |
1876 | pkg_config_type.qscintilla_api_file = mod_config_type.qscintilla_api_file | |
1877 | pkg_config_type.support_email_address = mod_config_type.support_email_address | |
1878 | pkg_config_type.user_configuration_file_is_supported = mod_config_type.user_configuration_file_is_supported | |
1879 | pkg_config_type.user_pyqt_sip_flags_is_supported = mod_config_type.user_pyqt_sip_flags_is_supported | |
1880 | pkg_config_type.version = mod_config_type.version | |
1881 | ||
1882 | pkg_config_type.init_target_configuration = staticmethod( | |
1883 | mod_config_type.init_target_configuration) | |
1884 | pkg_config_type.init_optparser = staticmethod( | |
1885 | mod_config_type.init_optparser) | |
1886 | pkg_config_type.apply_options = staticmethod( | |
1887 | mod_config_type.apply_options) | |
1888 | pkg_config_type.inform_user = staticmethod( | |
1889 | mod_config_type.inform_user) | |
1890 | pkg_config_type.pre_code_generation = staticmethod( | |
1891 | mod_config_type.pre_code_generation) | |
1892 | pkg_config_type.get_sip_flags = staticmethod( | |
1893 | mod_config_type.get_sip_flags) | |
1894 | ||
1895 | # Note the name change. | |
1896 | pkg_config_type.check_package = staticmethod( | |
1897 | mod_config_type.check_module) | |
1898 | ||
1899 | pkg_config_type.modules = [mod_config_type()] | |
1900 | ||
1901 | pkg_config = pkg_config_type() | |
1902 | ||
1903 | try: | |
1904 | _main(sys.argv, pkg_config) | |
1905 | except SystemExit: | |
1906 | raise | |
1907 | except: | |
1908 | if pkg_config.support_email_address: | |
1909 | sys.stderr.write( | |
1910 | """An internal error occured. Please report all the output from the program, | |
1911 | including the following traceback, to %s. | |
1912 | """ % pkg_config.support_email_address) | |
1913 | ||
1914 | raise |
0 | import sys | |
1 | ||
2 | from PyQt5 import QtCore, QtGui, QtWidgets | |
3 | import QtAV, QtAVWidgets | |
4 | ||
5 | ||
6 | class PlayerWindow(QtWidgets.QWidget): | |
7 | def __init__(self, parent = None): | |
8 | QtWidgets.QWidget.__init__(self, parent) | |
9 | ||
10 | self.m_unit = 1000.0 | |
11 | self.setWindowTitle("QtAV simple player example") | |
12 | self.m_player = QtAV.AVPlayer(self) | |
13 | vl = QtWidgets.QVBoxLayout() | |
14 | self.setLayout(vl) | |
15 | self.m_vo = QtAV.VideoOutput(self) | |
16 | ||
17 | if self.m_vo.widget() is None: | |
18 | QtWidgets.QMessageBox.warning(0, "QtAV error", "Can not create video renderer") | |
19 | return | |
20 | ||
21 | self.m_player.setRenderer(self.m_vo) | |
22 | vl.addWidget(self.m_vo.widget()) | |
23 | self.m_slider = QtWidgets.QSlider() | |
24 | self.m_slider.setOrientation(QtCore.Qt.Horizontal) | |
25 | self.m_slider.sliderMoved[int].connect(self.seekBySliderVal) | |
26 | self.m_slider.sliderPressed.connect(self.seekBySlider) | |
27 | self.m_player.positionChanged.connect(self.updateSliderVal) | |
28 | self.m_player.started.connect(self.updateSlider) | |
29 | self.m_player.notifyIntervalChanged.connect(self.updateSliderUnit) | |
30 | ||
31 | vl.addWidget(self.m_slider) | |
32 | hb = QtWidgets.QHBoxLayout() | |
33 | vl.addLayout(hb) | |
34 | self.m_openBtn = QtWidgets.QPushButton("Open") | |
35 | self.m_playBtn = QtWidgets.QPushButton("Play/Pause") | |
36 | self.m_stopBtn = QtWidgets.QPushButton("Stop") | |
37 | hb.addWidget(self.m_openBtn) | |
38 | hb.addWidget(self.m_playBtn) | |
39 | hb.addWidget(self.m_stopBtn) | |
40 | self.m_openBtn.clicked.connect(self.openMedia) | |
41 | self.m_playBtn.clicked.connect(self.playPause) | |
42 | self.m_stopBtn.clicked.connect(self.m_player.stop) | |
43 | ||
44 | def openMedia(self): | |
45 | fname = QtWidgets.QFileDialog.getOpenFileName(None, "Open a video")[0] | |
46 | if not fname: | |
47 | return | |
48 | self.m_player.play(fname) | |
49 | ||
50 | def seekBySliderVal(self, value): | |
51 | if not self.m_player.isPlaying(): | |
52 | return | |
53 | self.m_player.seek(int(value * self.m_unit)) | |
54 | ||
55 | def seekBySlider(self): | |
56 | self.seekBySliderVal(self.m_slider.value()) | |
57 | ||
58 | def playPause(self): | |
59 | if not self.m_player.isPlaying(): | |
60 | self.m_player.play() | |
61 | return | |
62 | self.m_player.pause(not self.m_player.isPaused()) | |
63 | ||
64 | def updateSliderVal(self, value): | |
65 | self.m_slider.setRange(0, int(self.m_player.duration() / self.m_unit)) | |
66 | self.m_slider.setValue(int(value / self.m_unit)) | |
67 | ||
68 | def updateSlider(self): | |
69 | self.updateSliderVal(self.m_player.position()) | |
70 | ||
71 | def updateSliderUnit(self): | |
72 | self.m_unit = float(self.m_player.notifyInterval()) | |
73 | self.updateSlider() | |
74 | ||
75 | ||
76 | if __name__ == "__main__": | |
77 | QtAVWidgets.registerRenderers() | |
78 | app = QtWidgets.QApplication(sys.argv) | |
79 | player = PlayerWindow() | |
80 | player.show() | |
81 | player.resize(800, 600) | |
82 | app.exec_() |
0 | namespace QtAV { | |
1 | ||
2 | class AVError | |
3 | { | |
4 | %TypeHeaderCode | |
5 | #include <QtAV/AVError.h> | |
6 | %End | |
7 | ||
8 | public: | |
9 | enum ErrorCode { | |
10 | NoError, | |
11 | ||
12 | //open/read/seek network stream error. value must be less then ResourceError because of correct_error_by_ffmpeg | |
13 | NetworkError, // all above and before NoError are NetworkError | |
14 | ||
15 | OpenTimedout, | |
16 | OpenError, | |
17 | ParseStreamTimedOut, | |
18 | FindStreamInfoTimedout = ParseStreamTimedOut, | |
19 | ParseStreamError, | |
20 | FindStreamInfoError = ParseStreamError, | |
21 | StreamNotFound, //a,v,s? | |
22 | ReadTimedout, | |
23 | ReadError, | |
24 | SeekError, | |
25 | ResourceError, // all above and before NetworkError are ResourceError | |
26 | ||
27 | OpenCodecError, | |
28 | CloseCodecError, | |
29 | AudioCodecNotFound, | |
30 | VideoCodecNotFound, | |
31 | SubtitleCodecNotFound, | |
32 | CodecError, // all above and before NoError are CodecError | |
33 | ||
34 | FormatError, // all above and before CodecError are FormatError | |
35 | ||
36 | // decrypt error. Not implemented | |
37 | AccessDenied, // all above and before NetworkError are AccessDenied | |
38 | ||
39 | UnknowError | |
40 | }; | |
41 | ||
42 | AVError(); | |
43 | AVError(ErrorCode code, int ffmpegError = 0); | |
44 | AVError(ErrorCode code, const QString& detail, int ffmpegError = 0); | |
45 | AVError(const AVError& other); | |
46 | ||
47 | //AVError &operator=(const AVError &other); | |
48 | bool operator==(const AVError &other) const; | |
49 | bool operator!=(const AVError &other) const; | |
50 | ||
51 | void setError(ErrorCode ec); | |
52 | ErrorCode error() const; | |
53 | QString string() const; | |
54 | ||
55 | int ffmpegErrorCode() const; | |
56 | QString ffmpegErrorString() const; | |
57 | }; | |
58 | ||
59 | }; |
0 | namespace QtAV | |
1 | { | |
2 | ||
3 | class AVOutput | |
4 | { | |
5 | %TypeHeaderCode | |
6 | #include <QtAV/AVOutput.h> | |
7 | %End | |
8 | ||
9 | public: | |
10 | explicit AVOutput(); | |
11 | virtual ~AVOutput(); | |
12 | ||
13 | private: | |
14 | AVOutput(const AVOutput& o); | |
15 | }; | |
16 | ||
17 | }; |
0 | namespace QtAV | |
1 | { | |
2 | ||
3 | class AVPlayer : public QObject | |
4 | { | |
5 | %TypeHeaderCode | |
6 | #include <QtAV/AVPlayer.h> | |
7 | %End | |
8 | ||
9 | public: | |
10 | enum State { | |
11 | StoppedState, | |
12 | PlayingState, | |
13 | PausedState | |
14 | }; | |
15 | ||
16 | static const QStringList& supportedProtocols(); | |
17 | ||
18 | explicit AVPlayer(QObject *parent /TransferThis/ = 0); | |
19 | ~AVPlayer(); | |
20 | ||
21 | // AVClock* masterClock(); | |
22 | void setFile(const QString& path); | |
23 | QString file() const; | |
24 | void setIODevice(QIODevice* device); | |
25 | // void setInput(MediaIO* in); | |
26 | // MediaIO* input() const; | |
27 | ||
28 | bool isLoaded() const; | |
29 | void setAsyncLoad(bool value = true); | |
30 | bool isAsyncLoad() const; | |
31 | void setAutoLoad(bool value = true); // NOT implemented | |
32 | bool isAutoLoad() const; // NOT implemented | |
33 | ||
34 | MediaStatus mediaStatus() const; | |
35 | bool relativeTimeMode() const; | |
36 | ||
37 | qint64 absoluteMediaStartPosition() const; | |
38 | qreal durationF() const; | |
39 | qint64 duration() const; | |
40 | qint64 mediaStartPosition() const; | |
41 | qint64 mediaStopPosition() const; | |
42 | qreal mediaStartPositionF() const; | |
43 | qint64 startPosition() const; | |
44 | qint64 stopPosition() const; | |
45 | qint64 position() const; | |
46 | int repeat() const; | |
47 | int currentRepeat() const; | |
48 | bool setExternalAudio(const QString& file); | |
49 | QString externalAudio() const; | |
50 | const QVariantList& externalAudioTracks() const; | |
51 | const QVariantList& internalAudioTracks() const; | |
52 | const QVariantList& internalVideoTracks() const; | |
53 | bool setAudioStream(const QString& file, int n = 0); | |
54 | bool setAudioStream(int n); | |
55 | bool setVideoStream(int n); | |
56 | const QVariantList& internalSubtitleTracks() const; | |
57 | bool setSubtitleStream(int n); | |
58 | int currentAudioStream() const; | |
59 | int currentVideoStream() const; | |
60 | int currentSubtitleStream() const; | |
61 | int audioStreamCount() const; | |
62 | int videoStreamCount() const; | |
63 | int subtitleStreamCount() const; | |
64 | // VideoCapture *videoCapture() const; | |
65 | void play(const QString& path); | |
66 | bool isPlaying() const; | |
67 | bool isPaused() const; | |
68 | State state() const; | |
69 | void setState(State value); | |
70 | ||
71 | void addVideoRenderer(VideoRenderer *renderer); | |
72 | void removeVideoRenderer(VideoRenderer *renderer); | |
73 | void clearVideoRenderers(); | |
74 | void setRenderer(VideoRenderer* renderer); | |
75 | VideoRenderer* renderer(); | |
76 | QList<QtAV::VideoRenderer*> videoOutputs(); | |
77 | // AudioOutput* audio(); | |
78 | void setSpeed(qreal speed); | |
79 | qreal speed() const; | |
80 | ||
81 | void setInterruptTimeout(qint64 ms); | |
82 | qint64 interruptTimeout() const; | |
83 | void setInterruptOnTimeout(bool value); | |
84 | bool isInterruptOnTimeout() const; | |
85 | void setFrameRate(qreal value); | |
86 | qreal forcedFrameRate() const; | |
87 | // const Statistics& statistics() const; | |
88 | // bool installFilter(AudioFilter* filter, int index = 0x7FFFFFFF); | |
89 | // bool installFilter(VideoFilter* filter, int index = 0x7FFFFFFF); | |
90 | // bool uninstallFilter(AudioFilter* filter); | |
91 | // bool uninstallFilter(VideoFilter* filter); | |
92 | // QList<Filter*> audioFilters() const; | |
93 | // QList<Filter*> videoFilters() const; | |
94 | // void setPriority(const QVector<QtAV::VideoDecoderId>& ids); | |
95 | void setVideoDecoderPriority(const QStringList& names); | |
96 | QStringList videoDecoderPriority() const; | |
97 | //void setPriority(const QVector<AudioOutputId>& ids); | |
98 | int brightness() const; | |
99 | int contrast() const; | |
100 | int hue() const; //not implemented | |
101 | int saturation() const; | |
102 | unsigned int chapters() const; | |
103 | void setOptionsForFormat(const QVariantHash &dict); | |
104 | QVariantHash optionsForFormat() const; | |
105 | void setOptionsForAudioCodec(const QVariantHash &dict); | |
106 | QVariantHash optionsForAudioCodec() const; | |
107 | void setOptionsForVideoCodec(const QVariantHash& dict); | |
108 | QVariantHash optionsForVideoCodec() const; | |
109 | ||
110 | // MediaEndAction mediaEndAction() const; | |
111 | // void setMediaEndAction(MediaEndAction value); | |
112 | ||
113 | ||
114 | ||
115 | public Q_SLOTS: | |
116 | bool load(); | |
117 | ||
118 | void togglePause(); | |
119 | void pause(bool p = true); | |
120 | void play(); | |
121 | void stop(); | |
122 | void stepForward(); | |
123 | void stepBackward(); | |
124 | ||
125 | void setRelativeTimeMode(bool value); | |
126 | void setRepeat(int max); | |
127 | void setStartPosition(qint64 pos); | |
128 | // void setStopPosition(qint64 pos = std::numeric_limits<qint64>::max()); | |
129 | // void setTimeRange(qint64 start, qint64 stop = std::numeric_limits<qint64>::max()); | |
130 | ||
131 | bool isSeekable() const; | |
132 | void setPosition(qint64 position); | |
133 | void seek(qreal r); // r: [0, 1] | |
134 | void seek(qint64 pos); //ms. same as setPosition(pos) | |
135 | void seekForward(); | |
136 | void seekBackward(); | |
137 | void seekNextChapter(); | |
138 | void seekPreviousChapter(); | |
139 | // void setSeekType(SeekType type); | |
140 | // SeekType seekType() const; | |
141 | ||
142 | qreal bufferProgress() const; | |
143 | qreal bufferSpeed() const; | |
144 | qint64 buffered() const; | |
145 | // void setBufferMode(BufferMode mode); | |
146 | // BufferMode bufferMode() const; | |
147 | void setBufferValue(qint64 value); | |
148 | int bufferValue() const; | |
149 | ||
150 | void setNotifyInterval(int msec); | |
151 | int notifyInterval() const; | |
152 | void updateClock(qint64 msecs); //update AVClock's external clock | |
153 | void setBrightness(int val); | |
154 | void setContrast(int val); | |
155 | void setHue(int val); //not implemented | |
156 | void setSaturation(int val); | |
157 | ||
158 | Q_SIGNALS: | |
159 | void bufferProgressChanged(qreal); | |
160 | void relativeTimeModeChanged(); | |
161 | void autoLoadChanged(); | |
162 | void asyncLoadChanged(); | |
163 | void muteChanged(); | |
164 | void sourceChanged(); | |
165 | void loaded(); // == mediaStatusChanged(QtAV::LoadedMedia) | |
166 | void mediaStatusChanged(QtAV::MediaStatus status); | |
167 | // void mediaEndActionChanged(QtAV::MediaEndAction action); | |
168 | void durationChanged(qint64); | |
169 | // void error(const QtAV::AVError& e); //explictly use QtAV::AVError in connection for Qt4 syntax | |
170 | void paused(bool p); | |
171 | void started(); | |
172 | void stopped(); | |
173 | void stoppedAt(qint64 position); | |
174 | void stateChanged(QtAV::AVPlayer::State state); | |
175 | void speedChanged(qreal speed); | |
176 | void repeatChanged(int r); | |
177 | void currentRepeatChanged(int r); | |
178 | void startPositionChanged(qint64 position); | |
179 | void stopPositionChanged(qint64 position); | |
180 | void seekableChanged(); | |
181 | void seekFinished(qint64 position); | |
182 | void positionChanged(qint64 position); | |
183 | void interruptTimeoutChanged(); | |
184 | void interruptOnTimeoutChanged(); | |
185 | void notifyIntervalChanged(); | |
186 | void brightnessChanged(int val); | |
187 | void contrastChanged(int val); | |
188 | void hueChanged(int val); | |
189 | void saturationChanged(int val); | |
190 | void chaptersChanged(unsigned int val); | |
191 | void subtitleStreamChanged(int value); | |
192 | void internalAudioTracksChanged(const QVariantList& tracks); | |
193 | void internalVideoTracksChanged(const QVariantList& tracks); | |
194 | void externalAudioTracksChanged(const QVariantList& tracks); | |
195 | void internalSubtitleTracksChanged(const QVariantList& tracks); | |
196 | void internalSubtitleHeaderRead(const QByteArray& codec, const QByteArray& data); | |
197 | // void internalSubtitlePacketRead(int track, const QtAV::Packet& packet); | |
198 | ||
199 | protected: | |
200 | virtual void timerEvent(QTimerEvent *); | |
201 | }; | |
202 | ||
203 | }; |
0 | namespace QtAV | |
1 | { | |
2 | ||
3 | class Frame | |
4 | { | |
5 | %TypeHeaderCode | |
6 | #include <QtAV/Frame.h> | |
7 | %End | |
8 | ||
9 | public: | |
10 | Frame(const Frame& other); | |
11 | virtual ~Frame() = 0; | |
12 | ||
13 | // NOTE: We don't expose the assignment operators. | |
14 | //Frame& operator =(const Frame &other); | |
15 | ||
16 | int planeCount() const; | |
17 | virtual int channelCount() const; | |
18 | ||
19 | //int bytesPerLine(int plane = 0) const; | |
20 | //QByteArray frameData() const; | |
21 | //int dataAlignment() const; | |
22 | //uchar* frameDataPtr(int* size = NULL) const; | |
23 | //QByteArray data(int plane = 0) const; | |
24 | //uchar* bits(int plane = 0); | |
25 | //const uchar *bits(int plane = 0) const; | |
26 | //const uchar* constBits(int plane = 0) const; | |
27 | //void setBits(uchar *b, int plane = 0); | |
28 | //void setBits(const QVector<uchar*>& b); | |
29 | //void setBits(quint8 *slice[]); | |
30 | ||
31 | //void setBytesPerLine(int lineSize, int plane = 0); | |
32 | //void setBytesPerLine(const QVector<int>& lineSize); | |
33 | //void setBytesPerLine(int stride[]); | |
34 | ||
35 | QVariantMap availableMetaData() const; | |
36 | QVariant metaData(const QString& key) const; | |
37 | void setMetaData(const QString &key, const QVariant &value); | |
38 | void setTimestamp(qreal ts); | |
39 | qreal timestamp() const; | |
40 | void swap(Frame &other); | |
41 | }; | |
42 | ||
43 | }; |
0 | namespace QtAV | |
1 | { | |
2 | ||
3 | class OpenGLRendererBase : public QtAV::VideoRenderer | |
4 | { | |
5 | %TypeHeaderCode | |
6 | #include <QtAV/OpenGLRendererBase.h> | |
7 | %End | |
8 | ||
9 | public: | |
10 | virtual ~OpenGLRendererBase(); | |
11 | bool isSupported(VideoFormat::PixelFormat pixfmt) const; | |
12 | OpenGLVideo* opengl() const; | |
13 | ||
14 | protected: | |
15 | virtual bool receiveFrame(const VideoFrame& frame); | |
16 | virtual void drawBackground(); | |
17 | virtual void drawFrame(); | |
18 | void onInitializeGL(); | |
19 | void onPaintGL(); | |
20 | void onResizeGL(int w, int h); | |
21 | void onResizeEvent(int w, int h); | |
22 | void onShowEvent(); | |
23 | ||
24 | private: | |
25 | OpenGLRendererBase(); | |
26 | }; | |
27 | ||
28 | }; |
0 | namespace QtAV | |
1 | { | |
2 | ||
3 | class OpenGLVideo : public QObject | |
4 | { | |
5 | %TypeHeaderCode | |
6 | #include <QtAV/OpenGLVideo.h> | |
7 | %End | |
8 | ||
9 | public: | |
10 | enum MeshType { | |
11 | RectMesh, | |
12 | SphereMesh | |
13 | }; | |
14 | ||
15 | static bool isSupported(VideoFormat::PixelFormat pixfmt); | |
16 | OpenGLVideo(); | |
17 | void setOpenGLContext(QOpenGLContext *ctx); | |
18 | QOpenGLContext* openGLContext(); | |
19 | void setCurrentFrame(const VideoFrame& frame); | |
20 | void fill(const QColor& color); | |
21 | void render(const QRectF& target = QRectF(), const QRectF& roi = QRectF(), const QMatrix4x4& transform = QMatrix4x4()); | |
22 | void setProjectionMatrixToRect(const QRectF& v); | |
23 | void setViewport(const QRectF& r); | |
24 | ||
25 | void setBrightness(qreal value); | |
26 | void setContrast(qreal value); | |
27 | void setHue(qreal value); | |
28 | void setSaturation(qreal value); | |
29 | ||
30 | // TODO: Add after we expose VideoShader | |
31 | //void setUserShader(VideoShader* shader); | |
32 | //VideoShader* userShader() const; | |
33 | ||
34 | void setMeshType(MeshType value); | |
35 | MeshType meshType() const; | |
36 | signals: | |
37 | void beforeRendering(); | |
38 | void afterRendering(); | |
39 | }; | |
40 | ||
41 | }; |
0 | namespace QtAV | |
1 | { | |
2 | ||
3 | class QPainterRenderer : public QtAV::VideoRenderer /Abstract/ | |
4 | { | |
5 | %TypeHeaderCode | |
6 | #include <QtAV/QPainterRenderer.h> | |
7 | %End | |
8 | ||
9 | public: | |
10 | QPainterRenderer(); | |
11 | bool isSupported(VideoFormat::PixelFormat pixfmt) const; | |
12 | protected: | |
13 | bool preparePixmap(const VideoFrame& frame); | |
14 | void drawBackground(); | |
15 | void drawFrame(); | |
16 | }; | |
17 | ||
18 | }; |
0 | %Module(name=QtAV, keyword_arguments="Optional") | |
1 | ||
2 | %Import QtGui/QtGuimod.sip | |
3 | %Import QtWidgets/QtWidgetsmod.sip | |
4 | %Import QtOpenGL/QtOpenGLmod.sip | |
5 | ||
6 | %Timeline {QtAV_1_11_0 QtAV_1_12_0} | |
7 | ||
8 | %Feature FULL_API | |
9 | ||
10 | %Copying | |
11 | QtAV: Multimedia framework based on Qt and FFmpeg | |
12 | Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com> | |
13 | ||
14 | This file is part of QtAV (from 2015) | |
15 | ||
16 | This library is free software; you can redistribute it and/or | |
17 | modify it under the terms of the GNU Lesser General Public | |
18 | License as published by the Free Software Foundation; either | |
19 | version 2.1 of the License, or (at your option) any later version. | |
20 | ||
21 | This library is distributed in the hope that it will be useful, | |
22 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
23 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
24 | Lesser General Public License for more details. | |
25 | ||
26 | You should have received a copy of the GNU Lesser General Public | |
27 | License along with this library; if not, write to the Free Software | |
28 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
29 | %End | |
30 | ||
31 | %HideNamespace QtAV | |
32 | ||
33 | %Include global.sip | |
34 | %Include VideoOutput.sip | |
35 | %Include AVError.sip | |
36 | %Include AVPlayer.sip | |
37 | ||
38 | %If (FULL_API) | |
39 | %Include VideoFormat.sip | |
40 | %Include Frame.sip | |
41 | %Include VideoFrame.sip | |
42 | %Include QPainterRenderer.sip | |
43 | %Include AVOutput.sip | |
44 | %Include VideoRenderer.sip | |
45 | %Include OpenGLRendererBase.sip | |
46 | %Include OpenGLVideo.sip | |
47 | %End |
0 | namespace QtAV | |
1 | { | |
2 | ||
3 | class VideoFormat | |
4 | { | |
5 | %TypeHeaderCode | |
6 | #include <QtAV/VideoFormat.h> | |
7 | %End | |
8 | ||
9 | public: | |
10 | enum PixelFormat { | |
11 | Format_Invalid = -1, | |
12 | Format_ARGB32, // AARRGGBB or 00RRGGBB, check hasAlpha is required | |
13 | Format_BGRA32, //BBGGRRAA | |
14 | Format_ABGR32, // QImage.RGBA8888 le | |
15 | Format_RGBA32, // QImage. no | |
16 | Format_RGB32, // 0xAARRGGBB native endian. same as QImage::Format_ARGB32. be: ARGB32, le: BGRA32 | |
17 | Format_BGR32, // 0xAABBGGRR native endian | |
18 | Format_RGB24, | |
19 | Format_BGR24, | |
20 | Format_RGB565, | |
21 | Format_BGR565, | |
22 | Format_RGB555, | |
23 | Format_BGR555, | |
24 | ||
25 | //http://www.fourcc.org/yuv.php | |
26 | Format_AYUV444, | |
27 | Format_YUV444P, | |
28 | Format_YUV422P, | |
29 | Format_YUV420P, | |
30 | Format_YUV411P, | |
31 | Format_YUV410P, | |
32 | Format_YV12, | |
33 | Format_UYVY, //422 | |
34 | Format_VYUY, //not in ffmpeg. OMX_COLOR_FormatCrYCbY | |
35 | Format_YUYV, //422, aka yuy2 | |
36 | Format_YVYU, //422 | |
37 | Format_NV12, | |
38 | Format_NV21, | |
39 | Format_IMC1, | |
40 | Format_IMC2, | |
41 | Format_IMC3, //same as IMC1, swap U V | |
42 | Format_IMC4, //same as IMC2, swap U V | |
43 | Format_Y8, //GREY. single 8 bit Y plane | |
44 | Format_Y16, //single 16 bit Y plane. LE | |
45 | ||
46 | Format_Jpeg, //yuvj | |
47 | ||
48 | //Format_CameraRaw, | |
49 | //Format_AdobeDng, | |
50 | ||
51 | Format_YUV420P9LE, | |
52 | Format_YUV422P9LE, | |
53 | Format_YUV444P9LE, | |
54 | Format_YUV420P10LE, | |
55 | Format_YUV422P10LE, | |
56 | Format_YUV444P10LE, | |
57 | Format_YUV420P12LE, | |
58 | Format_YUV422P12LE, | |
59 | Format_YUV444P12LE, | |
60 | Format_YUV420P14LE, | |
61 | Format_YUV422P14LE, | |
62 | Format_YUV444P14LE, | |
63 | Format_YUV420P16LE, | |
64 | Format_YUV422P16LE, | |
65 | Format_YUV444P16LE, | |
66 | Format_YUV420P9BE, | |
67 | Format_YUV422P9BE, | |
68 | Format_YUV444P9BE, | |
69 | Format_YUV420P10BE, | |
70 | Format_YUV422P10BE, | |
71 | Format_YUV444P10BE, | |
72 | Format_YUV420P12BE, | |
73 | Format_YUV422P12BE, | |
74 | Format_YUV444P12BE, | |
75 | Format_YUV420P14BE, | |
76 | Format_YUV422P14BE, | |
77 | Format_YUV444P14BE, | |
78 | Format_YUV420P16BE, | |
79 | Format_YUV422P16BE, | |
80 | Format_YUV444P16BE, | |
81 | ||
82 | Format_RGB48, // native endian | |
83 | Format_RGB48LE, | |
84 | Format_RGB48BE, | |
85 | Format_BGR48, | |
86 | Format_BGR48LE, | |
87 | Format_BGR48BE, | |
88 | Format_RGBA64, //native endian | |
89 | Format_RGBA64LE, | |
90 | Format_RGBA64BE, | |
91 | Format_BGRA64, //native endian | |
92 | Format_BGRA64LE, | |
93 | Format_BGRA64BE, | |
94 | ||
95 | Format_VYU, // for rgb422_apple texture, the layout is like rgb24: (v, y, u, ) | |
96 | Format_XYZ12, | |
97 | Format_XYZ12LE, | |
98 | Format_XYZ12BE, | |
99 | Format_User | |
100 | }; | |
101 | ||
102 | static PixelFormat pixelFormatFromImageFormat(QImage::Format format); | |
103 | static QImage::Format imageFormatFromPixelFormat(PixelFormat format); | |
104 | static PixelFormat pixelFormatFromFFmpeg(int ff); //AVPixelFormat | |
105 | static int pixelFormatToFFmpeg(PixelFormat fmt); | |
106 | static QVector<int> pixelFormatsFFmpeg(); | |
107 | ||
108 | VideoFormat(PixelFormat format = Format_Invalid); | |
109 | VideoFormat(int formatFF /Constrained/); | |
110 | VideoFormat(QImage::Format fmt /Constrained/); | |
111 | VideoFormat(const QString& name); | |
112 | VideoFormat(const VideoFormat &other /Constrained/); | |
113 | ~VideoFormat(); | |
114 | ||
115 | // NOTE: We don't expose the assignment operators. | |
116 | //VideoFormat& operator=(const VideoFormat &other); | |
117 | //VideoFormat& operator=(VideoFormat::PixelFormat pixfmt); | |
118 | //VideoFormat& operator=(QImage::Format qpixfmt); | |
119 | //VideoFormat& operator=(int ffpixfmt); | |
120 | bool operator==(const VideoFormat &other) const; | |
121 | bool operator==(VideoFormat::PixelFormat pixfmt /Constrained/) const; | |
122 | bool operator==(QImage::Format qpixfmt /Constrained/) const; | |
123 | bool operator==(int ffpixfmt /Constrained/) const; | |
124 | bool operator!=(const VideoFormat &other) const; | |
125 | bool operator!=(VideoFormat::PixelFormat pixfmt /Constrained/) const; | |
126 | bool operator!=(QImage::Format qpixfmt /Constrained/) const; | |
127 | bool operator!=(int ffpixfmt /Constrained/) const; | |
128 | ||
129 | bool isValid() const; | |
130 | ||
131 | PixelFormat pixelFormat() const; | |
132 | int pixelFormatFFmpeg() const; | |
133 | QImage::Format imageFormat() const; | |
134 | QString name() const; | |
135 | ||
136 | void setPixelFormat(PixelFormat format); | |
137 | void setPixelFormatFFmpeg(int format); | |
138 | ||
139 | int channels() const; | |
140 | int channels(int plane) const; | |
141 | int planeCount() const; | |
142 | int bitsPerPixel() const; | |
143 | /// nv12: 16 for uv plane | |
144 | int bitsPerPixel(int plane) const; | |
145 | /// bgr24 is 24 not 32 | |
146 | int bitsPerPixelPadded() const; | |
147 | int bytesPerPixel() const; | |
148 | int bytesPerPixel(int plane) const; | |
149 | int bitsPerComponent() const; | |
150 | ||
151 | int bytesPerLine(int width, int plane) const; | |
152 | int chromaWidth(int lumaWidth) const; | |
153 | int chromaHeight(int lumaHeight) const; | |
154 | int width(int lumaWidth, int plane) const; | |
155 | int height(int lumaHeight, int plane) const; | |
156 | qreal normalizedWidth(int plane) const; | |
157 | qreal normalizedHeight(int plane) const; | |
158 | bool isBigEndian() const; | |
159 | bool hasPalette() const; | |
160 | bool isPseudoPaletted() const; | |
161 | bool isBitStream() const; | |
162 | bool isHWAccelerated() const; | |
163 | bool isPlanar() const; | |
164 | bool isRGB() const; | |
165 | bool isXYZ() const; | |
166 | bool hasAlpha() const; | |
167 | ||
168 | static bool isPlanar(PixelFormat pixfmt); | |
169 | static bool isRGB(PixelFormat pixfmt); | |
170 | static bool hasAlpha(PixelFormat pixfmt); | |
171 | }; | |
172 | ||
173 | //QDebug operator<<(QDebug debug, const VideoFormat &fmt); | |
174 | //QDebug operator<<(QDebug debug, VideoFormat::PixelFormat pixFmt); | |
175 | ||
176 | }; |
0 | namespace QtAV | |
1 | { | |
2 | ||
3 | class VideoFrame : public QtAV::Frame | |
4 | { | |
5 | %TypeHeaderCode | |
6 | #include <QtAV/VideoFrame.h> | |
7 | %End | |
8 | ||
9 | public: | |
10 | //static VideoFrame fromGPU(const VideoFormat& fmt, int width, int height, int surface_h, quint8 *src[], int pitch[], bool optimized = true, bool swapUV = false); | |
11 | //static void copyPlane(quint8 *dst, size_t dst_stride, const quint8 *src, size_t src_stride, unsigned byteWidth, unsigned height); | |
12 | ||
13 | public: | |
14 | ||
15 | VideoFrame(); | |
16 | VideoFrame(int width, int height, const VideoFormat& format, const QByteArray& data = QByteArray(), int alignment = 1); | |
17 | VideoFrame(const QImage& image); | |
18 | VideoFrame(const VideoFrame &other); | |
19 | ~VideoFrame(); | |
20 | ||
21 | // NOTE: We don't expose the assignment operators. | |
22 | //VideoFrame &operator=(const VideoFrame &other); | |
23 | ||
24 | int channelCount() const; | |
25 | VideoFrame clone() const; | |
26 | VideoFormat format() const; | |
27 | VideoFormat::PixelFormat pixelFormat() const; | |
28 | QImage::Format imageFormat() const; | |
29 | int pixelFormatFFmpeg() const; | |
30 | ||
31 | bool isValid() const; | |
32 | operator bool() const; | |
33 | ||
34 | QSize size() const; | |
35 | int width() const; | |
36 | int height() const; | |
37 | int effectiveBytesPerLine(int plane) const; | |
38 | int planeWidth(int plane) const; | |
39 | int planeHeight(int plane) const; | |
40 | float displayAspectRatio() const; | |
41 | void setDisplayAspectRatio(float displayAspectRatio); | |
42 | ||
43 | // TODO: After we expose ColorSpace and ColorRange exposed. | |
44 | //ColorSpace colorSpace() const; | |
45 | //void setColorSpace(ColorSpace value); | |
46 | //ColorRange colorRange() const; | |
47 | //void setColorRange(ColorRange value); | |
48 | ||
49 | QImage toImage(QImage::Format fmt = QImage::Format_ARGB32, const QSize& dstSize = QSize(), const QRectF& roi = QRect()) const; | |
50 | VideoFrame to(VideoFormat::PixelFormat pixfmt, const QSize& dstSize = QSize(), const QRectF& roi = QRect()) const; | |
51 | VideoFrame to(const VideoFormat& fmt, const QSize& dstSize = QSize(), const QRectF& roi = QRect()) const; | |
52 | ||
53 | // TODO | |
54 | //bool to(VideoFormat::PixelFormat pixfmt, quint8 *const dst[], const int dstStride[], const QSize& dstSize = QSize(), const QRectF& roi = QRect()) const; | |
55 | //bool to(const VideoFormat& fmt, quint8 *const dst[], const int dstStride[], const QSize& dstSize = QSize(), const QRectF& roi = QRect()) const; | |
56 | ||
57 | //void* map(SurfaceType type, void* handle, int plane = 0); | |
58 | //void* map(SurfaceType type, void* handle, const VideoFormat& fmt, int plane = 0); | |
59 | ||
60 | void unmap(void* handle); | |
61 | ||
62 | //void* createInteropHandle(void* handle, SurfaceType type, int plane); | |
63 | }; | |
64 | ||
65 | ||
66 | class VideoFrameConverter | |
67 | { | |
68 | public: | |
69 | VideoFrameConverter(); | |
70 | ~VideoFrameConverter(); | |
71 | void setEq(int brightness, int contrast, int saturation); | |
72 | VideoFrame convert(const VideoFrame& frame, const VideoFormat& fmt /Constrained/) const; | |
73 | VideoFrame convert(const VideoFrame& frame, VideoFormat::PixelFormat fmt /Constrained/) const; | |
74 | VideoFrame convert(const VideoFrame& frame, QImage::Format fmt /Constrained/) const; | |
75 | VideoFrame convert(const VideoFrame& frame, int fffmt /Constrained/) const; | |
76 | }; | |
77 | ||
78 | }; |
0 | namespace QtAV | |
1 | { | |
2 | ||
3 | class VideoOutput : public QObject, public QtAV::VideoRenderer | |
4 | { | |
5 | %TypeHeaderCode | |
6 | #include <QtAV/VideoOutput.h> | |
7 | %End | |
8 | ||
9 | public: | |
10 | VideoOutput(QWidget* parent /TransferThis/ = 0); | |
11 | VideoOutput(VideoRendererId rendererId, QObject *parent /TransferThis/ = 0); | |
12 | ~VideoOutput(); | |
13 | ||
14 | VideoRendererId id() const; | |
15 | ||
16 | virtual QWidget* widget() final; | |
17 | }; | |
18 | ||
19 | }; |
0 | namespace QtAV | |
1 | { | |
2 | ||
3 | typedef int VideoRendererId /NoTypeName/; | |
4 | ||
5 | class VideoRenderer : public QtAV::AVOutput /Abstract/ | |
6 | { | |
7 | %TypeHeaderCode | |
8 | #include <QtAV/VideoRenderer.h> | |
9 | %End | |
10 | ||
11 | public: | |
12 | enum OutAspectRatioMode { | |
13 | RendererAspectRatio //Use renderer's aspect ratio, i.e. stretch to fit the renderer rect | |
14 | , VideoAspectRatio //Use video's aspect ratio and align center in renderer. | |
15 | , CustomAspectRation //Use the ratio set by setOutAspectRatio(qreal). Mode will be set to this if that function is called | |
16 | //, AspectRatio4_3, AspectRatio16_9 | |
17 | }; | |
18 | ||
19 | VideoRenderer(); | |
20 | virtual ~VideoRenderer(); | |
21 | virtual VideoRendererId id() const = 0; | |
22 | ||
23 | private: | |
24 | VideoRenderer(const VideoRenderer& r); | |
25 | }; | |
26 | ||
27 | }; |
0 | %ModuleCode | |
1 | #include <QtAV/QtAV_Global.h> | |
2 | %End | |
3 | ||
4 | namespace QtAV { | |
5 | ||
6 | unsigned Version(); | |
7 | %MethodCode | |
8 | ::QtAV_Version(); | |
9 | %End | |
10 | ||
11 | QString Version_String(); | |
12 | %MethodCode | |
13 | ::QtAV_Version_String(); | |
14 | %End | |
15 | ||
16 | QString Version_String_Long(); | |
17 | %MethodCode | |
18 | ::QtAV_Version_String_Long(); | |
19 | %End | |
20 | ||
21 | enum LogLevel { | |
22 | LogOff, | |
23 | LogDebug, // log all | |
24 | LogWarning, // log warning, critical, fatal | |
25 | LogCritical, // log critical, fatal | |
26 | LogFatal, // log fatal | |
27 | LogAll | |
28 | }; | |
29 | QString aboutFFmpeg_PlainText(); | |
30 | QString aboutFFmpeg_HTML(); | |
31 | QString aboutQtAV_PlainText(); | |
32 | QString aboutQtAV_HTML(); | |
33 | void setLogLevel(LogLevel value); | |
34 | LogLevel logLevel(); | |
35 | //void setFFmpegLogHandler(void(*)(void *, int, const char *, va_list)); | |
36 | void setFFmpegLogLevel(const QByteArray& level); | |
37 | ||
38 | QString avformatOptions(); | |
39 | QString avcodecOptions(); | |
40 | ||
41 | enum MediaStatus | |
42 | { | |
43 | UnknownMediaStatus, | |
44 | NoMedia, | |
45 | LoadingMedia, // when source is set | |
46 | LoadedMedia, // if auto load and source is set. player is stopped state | |
47 | StalledMedia, // insufficient buffering or other interruptions (timeout, user interrupt) | |
48 | BufferingMedia, // NOT IMPLEMENTED | |
49 | BufferedMedia, // when playing //NOT IMPLEMENTED | |
50 | EndOfMedia, // Playback has reached the end of the current media. The player is in the StoppedState. | |
51 | InvalidMedia // what if loop > 0 or stopPosition() is not mediaStopPosition()? | |
52 | }; | |
53 | ||
54 | enum BufferMode { | |
55 | BufferTime, | |
56 | BufferBytes, | |
57 | BufferPackets | |
58 | }; | |
59 | ||
60 | enum MediaEndActionFlag { | |
61 | MediaEndAction_Default, | |
62 | MediaEndAction_KeepDisplay, | |
63 | MediaEndAction_Pause | |
64 | }; | |
65 | ||
66 | //Q_DECLARE_FLAGS(MediaEndAction, MediaEndActionFlag) | |
67 | ||
68 | enum SeekUnit { | |
69 | SeekByTime, // only this is supported now | |
70 | SeekByByte, | |
71 | SeekByFrame | |
72 | }; | |
73 | enum SeekType { | |
74 | AccurateSeek, // slow | |
75 | KeyFrameSeek, // fast | |
76 | AnyFrameSeek | |
77 | }; | |
78 | ||
79 | ||
80 | enum ColorSpace { | |
81 | ColorSpace_Unknown, | |
82 | ColorSpace_RGB, | |
83 | ColorSpace_GBR, // for planar gbr format(e.g. video from x264) used in glsl | |
84 | ColorSpace_BT601, | |
85 | ColorSpace_BT709, | |
86 | ColorSpace_XYZ | |
87 | }; | |
88 | ||
89 | ||
90 | enum ColorRange { | |
91 | ColorRange_Unknown, | |
92 | ColorRange_Limited, // TV, MPEG | |
93 | ColorRange_Full // PC, JPEG | |
94 | }; | |
95 | ||
96 | ||
97 | enum SurfaceType { | |
98 | HostMemorySurface, | |
99 | GLTextureSurface, | |
100 | SourceSurface, | |
101 | UserSurface = 0xffff | |
102 | }; | |
103 | ||
104 | }; |
0 | namespace QtAV | |
1 | { | |
2 | ||
3 | class GLWidgetRenderer2 : public QGLWidget, public QtAV::OpenGLRendererBase | |
4 | { | |
5 | %TypeHeaderCode | |
6 | #include <QtAVWidgets/GLWidgetRenderer2.h> | |
7 | %End | |
8 | ||
9 | public: | |
10 | void setBrightness(qreal brightness); | |
11 | qreal brightness(); | |
12 | void setContrast(qreal contrast); | |
13 | qreal contrast(); | |
14 | void setHue(qreal hue); | |
15 | qreal hue(); | |
16 | void setSaturation(qreal saturation); | |
17 | qreal saturation(); | |
18 | void setBackgroundColor(QColor color); | |
19 | QColor backgroundColor(); | |
20 | ||
21 | QRectF regionOfInterest(); | |
22 | void setRegionOfInterest(QRectF region); | |
23 | qreal sourceAspectRatio(); | |
24 | qreal outAspectRatio(); | |
25 | void setOutAspectRatio(qreal ratio); | |
26 | OutAspectRatioMode outAspectRatioMode(); | |
27 | void setOutAspectRatioMode(OutAspectRatioMode mode); | |
28 | ||
29 | int orientation(); | |
30 | void setOrientation(int orientation); | |
31 | QRect videoRect(); | |
32 | QSize videoFrameSize(); | |
33 | ||
34 | public: | |
35 | GLWidgetRenderer2(QWidget* parent /TransferThis/ = 0, const QGLWidget* shareWidget = 0, Qt::WindowFlags f = 0); | |
36 | virtual VideoRendererId id(); | |
37 | virtual QWidget* widget(); | |
38 | ||
39 | signals: | |
40 | void sourceAspectRatioChanged(qreal value) final; | |
41 | void regionOfInterestChanged(); | |
42 | void outAspectRatioChanged(); | |
43 | void outAspectRatioModeChanged(); | |
44 | void brightnessChanged(qreal value); | |
45 | void contrastChanged(qreal); | |
46 | void hueChanged(qreal); | |
47 | void saturationChanged(qreal); | |
48 | void orientationChanged(); | |
49 | void videoRectChanged(); | |
50 | void videoFrameSizeChanged(); | |
51 | void backgroundColorChanged(); | |
52 | ||
53 | protected: | |
54 | virtual void initializeGL(); | |
55 | virtual void paintGL(); | |
56 | virtual void resizeGL(int w, int h); | |
57 | virtual void resizeEvent(QResizeEvent *); // not virtual in QGLWidget (Qt<5.5) | |
58 | virtual void showEvent(QShowEvent *); | |
59 | ||
60 | }; | |
61 | typedef GLWidgetRenderer2 VideoRendererGLWidget2; | |
62 | ||
63 | }; |
0 | namespace QtAV | |
1 | { | |
2 | ||
3 | class GraphicsItemRenderer : public QGraphicsObject, public QtAV::QPainterRenderer | |
4 | { | |
5 | %TypeHeaderCode | |
6 | #include <QtAVWidgets/GraphicsItemRenderer.h> | |
7 | %End | |
8 | ||
9 | public: | |
10 | void setBrightness(qreal brightness); | |
11 | qreal brightness(); | |
12 | void setContrast(qreal contrast); | |
13 | qreal contrast(); | |
14 | void setHue(qreal hue); | |
15 | qreal hue(); | |
16 | void setSaturation(qreal saturation); | |
17 | qreal saturation(); | |
18 | void setBackgroundColor(QColor color); | |
19 | QColor backgroundColor(); | |
20 | ||
21 | QRectF regionOfInterest(); | |
22 | void setRegionOfInterest(QRectF region); | |
23 | qreal sourceAspectRatio(); | |
24 | qreal outAspectRatio(); | |
25 | void setOutAspectRatio(qreal ratio); | |
26 | OutAspectRatioMode outAspectRatioMode(); | |
27 | void setOutAspectRatioMode(OutAspectRatioMode mode); | |
28 | ||
29 | int orientation(); | |
30 | void setOrientation(int orientation); | |
31 | QRect videoRect(); | |
32 | QSize videoFrameSize(); | |
33 | ||
34 | public: | |
35 | GraphicsItemRenderer(QGraphicsItem* parent /TransferThis/ = 0); | |
36 | virtual VideoRendererId id(); | |
37 | ||
38 | bool isSupported(VideoFormat::PixelFormat pixfmt); | |
39 | ||
40 | QRectF boundingRect() const; | |
41 | void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); | |
42 | QGraphicsItem* graphicsItem(); | |
43 | ||
44 | bool isOpenGL() const; | |
45 | void setOpenGL(bool o); | |
46 | ||
47 | OpenGLVideo* opengl() const; | |
48 | ||
49 | signals: | |
50 | void sourceAspectRatioChanged(qreal value) final; | |
51 | void regionOfInterestChanged(); | |
52 | void outAspectRatioChanged(); | |
53 | void outAspectRatioModeChanged(); | |
54 | void brightnessChanged(qreal value); | |
55 | void contrastChanged(qreal); | |
56 | void hueChanged(qreal); | |
57 | void saturationChanged(qreal); | |
58 | void backgroundColorChanged(); | |
59 | void orientationChanged(); | |
60 | void videoRectChanged(); | |
61 | void videoFrameSizeChanged(); | |
62 | void openGLChanged(); | |
63 | ||
64 | protected: | |
65 | bool receiveFrame(const VideoFrame& frame); | |
66 | void drawBackground(); | |
67 | void drawFrame(); | |
68 | bool event(QEvent *event); | |
69 | }; | |
70 | typedef GraphicsItemRenderer VideoRendererGraphicsItem; | |
71 | ||
72 | }; |
0 | namespace QtAV | |
1 | { | |
2 | ||
3 | class OpenGLWidgetRenderer : public QOpenGLWidget, public QtAV::OpenGLRendererBase | |
4 | { | |
5 | %TypeHeaderCode | |
6 | #include <QtAVWidgets/OpenGLWidgetRenderer.h> | |
7 | %End | |
8 | ||
9 | public: | |
10 | void setBrightness(qreal brightness); | |
11 | qreal brightness(); | |
12 | void setContrast(qreal contrast); | |
13 | qreal contrast(); | |
14 | void setHue(qreal hue); | |
15 | qreal hue(); | |
16 | void setSaturation(qreal saturation); | |
17 | qreal saturation(); | |
18 | void setBackgroundColor(QColor color); | |
19 | QColor backgroundColor(); | |
20 | ||
21 | QRectF regionOfInterest(); | |
22 | void setRegionOfInterest(QRectF region); | |
23 | qreal sourceAspectRatio(); | |
24 | qreal outAspectRatio(); | |
25 | void setOutAspectRatio(qreal ratio); | |
26 | OutAspectRatioMode outAspectRatioMode(); | |
27 | void setOutAspectRatioMode(OutAspectRatioMode mode); | |
28 | ||
29 | int orientation(); | |
30 | void setOrientation(int orientation); | |
31 | QRect videoRect(); | |
32 | QSize videoFrameSize(); | |
33 | ||
34 | public: | |
35 | explicit OpenGLWidgetRenderer(QWidget* parent /TransferThis/ = 0, Qt::WindowFlags f = 0); | |
36 | virtual VideoRendererId id(); | |
37 | virtual QWidget* widget(); | |
38 | ||
39 | signals: | |
40 | void sourceAspectRatioChanged(qreal value) final; | |
41 | void regionOfInterestChanged(); | |
42 | void outAspectRatioChanged(); | |
43 | void outAspectRatioModeChanged(); | |
44 | void brightnessChanged(qreal value); | |
45 | void contrastChanged(qreal); | |
46 | void hueChanged(qreal); | |
47 | void saturationChanged(qreal); | |
48 | void orientationChanged(); | |
49 | void videoRectChanged(); | |
50 | void videoFrameSizeChanged(); | |
51 | void backgroundColorChanged(); | |
52 | ||
53 | protected: | |
54 | virtual void initializeGL(); | |
55 | virtual void paintGL(); | |
56 | virtual void resizeGL(int w, int h); | |
57 | virtual void resizeEvent(QResizeEvent *); // not virtual in QGLWidget (Qt<5.5) | |
58 | virtual void showEvent(QShowEvent *); | |
59 | ||
60 | }; | |
61 | typedef OpenGLWidgetRenderer VideoRendererOpenGLWidget; | |
62 | ||
63 | }; |
0 | %Module(name=QtAVWidgets, keyword_arguments="Optional") | |
1 | ||
2 | %Import QtGui/QtGuimod.sip | |
3 | %Import QtWidgets/QtWidgetsmod.sip | |
4 | %Import QtOpenGL/QtOpenGLmod.sip | |
5 | %Import QtAV/QtAVmod.sip | |
6 | ||
7 | %Timeline {QtAVWidgets_1_11_0 QtAVWidgets_1_12_0} | |
8 | ||
9 | %Feature FULL_QTWIDGETS_API | |
10 | ||
11 | %Copying | |
12 | QtAV: Multimedia framework based on Qt and FFmpeg | |
13 | Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com> | |
14 | ||
15 | This file is part of QtAV (from 2015) | |
16 | ||
17 | This library is free software; you can redistribute it and/or | |
18 | modify it under the terms of the GNU Lesser General Public | |
19 | License as published by the Free Software Foundation; either | |
20 | version 2.1 of the License, or (at your option) any later version. | |
21 | ||
22 | This library is distributed in the hope that it will be useful, | |
23 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
24 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
25 | Lesser General Public License for more details. | |
26 | ||
27 | You should have received a copy of the GNU Lesser General Public | |
28 | License along with this library; if not, write to the Free Software | |
29 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
30 | %End | |
31 | ||
32 | %HideNamespace QtAV | |
33 | ||
34 | %Include global.sip | |
35 | ||
36 | %If (FULL_QTWIDGETS_API) | |
37 | %Include GLWidgetRenderer2.sip | |
38 | %Include GraphicsItemRenderer.sip | |
39 | // NOTE: We don't expose QOpenGLWidget because it is present in Qt >= 5.4.0 | |
40 | %Include OpenGLWidgetRenderer.sip | |
41 | %Include VideoPreviewWidget.sip | |
42 | %Include WidgetRenderer.sip | |
43 | %End |
0 | namespace QtAV | |
1 | { | |
2 | ||
3 | class VideoPreviewWidget : public QWidget | |
4 | { | |
5 | %TypeHeaderCode | |
6 | #include <QtAVWidgets/VideoPreviewWidget.h> | |
7 | %End | |
8 | ||
9 | public: | |
10 | explicit VideoPreviewWidget(QWidget *parent /TransferThis/ = 0); | |
11 | void setTimestamp(qint64 msec); | |
12 | qint64 timestamp() const; | |
13 | void preview(); | |
14 | void setFile(const QString& file); | |
15 | QString file() const; | |
16 | void setKeepAspectRatio(bool value = true); | |
17 | bool isKeepAspectRatio() const; | |
18 | bool isAutoDisplayFrame() const; | |
19 | void setAutoDisplayFrame(bool b=true); | |
20 | public slots: | |
21 | void displayFrame(const QtAV::VideoFrame& frame); | |
22 | void displayNoFrame(); | |
23 | signals: | |
24 | void timestampChanged(); | |
25 | void fileChanged(); | |
26 | void gotError(const QString &); | |
27 | void gotAbort(const QString &); | |
28 | void gotFrame(const QtAV::VideoFrame & frame); | |
29 | protected: | |
30 | virtual void resizeEvent(QResizeEvent *); | |
31 | }; | |
32 | ||
33 | }; |
0 | namespace QtAV | |
1 | { | |
2 | ||
3 | class WidgetRenderer : public QWidget, public QtAV::QPainterRenderer | |
4 | { | |
5 | %TypeHeaderCode | |
6 | #include <QtAVWidgets/WidgetRenderer.h> | |
7 | %End | |
8 | ||
9 | public: | |
10 | QColor backgroundColor(); | |
11 | void setBackgroundColor(QColor color); | |
12 | QRectF regionOfInterest(); | |
13 | void setRegionOfInterest(QRectF region); | |
14 | qreal sourceAspectRatio(); | |
15 | qreal outAspectRatio(); | |
16 | void setOutAspectRatio(qreal ratio); | |
17 | OutAspectRatioMode outAspectRatioMode(); | |
18 | void setOutAspectRatioMode(OutAspectRatioMode mode); | |
19 | int orientation(); | |
20 | void setOrientation(int orientation); | |
21 | QRect videoRect(); | |
22 | QSize videoFrameSize(); | |
23 | ||
24 | public: | |
25 | WidgetRenderer(QWidget* parent /TransferThis/ = 0, Qt::WindowFlags f = 0); | |
26 | virtual VideoRendererId id(); | |
27 | virtual QWidget* widget(); | |
28 | ||
29 | signals: | |
30 | void sourceAspectRatioChanged(qreal value) final; | |
31 | void regionOfInterestChanged(); | |
32 | void outAspectRatioChanged(); | |
33 | void outAspectRatioModeChanged(); | |
34 | void brightnessChanged(qreal value); | |
35 | void contrastChanged(qreal); | |
36 | void hueChanged(qreal); | |
37 | void saturationChanged(qreal); | |
38 | void backgroundColorChanged(); | |
39 | void orientationChanged(); | |
40 | void videoRectChanged(); | |
41 | void videoFrameSizeChanged(); | |
42 | void imageReady(); | |
43 | ||
44 | protected: | |
45 | bool receiveFrame(const VideoFrame& frame); | |
46 | void resizeEvent(QResizeEvent *); | |
47 | void paintEvent(QPaintEvent *); | |
48 | bool onSetOrientation(int value); | |
49 | }; | |
50 | typedef WidgetRenderer VideoRenderWidget; | |
51 | ||
52 | }; |
0 | %ModuleCode | |
1 | #include <QtAVWidgets/global.h> | |
2 | %End | |
3 | ||
4 | ||
5 | namespace QtAV { | |
6 | ||
7 | // Use custom code to allow QtAVWidgets.registerRenderers() instead of | |
8 | // QtAVWidgets.Widgets.registerRenderers() | |
9 | void registerRenderers(); | |
10 | %MethodCode | |
11 | QtAV::Widgets::registerRenderers(); | |
12 | %End | |
13 | ||
14 | VideoRendererId VideoRendererId_Widget; | |
15 | VideoRendererId VideoRendererId_GraphicsItem; | |
16 | VideoRendererId VideoRendererId_GLWidget; | |
17 | VideoRendererId VideoRendererId_GDI; | |
18 | VideoRendererId VideoRendererId_Direct2D; | |
19 | VideoRendererId VideoRendererId_XV; | |
20 | VideoRendererId VideoRendererId_X11; | |
21 | VideoRendererId VideoRendererId_GLWidget2; | |
22 | VideoRendererId VideoRendererId_OpenGLWidget; | |
23 | ||
24 | void about(); | |
25 | void aboutFFmpeg(); | |
26 | void aboutQtAV(); | |
27 | ||
28 | }; |
31 | 31 | # add HEADERS for moc |
32 | 32 | if(WIN32) |
33 | 33 | set(RC_FILE ${CMAKE_CURRENT_BINARY_DIR}/${MODULE}.rc) |
34 | configure_file(${CMAKE_SOURCE_DIR}/cmake/QtAV.rc.in ${RC_FILE}) | |
34 | configure_file(${QTAV_SOURCE_DIR}/cmake/QtAV.rc.in ${RC_FILE}) | |
35 | 35 | endif() |
36 | 36 | add_library(${MODULE} SHARED ${SOURCES} ${RESOURCES_SOURCES} ${HEADERS} ${RC_FILE}) |
37 | 37 | target_link_libraries(${MODULE} |
0 | 0 | /****************************************************************************** |
1 | 1 | QtAV: Multimedia framework based on Qt and FFmpeg |
2 | Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com> | |
2 | Copyright (C) 2012-2017 Wang Bin <wbsecg1@gmail.com> | |
3 | 3 | theoribeiro <theo@fictix.com.br> |
4 | 4 | |
5 | 5 | * This file is part of QtAV (from 2013) |
155 | 155 | Q_EMIT sourceChanged(); |
156 | 156 | if (!source) |
157 | 157 | return; |
158 | ((QmlAVPlayer*)source)->player()->addVideoRenderer(this); | |
158 | AVPlayer* p = qobject_cast<AVPlayer*>(source); | |
159 | if (!p) { | |
160 | QmlAVPlayer* qp = qobject_cast<QmlAVPlayer*>(source); | |
161 | if (!qp) { | |
162 | qWarning("source MUST be of type AVPlayer or QmlAVPlayer"); | |
163 | return; | |
164 | } | |
165 | p = qp->player(); | |
166 | } | |
167 | p->addVideoRenderer(this); | |
159 | 168 | } |
160 | 169 | |
161 | 170 | QQuickItemRenderer::FillMode QQuickItemRenderer::fillMode() const |
193 | 202 | { |
194 | 203 | if (videoFrameSize().isEmpty()) |
195 | 204 | return QPointF(); |
196 | ||
205 | DPTR_D(const QQuickItemRenderer); | |
197 | 206 | // Just normalize and use that function |
198 | 207 | // m_nativeSize is transposed in some orientations |
199 | if (orientation()%180 == 0) | |
208 | if (d.rotation()%180 == 0) | |
200 | 209 | return mapNormalizedPointToItem(QPointF(point.x() / videoFrameSize().width(), point.y() / videoFrameSize().height())); |
201 | 210 | else |
202 | 211 | return mapNormalizedPointToItem(QPointF(point.x() / videoFrameSize().height(), point.y() / videoFrameSize().width())); |
212 | 221 | { |
213 | 222 | qreal dx = point.x(); |
214 | 223 | qreal dy = point.y(); |
215 | if (orientation()%180 == 0) { | |
224 | DPTR_D(const QQuickItemRenderer); | |
225 | if (d.rotation()%180 == 0) { | |
216 | 226 | dx *= contentRect().width(); |
217 | 227 | dy *= contentRect().height(); |
218 | 228 | } else { |
220 | 230 | dy *= contentRect().width(); |
221 | 231 | } |
222 | 232 | |
223 | switch (orientation()) { | |
233 | switch (d.rotation()) { | |
224 | 234 | case 0: |
225 | 235 | default: |
226 | 236 | return contentRect().topLeft() + QPointF(dx, dy); |
242 | 252 | QPointF QQuickItemRenderer::mapPointToSource(const QPointF &point) const |
243 | 253 | { |
244 | 254 | QPointF norm = mapPointToSourceNormalized(point); |
245 | if (orientation()%180 == 0) | |
255 | if (d_func().rotation()%180 == 0) | |
246 | 256 | return QPointF(norm.x() * videoFrameSize().width(), norm.y() * videoFrameSize().height()); |
247 | 257 | else |
248 | 258 | return QPointF(norm.x() * videoFrameSize().height(), norm.y() * videoFrameSize().width()); |
262 | 272 | // Normalize the item source point |
263 | 273 | qreal nx = (point.x() - contentRect().x()) / contentRect().width(); |
264 | 274 | qreal ny = (point.y() - contentRect().y()) / contentRect().height(); |
265 | switch (orientation()) { | |
275 | switch (d_func().rotation()) { | |
266 | 276 | case 0: |
267 | 277 | default: |
268 | 278 | return QPointF(nx, ny); |
339 | 349 | if (d.frame_changed) |
340 | 350 | sgvn->setCurrentFrame(d.video_frame); |
341 | 351 | d.frame_changed = false; |
342 | sgvn->setTexturedRectGeometry(d.out_rect, normalizedROI(), d.orientation); | |
352 | sgvn->setTexturedRectGeometry(d.out_rect, normalizedROI(), d.rotation()); | |
343 | 353 | return; |
344 | 354 | } |
345 | 355 | if (!d.frame_changed) { |
356 | 366 | if (d.texture) |
357 | 367 | delete d.texture; |
358 | 368 | |
359 | if (d.orientation == 0) { | |
369 | if (d.rotation() == 0) { | |
360 | 370 | d.texture = window()->createTextureFromImage(d.image); |
361 | } else if (d.orientation == 180) { | |
371 | } else if (d.rotation() == 180) { | |
362 | 372 | d.texture = window()->createTextureFromImage(d.image.mirrored(true, true)); |
363 | 373 | } |
364 | 374 | static_cast<QSGSimpleTextureNode*>(d.node)->setTexture(d.texture); |
0 | 0 | /****************************************************************************** |
1 | 1 | QtAV: Multimedia framework based on Qt and FFmpeg |
2 | Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com> | |
2 | Copyright (C) 2012-2017 Wang Bin <wbsecg1@gmail.com> | |
3 | 3 | theoribeiro <theo@fictix.com.br> |
4 | 4 | |
5 | 5 | * This file is part of QtAV (from 2013) |
59 | 59 | bool isSupported(VideoFormat::PixelFormat pixfmt) const Q_DECL_OVERRIDE; |
60 | 60 | |
61 | 61 | QObject *source() const; |
62 | /*! | |
63 | * \brief setSource | |
64 | * \param source nullptr, an object of type AVPlayer or QmlAVPlayer | |
65 | */ | |
62 | 66 | void setSource(QObject *source); |
63 | 67 | |
64 | 68 | FillMode fillMode() const; |
62 | 62 | Q_ENUMS(Status) |
63 | 63 | Q_ENUMS(Error) |
64 | 64 | Q_ENUMS(ChannelLayout) |
65 | Q_ENUMS(BufferMode) | |
65 | 66 | // not supported by QtMultimedia |
66 | 67 | Q_ENUMS(PositionValue) |
68 | Q_ENUMS(MediaEndAction) | |
67 | 69 | Q_PROPERTY(int startPosition READ startPosition WRITE setStartPosition NOTIFY startPositionChanged) |
68 | 70 | Q_PROPERTY(int stopPosition READ stopPosition WRITE setStopPosition NOTIFY stopPositionChanged) |
69 | 71 | Q_PROPERTY(bool fastSeek READ isFastSeek WRITE setFastSeek NOTIFY fastSeekChanged) |
79 | 81 | Q_PROPERTY(int audioTrack READ audioTrack WRITE setAudioTrack NOTIFY audioTrackChanged) |
80 | 82 | Q_PROPERTY(int videoTrack READ videoTrack WRITE setVideoTrack NOTIFY videoTrackChanged) |
81 | 83 | Q_PROPERTY(int bufferSize READ bufferSize WRITE setBufferSize NOTIFY bufferSizeChanged) |
84 | Q_PROPERTY(BufferMode bufferMode READ bufferMode WRITE setBufferMode NOTIFY bufferModeChanged) | |
85 | Q_PROPERTY(qreal frameRate READ frameRate WRITE setFrameRate NOTIFY frameRateChanged) | |
82 | 86 | Q_PROPERTY(QUrl externalAudio READ externalAudio WRITE setExternalAudio NOTIFY externalAudioChanged) |
83 | 87 | Q_PROPERTY(QVariantList internalAudioTracks READ internalAudioTracks NOTIFY internalAudioTracksChanged) |
84 | 88 | Q_PROPERTY(QVariantList internalVideoTracks READ internalVideoTracks NOTIFY internalVideoTracksChanged) |
86 | 90 | Q_PROPERTY(QVariantList internalSubtitleTracks READ internalSubtitleTracks NOTIFY internalSubtitleTracksChanged) |
87 | 91 | // internal subtitle, e.g. mkv embedded subtitles |
88 | 92 | Q_PROPERTY(int internalSubtitleTrack READ internalSubtitleTrack WRITE setInternalSubtitleTrack NOTIFY internalSubtitleTrackChanged) |
93 | Q_PROPERTY(MediaEndAction mediaEndAction READ mediaEndAction WRITE setMediaEndAction NOTIFY mediaEndActionChanged) | |
89 | 94 | |
90 | 95 | Q_PROPERTY(QQmlListProperty<QuickAudioFilter> audioFilters READ audioFilters) |
91 | 96 | Q_PROPERTY(QQmlListProperty<QuickVideoFilter> videoFilters READ videoFilters) |
128 | 133 | Mono, |
129 | 134 | Stereo |
130 | 135 | }; |
136 | enum BufferMode | |
137 | { | |
138 | BufferTime = QtAV::BufferTime, | |
139 | BufferBytes = QtAV::BufferBytes, | |
140 | BufferPackets = QtAV::BufferPackets | |
141 | }; | |
142 | enum MediaEndAction { | |
143 | MediaEndAction_Default, /// stop playback (if loop end) and clear video renderer | |
144 | MediaEndAction_KeepDisplay = 1, /// stop playback but video renderer keeps the last frame | |
145 | MediaEndAction_Pause = 1 << 1 /// pause playback. Currently AVPlayer repeat mode will not work if this flag is set | |
146 | }; | |
131 | 147 | |
132 | 148 | explicit QmlAVPlayer(QObject *parent = 0); |
133 | 149 | //derived |
224 | 240 | void setAudioTrack(int value); |
225 | 241 | QVariantList internalAudioTracks() const; |
226 | 242 | /*! |
227 | /*! | |
228 | 243 | * \brief videoTrack |
229 | 244 | * The video stream number in current media. |
230 | 245 | * Value can be: 0, 1, 2.... 0 means the 1st video stream in current media |
235 | 250 | |
236 | 251 | int bufferSize() const; |
237 | 252 | void setBufferSize(int value); |
253 | ||
254 | BufferMode bufferMode() const; | |
255 | void setBufferMode(BufferMode value); | |
256 | ||
257 | qreal frameRate() const; | |
258 | void setFrameRate(qreal value); | |
259 | ||
260 | MediaEndAction mediaEndAction() const; | |
261 | void setMediaEndAction(MediaEndAction value); | |
238 | 262 | /*! |
239 | 263 | * \brief externalAudio |
240 | 264 | * If externalAudio url is valid, player will use audioTrack of external audio as audio source. |
306 | 330 | void internalSubtitleTrackChanged(); |
307 | 331 | void internalSubtitleTracksChanged(); |
308 | 332 | void bufferSizeChanged(); |
333 | void bufferModeChanged(); | |
334 | void frameRateChanged(); | |
335 | void mediaEndActionChanged(); | |
309 | 336 | |
310 | 337 | void errorChanged(); |
311 | 338 | void error(Error error, const QString &errorString); |
0 | 0 | /****************************************************************************** |
1 | 1 | QtAV: Multimedia framework based on Qt and FFmpeg |
2 | Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com> | |
2 | Copyright (C) 2012-2017 Wang Bin <wbsecg1@gmail.com> | |
3 | 3 | |
4 | 4 | * This file is part of QtAV (from 2015) |
5 | 5 | |
65 | 65 | OpenGLVideo* opengl() const Q_DECL_OVERRIDE; |
66 | 66 | |
67 | 67 | QObject *source() const; |
68 | /*! | |
69 | * \brief setSource | |
70 | * \param source nullptr, an object of type AVPlayer or QmlAVPlayer | |
71 | */ | |
68 | 72 | void setSource(QObject *source); |
69 | 73 | |
70 | 74 | FillMode fillMode() const; |
421 | 421 | if (mpPlayer) { |
422 | 422 | mpPlayer->setBufferValue(value); |
423 | 423 | Q_EMIT bufferSizeChanged(); |
424 | } | |
425 | } | |
426 | ||
427 | QmlAVPlayer::BufferMode QmlAVPlayer::bufferMode() const | |
428 | { | |
429 | return static_cast<BufferMode>(mpPlayer->bufferMode()); | |
430 | } | |
431 | ||
432 | void QmlAVPlayer::setBufferMode(QmlAVPlayer::BufferMode value) | |
433 | { | |
434 | QtAV::BufferMode mode = static_cast<QtAV::BufferMode>(value); | |
435 | if(mpPlayer->bufferMode() == mode) | |
436 | return; | |
437 | if(mpPlayer) { | |
438 | mpPlayer->setBufferMode(mode); | |
439 | Q_EMIT bufferModeChanged(); | |
440 | } | |
441 | } | |
442 | ||
443 | qreal QmlAVPlayer::frameRate() const | |
444 | { | |
445 | return mpPlayer->forcedFrameRate(); | |
446 | } | |
447 | ||
448 | void QmlAVPlayer::setFrameRate(qreal value) | |
449 | { | |
450 | if(mpPlayer) { | |
451 | mpPlayer->setFrameRate(value); | |
452 | Q_EMIT frameRateChanged(); | |
453 | } | |
454 | } | |
455 | ||
456 | QmlAVPlayer::MediaEndAction QmlAVPlayer::mediaEndAction() const | |
457 | { | |
458 | return static_cast<MediaEndAction>(int(mpPlayer->mediaEndAction())); | |
459 | } | |
460 | ||
461 | void QmlAVPlayer::setMediaEndAction(QmlAVPlayer::MediaEndAction value) | |
462 | { | |
463 | QtAV::MediaEndAction action = static_cast<QtAV::MediaEndAction>(value); | |
464 | if (mpPlayer->mediaEndAction() == action) | |
465 | return; | |
466 | if (mpPlayer) { | |
467 | mpPlayer->setMediaEndAction(action); | |
468 | Q_EMIT mediaEndActionChanged(); | |
424 | 469 | } |
425 | 470 | } |
426 | 471 |
76 | 76 | void setupAspectRatio() { //TODO: call when out_rect, renderer_size, orientation changed |
77 | 77 | matrix.setToIdentity(); |
78 | 78 | matrix.scale((GLfloat)out_rect.width()/(GLfloat)renderer_width, (GLfloat)out_rect.height()/(GLfloat)renderer_height, 1); |
79 | if (orientation) | |
80 | matrix.rotate(orientation, 0, 0, 1); // Z axis | |
79 | if (rotation()) | |
80 | matrix.rotate(rotation(), 0, 0, 1); // Z axis | |
81 | 81 | // FIXME: why x/y is mirrored? |
82 | if (orientation%180) | |
82 | if (rotation()%180) | |
83 | 83 | matrix.scale(-1, 1); |
84 | 84 | else |
85 | 85 | matrix.scale(1, -1); |
150 | 150 | DPTR_D(QuickFBORenderer); |
151 | 151 | if (d.source == source) |
152 | 152 | return; |
153 | ||
154 | AVPlayer* p0 = 0; | |
155 | p0 = qobject_cast<AVPlayer*>(d.source); | |
156 | if (!p0) { | |
157 | QmlAVPlayer* qp0 = qobject_cast<QmlAVPlayer*>(d.source); | |
158 | if (qp0) | |
159 | p0 = qp0->player(); | |
160 | } | |
161 | if(p0) | |
162 | p0->removeVideoRenderer(this); | |
163 | ||
153 | 164 | d.source = source; |
154 | 165 | Q_EMIT sourceChanged(); |
155 | 166 | if (!source) |
156 | 167 | return; |
157 | ((QmlAVPlayer*)source)->player()->addVideoRenderer(this); | |
168 | AVPlayer* p = qobject_cast<AVPlayer*>(source); | |
169 | if (!p) { | |
170 | QmlAVPlayer* qp = qobject_cast<QmlAVPlayer*>(source); | |
171 | if (!qp) { | |
172 | qWarning("source MUST be of type AVPlayer or QmlAVPlayer"); | |
173 | return; | |
174 | } | |
175 | p = qp->player(); | |
176 | } | |
177 | p->addVideoRenderer(this); | |
158 | 178 | } |
159 | 179 | |
160 | 180 | QuickFBORenderer::FillMode QuickFBORenderer::fillMode() const |
189 | 209 | |
190 | 210 | // Just normalize and use that function |
191 | 211 | // m_nativeSize is transposed in some orientations |
192 | if (orientation()%180 == 0) | |
212 | if (d_func().rotation()%180 == 0) | |
193 | 213 | return mapNormalizedPointToItem(QPointF(point.x() / videoFrameSize().width(), point.y() / videoFrameSize().height())); |
194 | 214 | else |
195 | 215 | return mapNormalizedPointToItem(QPointF(point.x() / videoFrameSize().height(), point.y() / videoFrameSize().width())); |
205 | 225 | { |
206 | 226 | qreal dx = point.x(); |
207 | 227 | qreal dy = point.y(); |
208 | if (orientation()%180 == 0) { | |
228 | if (d_func().rotation()%180 == 0) { | |
209 | 229 | dx *= contentRect().width(); |
210 | 230 | dy *= contentRect().height(); |
211 | 231 | } else { |
213 | 233 | dy *= contentRect().width(); |
214 | 234 | } |
215 | 235 | |
216 | switch (orientation()) { | |
236 | switch (d_func().rotation()) { | |
217 | 237 | case 0: |
218 | 238 | default: |
219 | 239 | return contentRect().topLeft() + QPointF(dx, dy); |
235 | 255 | QPointF QuickFBORenderer::mapPointToSource(const QPointF &point) const |
236 | 256 | { |
237 | 257 | QPointF norm = mapPointToSourceNormalized(point); |
238 | if (orientation()%180 == 0) | |
258 | if (d_func().rotation()%180 == 0) | |
239 | 259 | return QPointF(norm.x() * videoFrameSize().width(), norm.y() * videoFrameSize().height()); |
240 | 260 | else |
241 | 261 | return QPointF(norm.x() * videoFrameSize().height(), norm.y() * videoFrameSize().width()); |
255 | 275 | // Normalize the item source point |
256 | 276 | qreal nx = (point.x() - contentRect().x()) / contentRect().width(); |
257 | 277 | qreal ny = (point.y() - contentRect().y()) / contentRect().height(); |
258 | switch (orientation()) { | |
278 | switch (d_func().rotation()) { | |
259 | 279 | case 0: |
260 | 280 | default: |
261 | 281 | return QPointF(nx, ny); |
0 | /****************************************************************************** | |
1 | QtAV: Multimedia framework based on Qt and FFmpeg | |
2 | Copyright (C) 2012-2017 Wang Bin <wbsecg1@gmail.com> | |
3 | ||
4 | * This file is part of QtAV (from 2016) | |
5 | ||
6 | This library is free software; you can redistribute it and/or | |
7 | modify it under the terms of the GNU Lesser General Public | |
8 | License as published by the Free Software Foundation; either | |
9 | version 2.1 of the License, or (at your option) any later version. | |
10 | ||
11 | This library is distributed in the hope that it will be useful, | |
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
14 | Lesser General Public License for more details. | |
15 | ||
16 | You should have received a copy of the GNU Lesser General Public | |
17 | License along with this library; if not, write to the Free Software | |
18 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
19 | ******************************************************************************/ | |
20 | ||
0 | 21 | #include "QmlAV/QuickFilter.h" |
1 | 22 | #include "QtAV/private/Filter_p.h" |
2 | 23 | #include "QtAV/LibAVFilter.h" |
10 | 31 | { |
11 | 32 | public: |
12 | 33 | QuickVideoFilterPrivate() : type(QuickVideoFilter::AVFilter) |
34 | , user_filter(NULL) | |
13 | 35 | , avfilter(new LibAVFilterVideo()) |
14 | 36 | , glslfilter(new GLSLFilter()) |
15 | 37 | { |
18 | 40 | |
19 | 41 | QuickVideoFilter::FilterType type; |
20 | 42 | VideoFilter *filter; |
21 | QScopedPointer<VideoFilter> user_filter; | |
43 | VideoFilter *user_filter; // life time is managed by qml | |
22 | 44 | QScopedPointer<LibAVFilterVideo> avfilter; |
23 | 45 | QScopedPointer<GLSLFilter> glslfilter; |
24 | 46 | }; |
54 | 76 | else if (value == AVFilter) |
55 | 77 | d.filter = d.avfilter.data(); |
56 | 78 | else |
57 | d.filter = d.user_filter.data(); | |
79 | d.filter = d.user_filter; | |
58 | 80 | Q_EMIT typeChanged(); |
59 | 81 | } |
60 | 82 | |
75 | 97 | |
76 | 98 | VideoFilter* QuickVideoFilter::userFilter() const |
77 | 99 | { |
78 | return d_func().user_filter.data(); | |
100 | return d_func().user_filter; | |
79 | 101 | } |
80 | 102 | |
81 | 103 | void QuickVideoFilter::setUserFilter(VideoFilter *f) |
82 | 104 | { |
83 | 105 | DPTR_D(QuickVideoFilter); |
84 | if (d.user_filter.data() == f) | |
85 | return; | |
86 | d.user_filter.reset(f); | |
106 | if (d.user_filter == f) | |
107 | return; | |
108 | d.user_filter = f; | |
109 | if (d.type == UserFilter) | |
110 | d.filter = d.user_filter; | |
87 | 111 | Q_EMIT userFilterChanged(); |
88 | 112 | } |
89 | 113 | |
114 | 138 | public: |
115 | 139 | QuickAudioFilterPrivate() : AudioFilterPrivate() |
116 | 140 | , type(QuickAudioFilter::AVFilter) |
141 | , user_filter(NULL) | |
117 | 142 | , avfilter(new LibAVFilterAudio()) |
118 | 143 | { |
119 | 144 | filter = avfilter.data(); |
121 | 146 | |
122 | 147 | QuickAudioFilter::FilterType type; |
123 | 148 | AudioFilter *filter; |
124 | QScopedPointer<AudioFilter> user_filter; | |
149 | AudioFilter* user_filter; // life time is managed by qml | |
125 | 150 | QScopedPointer<LibAVFilterAudio> avfilter; |
126 | 151 | }; |
127 | 152 | |
146 | 171 | if (value == AVFilter) |
147 | 172 | d.filter = d.avfilter.data(); |
148 | 173 | else |
149 | d.filter = d.user_filter.data(); | |
174 | d.filter = d.user_filter; | |
150 | 175 | Q_EMIT typeChanged(); |
151 | 176 | } |
152 | 177 | |
167 | 192 | |
168 | 193 | AudioFilter *QuickAudioFilter::userFilter() const |
169 | 194 | { |
170 | return d_func().user_filter.data(); | |
195 | return d_func().user_filter; | |
171 | 196 | } |
172 | 197 | |
173 | 198 | void QuickAudioFilter::setUserFilter(AudioFilter *f) |
174 | 199 | { |
175 | 200 | DPTR_D(QuickAudioFilter); |
176 | if (d.user_filter.data() == f) | |
177 | return; | |
178 | d.user_filter.reset(f); | |
201 | if (d.user_filter == f) | |
202 | return; | |
203 | d.user_filter = f; | |
204 | if (d.type == UserFilter) | |
205 | d.filter = d.user_filter; | |
179 | 206 | Q_EMIT userFilterChanged(); |
180 | 207 | } |
181 | 208 |
27 | 27 | { |
28 | 28 | connect(&m_extractor, SIGNAL(positionChanged()), this, SIGNAL(timestampChanged())); |
29 | 29 | connect(&m_extractor, SIGNAL(frameExtracted(QtAV::VideoFrame)), SLOT(displayFrame(QtAV::VideoFrame))); |
30 | connect(&m_extractor, SIGNAL(error()), SLOT(displayNoFrame())); | |
30 | connect(&m_extractor, SIGNAL(error(const QString &)), SLOT(displayNoFrame())); | |
31 | connect(&m_extractor, SIGNAL(aborted(const QString &)), SLOT(displayNoFrame())); | |
31 | 32 | connect(this, SIGNAL(fileChanged()), SLOT(displayNoFrame())); |
32 | 33 | } |
33 | 34 |
88 | 88 | property alias internalAudioTracks: player.internalAudioTracks |
89 | 89 | property alias externalAudioTracks: player.externalAudioTracks |
90 | 90 | property alias internalVideoTracks: player.internalVideoTracks |
91 | property alias internalSubtitleTracks: player.internalSubtitleTracks | |
92 | property alias internalSubtitleTrack: player.internalSubtitleTrack | |
91 | 93 | /*** Properties of VideoOutput ***/ |
92 | 94 | /*! |
93 | 95 | \qmlproperty enumeration Video::fillMode |
12 | 12 | preparePaths($$OUT_PWD/../out) |
13 | 13 | #https://github.com/wang-bin/QtAV/issues/368#issuecomment-73246253 |
14 | 14 | #http://qt-project.org/forums/viewthread/38438 |
15 | # mkspecs/features/qml_plugin.prf | |
16 | URI = QtAV #uri used in QtAVQmlPlugin::registerTypes(uri) | |
17 | qtAtLeast(5, 3): QMAKE_MOC_OPTIONS += -Muri=$$URI # not sure what moc does | |
18 | #DESTDIR = $$BUILD_DIR/bin/QtAV | |
15 | # mkspecs/features/qml_plugin.prf mkspecs/features/qml_module.prf | |
16 | TARGETPATH = QtAV | |
17 | URI = $$replace(TARGETPATH, "/", ".") | |
18 | qtAtLeast(5, 3): QMAKE_MOC_OPTIONS += -Muri=$$URI | |
19 | ||
20 | static: CONFIG += builtin_resources | |
21 | ||
22 | builtin_resources { | |
23 | RESOURCES += libQmlAV.qrc | |
24 | } | |
25 | ||
19 | 26 | qtav_qml.files = qmldir Video.qml plugins.qmltypes |
20 | 27 | !static { #static lib copy error before ranlib. copy only in sdk_install |
21 | 28 | plugin.files = $$DESTDIR/$$qtSharedLib($$NAME) |
62 | 69 | EXTRA_COPY_FILES = $$qtav_qml.files |
63 | 70 | |
64 | 71 | QMAKE_WRITE_DEFAULT_RC = 1 |
65 | QMAKE_TARGET_COMPANY = "Shanghai University->S3 Graphics->Deepin | wbsecg1@gmail.com" | |
72 | QMAKE_TARGET_COMPANY = "wbsecg1@gmail.com" | |
66 | 73 | QMAKE_TARGET_DESCRIPTION = "QtAV QML module. QtAV Multimedia framework. http://qtav.org" |
67 | QMAKE_TARGET_COPYRIGHT = "Copyright (C) 2012-2017 WangBin, wbsecg1@gmail.com" | |
74 | QMAKE_TARGET_COPYRIGHT = "Copyright (C) 2012-2019 WangBin, wbsecg1@gmail.com" | |
68 | 75 | QMAKE_TARGET_PRODUCT = "QtAV QML" |
69 | 76 | |
70 | 77 | SOURCES += \ |
0 | <RCC> | |
1 | <qresource prefix="/qt-project.org/imports/QtAV"> | |
2 | <file>qmldir</file> | |
3 | <file>Video.qml</file> | |
4 | </qresource> | |
5 | </RCC> |
31 | 31 | #include "QmlAV/QuickFBORenderer.h" |
32 | 32 | #endif |
33 | 33 | |
34 | inline void initResources() { | |
35 | #ifdef BUILD_QMLAV_STATIC | |
36 | Q_INIT_RESOURCE(libQmlAV); | |
37 | #endif | |
38 | } | |
39 | ||
34 | 40 | namespace QtAV { |
35 | 41 | |
36 | 42 | class QtAVQmlPlugin : public QQmlExtensionPlugin |
38 | 44 | Q_OBJECT |
39 | 45 | Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface") |
40 | 46 | public: |
47 | QtAVQmlPlugin() { | |
48 | initResources(); | |
49 | } | |
41 | 50 | void registerTypes(const char *uri) |
42 | 51 | { |
43 | 52 | Q_ASSERT(QLatin1String(uri) == QLatin1String("QtAV")); |
187 | 187 | } |
188 | 188 | } |
189 | 189 | Enum { |
190 | name: "MediaEndAction" | |
191 | values: { | |
192 | "MediaEndAction_Default": 0, | |
193 | "MediaEndAction_KeepDisplay": 1, | |
194 | "MediaEndAction_Pause": 2 | |
195 | } | |
196 | } | |
197 | Enum { | |
190 | 198 | name: "ChannelLayout" |
191 | 199 | values: { |
192 | 200 | "ChannelLayoutAuto": 0, |
207 | 215 | Property { name: "playbackState"; type: "PlaybackState"; isReadonly: true } |
208 | 216 | Property { name: "autoPlay"; type: "bool" } |
209 | 217 | Property { name: "autoLoad"; type: "bool" } |
218 | Property { name: "mediaEndAction"; type: "MediaEndAction" } | |
210 | 219 | Property { name: "playbackRate"; type: "double" } |
211 | 220 | Property { name: "source"; type: "QUrl" } |
212 | 221 | Property { name: "loops"; type: "int" } |
0 | 0 | <?xml version="1.0" encoding="UTF-8"?> |
1 | 1 | <Installer> |
2 | 2 | <Name>QtAV</Name> |
3 | <Version>1.12.0</Version> | |
3 | <Version>1.13.0</Version> | |
4 | 4 | <Title>QtAV Multimedia framework</Title> |
5 | 5 | <Publisher>WangBin wbsecg1@gmail.com</Publisher> |
6 | 6 | <ProductUrl>http://qtav.org</ProductUrl> |
1 | 1 | <Package> |
2 | 2 | <DisplayName>QtAV</DisplayName> |
3 | 3 | <Description>Install QtAV multimedia library</Description> |
4 | <Version>1.12.0-0</Version> | |
5 | <ReleaseDate>2017-06-20</ReleaseDate> | |
4 | <Version>1.13.0-0</Version> | |
5 | <ReleaseDate>2019-07-11</ReleaseDate> | |
6 | 6 | <Name>com.qtav.product</Name> |
7 | 7 | <Licenses> |
8 | 8 | <License name="LGPL v2.1" file="lgpl-2.1.txt" /> |
22 | 22 | copy /y bin\*portaudio*.dll %QTDIR%\bin |
23 | 23 | xcopy /syi include %QTDIR%\include |
24 | 24 | copy /y lib\*Qt*AV*.lib %QTDIR%\lib |
25 | copy /y lib\QtAV1.lib %QTDIR%\lib\Qt5AV.lib | |
26 | copy /y lib\QtAVWidgets1.lib %QTDIR%\lib\Qt5AVWidgets.lib | |
25 | 27 | copy /y lib\*Qt*AV*.a %QTDIR%\lib |
26 | xcopy /syi qml\QtAV %QTDIR%\qml\QtAV | |
28 | copy /y lib\libQtAV1.a %QTDIR%\lib\libQt5AV.a | |
29 | copy /y lib\libQtAVWidgets1.a %QTDIR%\lib\libQt5AVWidgets.a | |
30 | xcopy /syi bin\QtAV %QTDIR%\qml\QtAV | |
27 | 31 | xcopy /syi mkspecs %QTDIR%\mkspecs |
28 | 32 | @echo QtAV SDK is installed |
29 | 33 | goto END |
1 | 1 | <Package> |
2 | 2 | <DisplayName>Development files</DisplayName> |
3 | 3 | <Description>Install QtAV headers and lib.</Description> |
4 | <Version>1.12.0-0</Version> | |
5 | <ReleaseDate>2017-06-20</ReleaseDate> | |
4 | <Version>1.13.0-0</Version> | |
5 | <ReleaseDate>2019-07-11</ReleaseDate> | |
6 | 6 | <Name>com.qtav.product.dev</Name> |
7 | 7 | <Default>script</Default> |
8 | 8 | <Script>installscript.qs</Script> |
1 | 1 | <Package> |
2 | 2 | <DisplayName>Examples</DisplayName> |
3 | 3 | <Description>Install QtAV examples.</Description> |
4 | <Version>1.12.0-0</Version> | |
5 | <ReleaseDate>2017-06-20</ReleaseDate> | |
4 | <Version>1.13.0-0</Version> | |
5 | <ReleaseDate>2019-07-11</ReleaseDate> | |
6 | 6 | <Name>com.qtav.product.examples</Name> |
7 | 7 | <Default>script</Default> |
8 | 8 | <Script>installscript.qs</Script> |
1 | 1 | <Package> |
2 | 2 | <DisplayName>Player</DisplayName> |
3 | 3 | <Description>Default player.</Description> |
4 | <Version>1.12.0-0</Version> | |
5 | <ReleaseDate>2017-06-20</ReleaseDate> | |
4 | <Version>1.13.0-0</Version> | |
5 | <ReleaseDate>2019-07-11</ReleaseDate> | |
6 | 6 | <Name>com.qtav.product.player</Name> |
7 | 7 | <Default>true</Default> |
8 | 8 | <ForcedInstallation>true</ForcedInstallation> |
1 | 1 | <Package> |
2 | 2 | <DisplayName>Runtime library</DisplayName> |
3 | 3 | <Description>Install QtAV runtime library.</Description> |
4 | <Version>1.12.0-0</Version> | |
5 | <ReleaseDate>2017-06-20</ReleaseDate> | |
4 | <Version>1.13.0-0</Version> | |
5 | <ReleaseDate>2019-07-11</ReleaseDate> | |
6 | 6 | <Name>com.qtav.product.runtime</Name> |
7 | 7 | <Translations> |
8 | 8 | <Translation>zh_CN.qm</Translation> |
210 | 210 | newSeekRequest(new stepBackwardTask(this, pre_pts)); |
211 | 211 | } |
212 | 212 | |
213 | void AVDemuxThread::seek(qint64 pos, SeekType type) | |
214 | { | |
215 | end = false; | |
216 | // queue maybe blocked by put() | |
217 | if (audio_thread) { | |
218 | audio_thread->packetQueue()->clear(); | |
219 | } | |
220 | if (video_thread) { | |
221 | video_thread->packetQueue()->clear(); | |
222 | } | |
213 | void AVDemuxThread::seek(qint64 external_pos, qint64 pos, SeekType type) | |
214 | { | |
223 | 215 | class SeekTask : public QRunnable { |
224 | 216 | public: |
225 | SeekTask(AVDemuxThread *dt, qint64 t, SeekType st) | |
217 | SeekTask(AVDemuxThread *dt, qint64 external_pos, qint64 t, SeekType st) | |
226 | 218 | : demux_thread(dt) |
227 | 219 | , type(st) |
228 | 220 | , position(t) |
221 | , external_pos(external_pos) | |
229 | 222 | {} |
230 | 223 | void run() { |
224 | // queue maybe blocked by put() | |
225 | if (demux_thread->audio_thread) { | |
226 | demux_thread->audio_thread->packetQueue()->clear(); | |
227 | } | |
228 | if (demux_thread->video_thread) { | |
229 | demux_thread->video_thread->packetQueue()->clear(); | |
230 | } | |
231 | 231 | if (demux_thread->video_thread) |
232 | 232 | demux_thread->video_thread->setDropFrameOnSeek(true); |
233 | demux_thread->seekInternal(position, type); | |
233 | demux_thread->seekInternal(position, type, external_pos); | |
234 | 234 | } |
235 | 235 | private: |
236 | 236 | AVDemuxThread *demux_thread; |
237 | 237 | SeekType type; |
238 | 238 | qint64 position; |
239 | qint64 external_pos; | |
239 | 240 | }; |
240 | newSeekRequest(new SeekTask(this, pos, type)); | |
241 | } | |
242 | ||
243 | void AVDemuxThread::seekInternal(qint64 pos, SeekType type) | |
241 | ||
242 | end = false; | |
243 | // queue maybe blocked by put() | |
244 | // These must be here or seeking while paused will not update the video frame | |
245 | if (audio_thread) { | |
246 | audio_thread->packetQueue()->clear(); | |
247 | } | |
248 | if (video_thread) { | |
249 | video_thread->packetQueue()->clear(); | |
250 | } | |
251 | newSeekRequest(new SeekTask(this, external_pos, pos, type)); | |
252 | } | |
253 | ||
254 | void AVDemuxThread::seekInternal(qint64 pos, SeekType type, qint64 external_pos) | |
244 | 255 | { |
245 | 256 | AVThread* av[] = { audio_thread, video_thread}; |
246 | 257 | qDebug("seek to %s %lld ms (%f%%)", QTime(0, 0, 0).addMSecs(pos).toString().toUtf8().constData(), pos, double(pos - demuxer->startTime())/double(demuxer->duration())*100.0); |
263 | 274 | Q_ASSERT(sync_id != 0); |
264 | 275 | qDebug("demuxer sync id: %d/%d", sync_id, t->clock()->syncId()); |
265 | 276 | t->packetQueue()->clear(); |
277 | if (external_pos != std::numeric_limits < qint64 >::min() ) | |
278 | t->clock()->updateExternalClock(qMax(qint64(0), external_pos)); | |
279 | t->clock()->updateValue(double(pos)/1000.0); | |
266 | 280 | t->requestSeek(); |
267 | 281 | // TODO: the first frame (key frame) will not be decoded correctly if flush() is called. |
268 | 282 | //PacketBuffer *pb = t->packetQueue(); |
45 | 45 | AVThread* videoThread(); |
46 | 46 | void stepForward(); // show next video frame and pause |
47 | 47 | void stepBackward(); |
48 | void seek(qint64 pos, SeekType type); //ms | |
48 | void seek(qint64 external_pos, qint64 pos, SeekType type); //ms | |
49 | 49 | //AVDemuxer* demuxer |
50 | 50 | bool isPaused() const; |
51 | 51 | bool isEnd() const; |
83 | 83 | void setAVThread(AVThread *&pOld, AVThread* pNew); |
84 | 84 | void newSeekRequest(QRunnable *r); |
85 | 85 | void processNextSeekTask(); |
86 | void seekInternal(qint64 pos, SeekType type); //must call in AVDemuxThread | |
86 | void seekInternal(qint64 pos, SeekType type, qint64 external_pos = std::numeric_limits < qint64 >::min()); //must call in AVDemuxThread | |
87 | 87 | void pauseInternal(bool value); |
88 | 88 | |
89 | 89 | bool paused; |
0 | 0 | /****************************************************************************** |
1 | 1 | QtAV: Multimedia framework based on Qt and FFmpeg |
2 | Copyright (C) 2012-2017 Wang Bin <wbsecg1@gmail.com> | |
2 | Copyright (C) 2012-2018 Wang Bin <wbsecg1@gmail.com> | |
3 | 3 | |
4 | 4 | * This file is part of QtAV |
5 | 5 | |
325 | 325 | class AVInitializer { |
326 | 326 | public: |
327 | 327 | AVInitializer() { |
328 | #if !AVCODEC_STATIC_REGISTER | |
328 | 329 | avcodec_register_all(); |
330 | #endif | |
329 | 331 | #if QTAV_HAVE(AVDEVICE) |
330 | 332 | avdevice_register_all(); |
331 | 333 | #endif |
334 | #if !AVFORMAT_STATIC_REGISTER | |
332 | 335 | av_register_all(); |
336 | #endif | |
333 | 337 | avformat_network_init(); |
334 | 338 | } |
335 | 339 | ~AVInitializer() { |
351 | 355 | static QStringList exts; |
352 | 356 | static QStringList fmts; |
353 | 357 | if (exts.isEmpty() && fmts.isEmpty()) { |
358 | QStringList e, f; | |
359 | #if AVFORMAT_STATIC_REGISTER | |
360 | const AVInputFormat *i = NULL; | |
361 | void* it = NULL; | |
362 | while ((i = av_demuxer_iterate(&it))) { | |
363 | #else | |
364 | AVInputFormat *i = NULL; | |
354 | 365 | av_register_all(); // MUST register all input/output formats |
355 | AVInputFormat *i = NULL; | |
356 | QStringList e, f; | |
357 | 366 | while ((i = av_iformat_next(i))) { |
367 | #endif | |
358 | 368 | if (i->extensions) |
359 | 369 | e << QString::fromLatin1(i->extensions).split(QLatin1Char(','), QString::SkipEmptyParts); |
360 | 370 | if (i->name) |
399 | 409 | #if QTAV_HAVE(AVDEVICE) |
400 | 410 | protocols << QStringLiteral("avdevice"); |
401 | 411 | #endif |
412 | #if !AVFORMAT_STATIC_REGISTER | |
402 | 413 | av_register_all(); // MUST register all input/output formats |
414 | #endif | |
403 | 415 | void* opq = 0; |
404 | 416 | const char* protocol = avio_enum_protocols(&opq, 0); |
405 | 417 | while (protocol) { |
0 | 0 | /****************************************************************************** |
1 | 1 | QtAV: Multimedia framework based on Qt and FFmpeg |
2 | Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com> | |
2 | Copyright (C) 2012-2018 Wang Bin <wbsecg1@gmail.com> | |
3 | 3 | |
4 | 4 | * This file is part of QtAV (from 2015) |
5 | 5 | |
42 | 42 | , started(false) |
43 | 43 | , eof(false) |
44 | 44 | , media_changed(true) |
45 | , open(false) | |
45 | 46 | , format_ctx(0) |
46 | 47 | , format(0) |
47 | 48 | , io(0) |
49 | 50 | , aenc(0) |
50 | 51 | , venc(0) |
51 | 52 | { |
53 | #if !AVFORMAT_STATIC_REGISTER | |
52 | 54 | av_register_all(); |
55 | #endif | |
53 | 56 | } |
54 | 57 | ~Private() { |
55 | 58 | //delete interrupt_hanlder; |
72 | 75 | bool started; |
73 | 76 | bool eof; |
74 | 77 | bool media_changed; |
78 | bool open; | |
75 | 79 | AVFormatContext *format_ctx; |
76 | 80 | //copy the info, not parse the file when constructed, then need member vars |
77 | 81 | QString file; |
121 | 125 | c->time_base = s->time_base; |
122 | 126 | /* Some formats want stream headers to be separate. */ |
123 | 127 | if (ctx->oformat->flags & AVFMT_GLOBALHEADER) |
124 | c->flags |= CODEC_FLAG_GLOBAL_HEADER; | |
128 | c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; | |
125 | 129 | // expose avctx to encoder and set properties in encoder? |
126 | 130 | // list codecs for a given format in ui |
127 | 131 | return s; |
142 | 146 | c->height = venc->height(); |
143 | 147 | /// MUST set after encoder is open to ensure format is valid and the same |
144 | 148 | c->pix_fmt = (AVPixelFormat)VideoFormat::pixelFormatToFFmpeg(venc->pixelFormat()); |
149 | ||
150 | // Set avg_frame_rate based on encoder frame_rate | |
151 | s->avg_frame_rate = av_d2q(venc->frameRate(), venc->frameRate()*1001.0+2); | |
152 | ||
145 | 153 | video_streams.push_back(s->id); |
146 | 154 | } |
147 | 155 | } |
156 | 164 | c->channel_layout = aenc->audioFormat().channelLayoutFFmpeg(); |
157 | 165 | c->channels = aenc->audioFormat().channels(); |
158 | 166 | c->bits_per_raw_sample = aenc->audioFormat().bytesPerSample()*8; // need?? |
167 | ||
168 | AVCodecContext *avctx = (AVCodecContext *) aenc->codecContext(); | |
169 | #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(56,5,100) | |
170 | c->initial_padding = avctx->initial_padding; | |
171 | #else | |
172 | c->delay = avctx->delay; | |
173 | #endif | |
174 | if (avctx->extradata_size) { | |
175 | c->extradata = avctx->extradata; | |
176 | c->extradata_size = avctx->extradata_size; | |
177 | } | |
178 | ||
159 | 179 | audio_streams.push_back(s->id); |
160 | 180 | } |
161 | 181 | } |
167 | 187 | static QStringList exts; |
168 | 188 | static QStringList fmts; |
169 | 189 | if (exts.isEmpty() && fmts.isEmpty()) { |
190 | QStringList e, f; | |
191 | #if AVFORMAT_STATIC_REGISTER | |
192 | const AVOutputFormat *o = NULL; | |
193 | void* it = NULL; | |
194 | while ((o = av_muxer_iterate(&it))) { | |
195 | #else | |
170 | 196 | av_register_all(); // MUST register all input/output formats |
171 | 197 | AVOutputFormat *o = NULL; |
172 | QStringList e, f; | |
173 | 198 | while ((o = av_oformat_next(o))) { |
199 | #endif | |
174 | 200 | if (o->extensions) |
175 | 201 | e << QString::fromLatin1(o->extensions).split(QLatin1Char(','), QString::SkipEmptyParts); |
176 | 202 | if (o->name) |
220 | 246 | #if QTAV_HAVE(AVDEVICE) |
221 | 247 | protocols << QStringLiteral("avdevice"); |
222 | 248 | #endif |
249 | #if !AVFORMAT_STATIC_REGISTER | |
223 | 250 | av_register_all(); // MUST register all input/output formats |
251 | #endif | |
224 | 252 | void* opq = 0; |
225 | 253 | const char* protocol = avio_enum_protocols(&opq, 1); |
226 | 254 | while (protocol) { |
322 | 350 | d->format_forced.clear(); |
323 | 351 | } |
324 | 352 | d->io->setProperty("device", QVariant::fromValue(device)); //open outside? |
353 | ||
354 | if (device->isWritable()) { | |
355 | d->io->setAccessMode(MediaIO::Write); | |
356 | } | |
357 | ||
325 | 358 | return d->media_changed; |
326 | 359 | } |
327 | 360 | |
391 | 424 | // d->format_ctx->start_time_realtime |
392 | 425 | AV_ENSURE_OK(avformat_write_header(d->format_ctx, &d->dict), false); |
393 | 426 | d->started = false; |
427 | d->open = true; | |
394 | 428 | |
395 | 429 | return true; |
396 | 430 | } |
399 | 433 | { |
400 | 434 | if (!isOpen()) |
401 | 435 | return true; |
436 | d->open = false; | |
402 | 437 | av_write_trailer(d->format_ctx); |
403 | 438 | // close AVCodecContext* in encoder |
404 | 439 | // custom io will call avio_close in ~MediaIO() |
420 | 455 | |
421 | 456 | bool AVMuxer::isOpen() const |
422 | 457 | { |
423 | return d->format_ctx; | |
458 | return d->open; | |
424 | 459 | } |
425 | 460 | |
426 | 461 | bool AVMuxer::writeAudio(const QtAV::Packet& packet) |
0 | /****************************************************************************** | |
0 | /****************************************************************************** | |
1 | 1 | QtAV: Multimedia framework based on Qt and FFmpeg |
2 | 2 | Copyright (C) 2012-2017 Wang Bin <wbsecg1@gmail.com> |
3 | 3 | |
44 | 44 | #include "QtAV/private/AVCompat.h" |
45 | 45 | #include "utils/internal.h" |
46 | 46 | #include "utils/Logger.h" |
47 | extern "C" { | |
48 | #include <libavutil/mathematics.h> | |
49 | } | |
47 | 50 | |
48 | 51 | #define EOF_ISSUE_SOLVED 0 |
49 | 52 | namespace QtAV { |
679 | 682 | d->video_tracks = d->getTracksInfo(&d->demuxer, AVDemuxer::VideoStream); |
680 | 683 | Q_EMIT internalVideoTracksChanged(d->video_tracks); |
681 | 684 | Q_EMIT durationChanged(duration()); |
685 | Q_EMIT chaptersChanged(chapters()); | |
682 | 686 | // setup parameters from loaded media |
683 | 687 | d->media_start_pts = d->demuxer.startTime(); |
684 | 688 | // TODO: what about other proctols? some vob duration() == 0 |
714 | 718 | d->vdec = 0; |
715 | 719 | } |
716 | 720 | d->demuxer.unload(); |
721 | Q_EMIT chaptersChanged(0); | |
717 | 722 | Q_EMIT durationChanged(0LL); // for ui, slider is invalid. use stopped instead, and remove this signal here? |
718 | 723 | // ?? |
719 | 724 | d->audio_tracks = d->getTracksInfo(&d->demuxer, AVDemuxer::AudioStream); |
856 | 861 | if (relativeTimeMode()) |
857 | 862 | pos_pts += absoluteMediaStartPosition(); |
858 | 863 | d->seeking = true; |
859 | masterClock()->updateValue(double(pos_pts)/1000.0); //what is duration == 0 | |
860 | masterClock()->updateExternalClock(pos_pts); //in msec. ignore usec part using t/1000 | |
861 | d->read_thread->seek(pos_pts, seekType()); | |
864 | d->read_thread->seek(position,pos_pts, seekType()); | |
862 | 865 | |
863 | 866 | Q_EMIT positionChanged(position); //emit relative position |
864 | 867 | } |
1230 | 1233 | d->vthread->start(); |
1231 | 1234 | } |
1232 | 1235 | |
1233 | d->read_thread->setMediaEndAction(mediaEndAction()); | |
1234 | d->read_thread->start(); | |
1235 | ||
1236 | 1236 | if (d->demuxer.audioCodecContext() && d->athread) |
1237 | 1237 | d->athread->waitForStarted(); |
1238 | 1238 | if (d->demuxer.videoCodecContext() && d->vthread) |
1239 | 1239 | d->vthread->waitForStarted(); |
1240 | ||
1241 | d->read_thread->setMediaEndAction(mediaEndAction()); | |
1242 | d->read_thread->start(); | |
1243 | ||
1240 | 1244 | /// demux thread not started, seek tasks will be cleared |
1241 | 1245 | d->read_thread->waitForStarted(); |
1242 | 1246 | if (d->timer_id < 0) { |
1384 | 1388 | } |
1385 | 1389 | } |
1386 | 1390 | |
1391 | void AVPlayer::seekChapter(int incr) | |
1392 | { | |
1393 | if (!chapters()) | |
1394 | return; | |
1395 | ||
1396 | qint64 pos = masterClock()->value() * AV_TIME_BASE; | |
1397 | int i = 0; | |
1398 | ||
1399 | AVFormatContext *ic = d->demuxer.formatContext(); | |
1400 | ||
1401 | AVRational av_time_base_q; | |
1402 | av_time_base_q.num = 1; | |
1403 | av_time_base_q.den = AV_TIME_BASE; | |
1404 | ||
1405 | /* find the current chapter */ | |
1406 | for (i = 0; i < chapters(); ++i) { | |
1407 | AVChapter *ch = ic->chapters[i]; | |
1408 | if (av_compare_ts(pos, av_time_base_q, ch->start, ch->time_base) < 0) { | |
1409 | --i; | |
1410 | break; | |
1411 | } | |
1412 | } | |
1413 | ||
1414 | i += incr; | |
1415 | //i = FFMAX(i, 0); | |
1416 | if (i <= 0) | |
1417 | i = 0; | |
1418 | if (i >= chapters()) | |
1419 | return; | |
1420 | ||
1421 | //av_log(NULL, AV_LOG_VERBOSE, "Seeking to chapter %d.\n", i); | |
1422 | qDebug() << QString::fromLatin1("Seeking to chapter : ") << QString::number(i); | |
1423 | setPosition(av_rescale_q(ic->chapters[i]->start, ic->chapters[i]->time_base, | |
1424 | av_time_base_q) / 1000); | |
1425 | } | |
1426 | ||
1387 | 1427 | void AVPlayer::stop() |
1388 | 1428 | { |
1389 | 1429 | // check d->timer_id, <0 return? |
1531 | 1571 | seek(position() - kSeekMS); |
1532 | 1572 | } |
1533 | 1573 | |
1574 | void AVPlayer::seekNextChapter() | |
1575 | { | |
1576 | if (chapters() <= 1) | |
1577 | return; | |
1578 | seekChapter(1); | |
1579 | } | |
1580 | ||
1581 | void AVPlayer::seekPreviousChapter() | |
1582 | { | |
1583 | if (chapters() <= 1) | |
1584 | return; | |
1585 | seekChapter(-1); | |
1586 | } | |
1587 | ||
1534 | 1588 | void AVPlayer::setSeekType(SeekType type) |
1535 | 1589 | { |
1536 | 1590 | d->seek_type = type; |
1634 | 1688 | return d->saturation; |
1635 | 1689 | } |
1636 | 1690 | |
1691 | unsigned int AVPlayer::chapters() const | |
1692 | { | |
1693 | return d->demuxer.formatContext()->nb_chapters; | |
1694 | } | |
1695 | ||
1637 | 1696 | void AVPlayer::setSaturation(int val) |
1638 | 1697 | { |
1639 | 1698 | if (d->saturation == val) |
0 | 0 | /****************************************************************************** |
1 | 1 | QtAV: Multimedia framework based on Qt and FFmpeg |
2 | Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com> | |
2 | Copyright (C) 2012-2017 Wang Bin <wbsecg1@gmail.com> | |
3 | 3 | |
4 | 4 | * This file is part of QtAV (from 2014) |
5 | 5 | |
27 | 27 | #include "QtAV/MediaIO.h" |
28 | 28 | #include "QtAV/VideoCapture.h" |
29 | 29 | #include "QtAV/private/AVCompat.h" |
30 | #if AV_MODULE_CHECK(LIBAVFORMAT, 55, 18, 0, 39, 100) | |
31 | extern "C" { | |
32 | #include <libavutil/display.h> | |
33 | } | |
34 | #endif | |
30 | 35 | #include "utils/Logger.h" |
31 | 36 | |
32 | 37 | namespace QtAV { |
324 | 329 | statistics.video_only.pix_fmt = QLatin1String(av_get_pix_fmt_name(avctx->pix_fmt)); |
325 | 330 | statistics.video_only.height = avctx->height; |
326 | 331 | statistics.video_only.width = avctx->width; |
332 | statistics.video_only.rotate = 0; | |
333 | #if AV_MODULE_CHECK(LIBAVFORMAT, 55, 18, 0, 39, 100) | |
334 | quint8 *sd = av_stream_get_side_data(demuxer.formatContext()->streams[s], AV_PKT_DATA_DISPLAYMATRIX, NULL); | |
335 | if (sd) { | |
336 | double r = av_display_rotation_get((qint32*)sd); | |
337 | if (!qIsNaN(r)) | |
338 | statistics.video_only.rotate = ((int)r + 360) % 360; | |
339 | } | |
340 | #endif | |
327 | 341 | } |
328 | 342 | // notify statistics change after audio/video thread is set |
329 | 343 | bool AVPlayer::Private::setupAudioThread(AVPlayer *player) |
413 | 427 | } |
414 | 428 | } |
415 | 429 | } |
430 | ||
431 | // we set the thre state before the thread start, | |
432 | // as it maybe clear after by AVDemuxThread starting | |
433 | athread->resetState(); | |
416 | 434 | athread->setDecoder(adec); |
417 | 435 | setAVOutput(ao, ao, athread); |
418 | 436 | updateBufferValue(athread->packetQueue()); |
444 | 462 | QVariantMap t; |
445 | 463 | t[QStringLiteral("id")] = info.size(); |
446 | 464 | t[QStringLiteral("file")] = demuxer->fileName(); |
465 | t[QStringLiteral("stream_index")] = QVariant(s); | |
466 | ||
447 | 467 | AVStream *stream = demuxer->formatContext()->streams[s]; |
448 | 468 | AVCodecContext *ctx = stream->codec; |
449 | 469 | if (ctx) { |
588 | 608 | } |
589 | 609 | QObject::connect(vthread, SIGNAL(finished()), player, SLOT(tryClearVideoRenderers()), Qt::DirectConnection); |
590 | 610 | } |
611 | ||
612 | // we set the thre state before the thread start | |
613 | // as it maybe clear after by AVDemuxThread starting | |
614 | vthread->resetState(); | |
591 | 615 | vthread->setDecoder(vdec); |
592 | 616 | |
593 | 617 | vthread->setBrightness(brightness); |
0 | 0 | /****************************************************************************** |
1 | 1 | QtAV: Multimedia framework based on Qt and FFmpeg |
2 | Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com> | |
2 | Copyright (C) 2012-2018 Wang Bin <wbsecg1@gmail.com> | |
3 | 3 | |
4 | 4 | * This file is part of QtAV |
5 | 5 | |
363 | 363 | void AVThread::waitAndCheck(ulong value, qreal pts) |
364 | 364 | { |
365 | 365 | DPTR_D(AVThread); |
366 | if (value <= 0) | |
366 | if (value <= 0 || pts < 0) | |
367 | 367 | return; |
368 | 368 | value += d.wait_err; |
369 | 369 | d.wait_timer.restart(); |
381 | 381 | us = qMin(us, ulong((double)(qMax<qreal>(0, pts - d.clock->value()))*1000000.0)); |
382 | 382 | //qDebug("us: %lu/%lu, pts: %f, clock: %f", us, ms-et.elapsed(), pts, d.clock->value()); |
383 | 383 | processNextTask(); |
384 | us = qMin<ulong>(us, (ms-d.wait_timer.elapsed())*1000); | |
384 | const qint64 left = qint64(ms) - d.wait_timer.elapsed(); | |
385 | if (left <= 0) { | |
386 | us = 0; | |
387 | break; | |
388 | } | |
389 | us = qMin<ulong>(us, left*1000); | |
385 | 390 | } |
386 | 391 | if (us > 0) |
387 | 392 | usleep(us); |
80 | 80 | qreal decodeFrameRate() const; //move to statistics? |
81 | 81 | void setDropFrameOnSeek(bool value); |
82 | 82 | |
83 | void resetState(); | |
83 | 84 | public slots: |
84 | 85 | virtual void stop(); |
85 | 86 | /*change pause state. the pause/continue action will do in the next loop*/ |
99 | 100 | void onFinished(); |
100 | 101 | protected: |
101 | 102 | AVThread(AVThreadPrivate& d, QObject *parent = 0); |
102 | void resetState(); | |
103 | 103 | /* |
104 | 104 | * If the pause state is true setted by pause(true), then block the thread and wait for pause state changed, i.e. pause(false) |
105 | 105 | * and return true. Otherwise, return false immediatly. |
285 | 285 | // uninstall encoder filters first then encoders can be closed safely |
286 | 286 | if (sourcePlayer()) { |
287 | 287 | sourcePlayer()->uninstallFilter(d->afilter); |
288 | disconnect(sourcePlayer(), SIGNAL(stopped()), d->afilter, SLOT(finish())); | |
288 | 289 | sourcePlayer()->uninstallFilter(d->vfilter); |
290 | disconnect(sourcePlayer(), SIGNAL(stopped()), d->vfilter, SLOT(finish())); | |
289 | 291 | } |
290 | 292 | if (d->afilter) |
291 | 293 | d->afilter->finish(); //FIXME: thread of sync mode |
29 | 29 | #endif |
30 | 30 | |
31 | 31 | namespace QtAV { |
32 | ||
33 | const qint64 kHz = 1000000LL; | |
34 | 32 | |
35 | 33 | typedef struct { |
36 | 34 | AVSampleFormat avfmt; |
132 | 132 | |
133 | 133 | AudioFrame AudioFrame::clone() const |
134 | 134 | { |
135 | Q_D(const AudioFrame); | |
135 | return mid(0, -1); | |
136 | } | |
137 | ||
138 | AudioFrame AudioFrame::mid(int pos, int len) const | |
139 | { | |
140 | Q_D(const AudioFrame); | |
141 | ||
136 | 142 | if (d->format.sampleFormatFFmpeg() == AV_SAMPLE_FMT_NONE |
137 | || d->format.channels() <= 0) | |
143 | || d->format.channels() <= 0) { | |
138 | 144 | return AudioFrame(); |
139 | if (d->samples_per_ch <= 0 || bytesPerLine(0) <= 0) | |
145 | } | |
146 | ||
147 | if (d->samples_per_ch <= 0 || bytesPerLine(0) <= 0 || len == 0) { | |
140 | 148 | return AudioFrame(format()); |
141 | QByteArray buf(bytesPerLine()*planeCount(), 0); | |
149 | } | |
150 | ||
151 | int bufSize = bytesPerLine(); | |
152 | int posBytes = 0; | |
153 | if (pos > 0) { | |
154 | posBytes = pos * d->format.bytesPerSample(); | |
155 | bufSize -= posBytes; | |
156 | } else { | |
157 | pos = 0; | |
158 | } | |
159 | ||
160 | int lenBytes = len * d->format.bytesPerSample(); | |
161 | if (len > 0 && lenBytes < bufSize) { | |
162 | bufSize = lenBytes; | |
163 | } else { | |
164 | lenBytes = bufSize; | |
165 | } | |
166 | ||
167 | QByteArray buf(bufSize * planeCount(), 0); | |
142 | 168 | char *dst = buf.data(); //must before buf is shared, otherwise data will be detached. |
169 | ||
143 | 170 | for (int i = 0; i < planeCount(); ++i) { |
144 | const int plane_size = bytesPerLine(i); | |
145 | memcpy(dst, constBits(i), plane_size); | |
146 | dst += plane_size; | |
147 | } | |
171 | memcpy(dst, constBits(i) + posBytes, lenBytes); | |
172 | dst += lenBytes; | |
173 | } | |
174 | ||
148 | 175 | AudioFrame f(d->format, buf); |
149 | f.setSamplesPerChannel(samplesPerChannel()); | |
150 | f.setTimestamp(timestamp()); | |
176 | f.setSamplesPerChannel(bufSize / d->format.bytesPerSample()); | |
177 | f.setTimestamp(d->timestamp + (qreal) d->format.durationForBytes(posBytes) / AudioFormat::kHz); | |
151 | 178 | // meta data? |
152 | 179 | return f; |
180 | } | |
181 | ||
182 | void AudioFrame::prepend(AudioFrame &other) | |
183 | { | |
184 | Q_D(AudioFrame); | |
185 | ||
186 | if (d->format != other.format()) { | |
187 | qWarning() << "To prepend a frame it must have the same audio format"; | |
188 | return; | |
189 | } | |
190 | ||
191 | d->data.prepend(other.data()); | |
192 | d->samples_per_ch += other.samplesPerChannel(); | |
193 | d->timestamp = other.timestamp(); | |
194 | ||
195 | for (int i = 0; i < planeCount(); i++) { | |
196 | d->line_sizes[i] += other.bytesPerLine(i); | |
197 | } | |
153 | 198 | } |
154 | 199 | |
155 | 200 | AudioFormat AudioFrame::format() const |
77 | 77 | //No decoder or output. No audio output is ok, just display picture |
78 | 78 | if (!d.dec || !d.dec->isAvailable() || !d.outputSet) |
79 | 79 | return; |
80 | resetState(); | |
80 | // resetState(); // we can't reset the thread state from here | |
81 | 81 | Q_ASSERT(d.clock != 0); |
82 | 82 | d.init(); |
83 | 83 | Packet pkt; |
11 | 11 | include_directories(${QTDIR}/include) #TODO: remove. use external/include |
12 | 12 | get_filename_component(QTDIR "${QTDIR}" ABSOLUTE) |
13 | 13 | |
14 | list(APPEND EXTRA_INCLUDE ${CMAKE_SOURCE_DIR}/external/include) | |
15 | list(APPEND EXTRA_LIBS ${CMAKE_LIBRARY_PATH_FLAG}${CMAKE_SOURCE_DIR}/external/lib) | |
14 | list(APPEND EXTRA_INCLUDE ${QTAV_SOURCE_DIR}/external/include) | |
15 | list(APPEND EXTRA_LIBS ${CMAKE_LIBRARY_PATH_FLAG}${QTAV_SOURCE_DIR}/external/lib) | |
16 | 16 | if(APPLE) |
17 | 17 | if(IOS) |
18 | 18 | #set_xcode_property(myioslib IPHONEOS_DEPLOYMENT_TARGET "8.0") |
21 | 21 | list(APPEND EXTRA_LIBS -L/usr/local/lib) |
22 | 22 | endif() |
23 | 23 | endif() |
24 | if(EXISTS ${CMAKE_SOURCE_DIR}/contrib/capi/capi.h) | |
24 | if(EXISTS ${QTAV_SOURCE_DIR}/contrib/capi/capi.h) | |
25 | 25 | set(HAVE_CAPI 1) |
26 | list(APPEND EXTRA_INCLUDE ${CMAKE_SOURCE_DIR}/contrib/capi) # TODO: only files use capi.h | |
26 | list(APPEND EXTRA_INCLUDE ${QTAV_SOURCE_DIR}/contrib/capi) # TODO: only files use capi.h | |
27 | 27 | list(APPEND EXTRA_DEFS -DQTAV_HAVE_CAPI=1) |
28 | 28 | endif() |
29 | 29 | |
323 | 323 | if(WIN32 OR WindowsStore OR WindowsPhone) |
324 | 324 | check_include_files(XAudio2.h HAVE_XAUDIO2_H) |
325 | 325 | if(NOT HAVE_XAUDIO2_H) |
326 | list(APPEND EXTRA_INCLUDE ${CMAKE_SOURCE_DIR}/contrib/dxsdk) | |
326 | list(APPEND EXTRA_INCLUDE ${QTAV_SOURCE_DIR}/contrib/dxsdk) | |
327 | 327 | endif() |
328 | 328 | message("Qt5Gui_EGL_INCLUDE_DIRS: ${Qt5Gui_EGL_INCLUDE_DIRS}") |
329 | 329 | list(APPEND HEADERS |
472 | 472 | set_source_files_properties(${RESOURCES_SOURCES} PROPERTIES GENERATED ON) |
473 | 473 | if(WIN32) |
474 | 474 | set(RC_FILE ${CMAKE_CURRENT_BINARY_DIR}/${MODULE}.rc) |
475 | configure_file(${CMAKE_SOURCE_DIR}/cmake/QtAV.rc.in ${RC_FILE}) | |
475 | configure_file(${QTAV_SOURCE_DIR}/cmake/QtAV.rc.in ${RC_FILE}) | |
476 | 476 | endif() |
477 | 477 | # add HEADERS for moc |
478 | 478 | add_library(${MODULE} SHARED ${SOURCES} ${RESOURCES_SOURCES} ${HEADERS} ${RC_FILE}) |
0 | 0 | /****************************************************************************** |
1 | 1 | QtAV: Multimedia framework based on Qt and FFmpeg |
2 | Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com> | |
2 | Copyright (C) 2012-2018 Wang Bin <wbsecg1@gmail.com> | |
3 | 3 | |
4 | 4 | * This file is part of QtAV |
5 | 5 | |
56 | 56 | QByteArray Frame::frameData() const |
57 | 57 | { |
58 | 58 | return d_func()->data; |
59 | } | |
60 | ||
61 | int Frame::dataAlignment() const | |
62 | { | |
63 | return d_func()->data_align; | |
59 | 64 | } |
60 | 65 | |
61 | 66 | QByteArray Frame::data(int plane) const |
352 | 352 | d->vframes.put(frame); |
353 | 353 | Q_EMIT frameRead(frame); |
354 | 354 | //qDebug("frame got @%.3f, queue enough: %d", frame.timestamp(), vframes.isEnough()); |
355 | if (d->vframes.isEnough()) | |
355 | if (d->vframes.isFull()) | |
356 | 356 | break; |
357 | 357 | } else { |
358 | 358 | qDebug("dec error, continue to decoder"); |
0 | 0 | /****************************************************************************** |
1 | 1 | ImageConverter: Base class for image resizing & color model convertion |
2 | Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com> | |
2 | Copyright (C) 2012-2018 Wang Bin <wbsecg1@gmail.com> | |
3 | 3 | |
4 | 4 | * This file is part of QtAV |
5 | 5 | |
44 | 44 | |
45 | 45 | QByteArray ImageConverter::outData() const |
46 | 46 | { |
47 | return d_func().data_out; | |
47 | DPTR_D(const ImageConverter); | |
48 | return d.data_out; | |
48 | 49 | } |
49 | 50 | |
50 | 51 | bool ImageConverter::check() const |
214 | 215 | const int nb_planes = qMax(av_pix_fmt_count_planes(d.fmt_out), 0); |
215 | 216 | d.bits.resize(nb_planes); |
216 | 217 | d.pitchs.resize(nb_planes); |
217 | // alignment is 16. sws in ffmpeg is 16, libav10 is 8 | |
218 | const int kAlign = 16; | |
218 | // alignment is 16. sws in ffmpeg is 16, libav10 is 8. if not aligned sws will print warnings and go slow code paths | |
219 | const int kAlign = DataAlignment; | |
219 | 220 | AV_ENSURE(av_image_fill_linesizes((int*)d.pitchs.constData(), d.fmt_out, kAlign > 7 ? FFALIGN(d.w_out, 8) : d.w_out), false); |
220 | 221 | for (int i = 0; i < d.pitchs.size(); ++i) |
221 | 222 | d.pitchs[i] = FFALIGN(d.pitchs[i], kAlign); |
223 | 224 | if (s < 0) |
224 | 225 | return false; |
225 | 226 | d.data_out.resize(s + kAlign-1); |
226 | const int offset = (kAlign - ((uintptr_t)d.data_out.constData() & (kAlign-1))) & (kAlign-1); | |
227 | AV_ENSURE(av_image_fill_pointers((uint8_t**)d.bits.constData(), d.fmt_out, d.h_out, (uint8_t*)d.data_out.constData()+offset, d.pitchs.constData()), false); | |
227 | d.out_offset = (kAlign - ((uintptr_t)d.data_out.constData() & (kAlign-1))) & (kAlign-1); | |
228 | AV_ENSURE(av_image_fill_pointers((uint8_t**)d.bits.constData(), d.fmt_out, d.h_out, (uint8_t*)d.data_out.constData()+d.out_offset, d.pitchs.constData()), false); | |
228 | 229 | // TODO: special formats |
229 | 230 | //if (desc->flags & AV_PIX_FMT_FLAG_PAL || desc->flags & AV_PIX_FMT_FLAG_PSEUDOPAL) |
230 | 231 | // avpriv_set_systematic_pal2((uint32_t*)pointers[1], pix_fmt); |
0 | 0 | /****************************************************************************** |
1 | 1 | ImageConverter: Base class for image resizing & color model convertion |
2 | Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com> | |
2 | Copyright (C) 2012-2018 Wang Bin <wbsecg1@gmail.com> | |
3 | 3 | |
4 | 4 | * This file is part of QtAV |
5 | 5 | |
34 | 34 | { |
35 | 35 | DPTR_DECLARE_PRIVATE(ImageConverter) |
36 | 36 | public: |
37 | enum { DataAlignment = 16 }; | |
38 | ||
37 | 39 | ImageConverter(); |
38 | 40 | virtual ~ImageConverter(); |
39 | 41 | |
42 | // the real data starts with DataAlignment (16bit) aligned address | |
40 | 43 | QByteArray outData() const; |
41 | 44 | // return false if i/o format not supported, or size is not valid. |
42 | 45 | // TODO: use isSupported(i/o format); |
0 | 0 | /****************************************************************************** |
1 | 1 | QtAV: Multimedia framework based on Qt and FFmpeg |
2 | Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com> | |
2 | Copyright (C) 2012-2018 Wang Bin <wbsecg1@gmail.com> | |
3 | 3 | |
4 | 4 | * This file is part of QtAV |
5 | 5 | |
42 | 42 | , contrast(0) |
43 | 43 | , saturation(0) |
44 | 44 | , update_data(true) |
45 | , out_offset(0) | |
45 | 46 | { |
46 | 47 | bits.reserve(8); |
47 | 48 | pitchs.reserve(8); |
56 | 57 | ColorRange range_in, range_out; |
57 | 58 | int brightness, contrast, saturation; |
58 | 59 | bool update_data; |
60 | int out_offset; | |
59 | 61 | QByteArray data_out; |
60 | 62 | QVector<quint8*> bits; |
61 | 63 | QVector<int> pitchs; |
0 | /****************************************************************************** | |
0 | /****************************************************************************** | |
1 | 1 | QtAV: Multimedia framework based on Qt and FFmpeg |
2 | 2 | Copyright (C) 2012-2017 Wang Bin <wbsecg1@gmail.com> |
3 | 3 | |
81 | 81 | Q_PROPERTY(State state READ state WRITE setState NOTIFY stateChanged) |
82 | 82 | Q_PROPERTY(QtAV::MediaStatus mediaStatus READ mediaStatus NOTIFY mediaStatusChanged) |
83 | 83 | Q_PROPERTY(QtAV::MediaEndAction mediaEndAction READ mediaEndAction WRITE setMediaEndAction NOTIFY mediaEndActionChanged) |
84 | Q_PROPERTY(unsigned int chapters READ chapters NOTIFY chaptersChanged) | |
84 | 85 | Q_ENUMS(State) |
85 | 86 | public: |
86 | 87 | /*! |
198 | 199 | /*! |
199 | 200 | * \brief externalAudioTracks |
200 | 201 | * A list of QVariantMap. Using QVariantMap and QVariantList is mainly for QML support. |
201 | * [ {id: 0, file: abc.dts, language: eng, title: xyz}, ...] | |
202 | * [ {id: 0, stream_index: 2, file: abc.dts, language: eng, title: xyz}, ...] | |
202 | 203 | * id: used for setAudioStream(id) |
204 | * stream_index: the actual muxer stream index -- not used by API to this class but possibly | |
205 | * useful for interop with external libs that require absolute stream_index | |
203 | 206 | * \sa externalAudioTracksChanged |
204 | 207 | */ |
205 | 208 | const QVariantList& externalAudioTracks() const; |
209 | /*! | |
210 | * \brief internalAudioTracks | |
211 | * A list of QVariantMap. Using QVariantMap and QVariantList is mainly for QML support. | |
212 | * [ {id: 0, stream_index: 2, file: abc.dts, language: eng, title: xyz}, ...] | |
213 | * id: used for setAudioStream(id) | |
214 | * stream_index: the actual muxer stream index -- not used by API to this class but possibly | |
215 | * useful for interop with external libs that require absolute stream_index | |
216 | */ | |
206 | 217 | const QVariantList& internalAudioTracks() const; |
218 | /*! | |
219 | * \brief internalVideoTracks | |
220 | * A list of QVariantMap. Using QVariantMap and QVariantList is mainly for QML support. | |
221 | * [ {id: 0, stream_index: 2, file: abc.dts, language: eng, title: xyz}, ...] | |
222 | * id: used for setAudioStream(id) | |
223 | * stream_index: the actual muxer stream index -- not used by API to this class but possibly | |
224 | * useful for interop with external libs that require absolute stream_index | |
225 | */ | |
207 | 226 | const QVariantList& internalVideoTracks() const; |
208 | 227 | /*! |
209 | 228 | * \brief setAudioStream |
355 | 374 | int contrast() const; |
356 | 375 | int hue() const; //not implemented |
357 | 376 | int saturation() const; |
377 | unsigned int chapters() const; | |
358 | 378 | /*! |
359 | 379 | * \sa AVDemuxer::setOptions() |
360 | 380 | * example: |
470 | 490 | void seek(qint64 pos); //ms. same as setPosition(pos) |
471 | 491 | void seekForward(); |
472 | 492 | void seekBackward(); |
493 | void seekNextChapter(); | |
494 | void seekPreviousChapter(); | |
473 | 495 | void setSeekType(SeekType type); |
474 | 496 | SeekType seekType() const; |
475 | 497 | |
560 | 582 | void contrastChanged(int val); |
561 | 583 | void hueChanged(int val); |
562 | 584 | void saturationChanged(int val); |
585 | void chaptersChanged(unsigned int val); | |
563 | 586 | void subtitleStreamChanged(int value); |
564 | 587 | /*! |
565 | 588 | * \brief internalAudioTracksChanged |
592 | 615 | void updateMediaStatus(QtAV::MediaStatus status); |
593 | 616 | void onSeekFinished(qint64 value); |
594 | 617 | void tryClearVideoRenderers(); |
618 | void seekChapter(int incr); | |
595 | 619 | protected: |
596 | 620 | // TODO: set position check timer interval |
597 | 621 | virtual void timerEvent(QTimerEvent *); |
63 | 63 | */ |
64 | 64 | const AudioFormat& audioFormat() const; |
65 | 65 | void setAudioFormat(const AudioFormat& format); |
66 | ||
67 | int frameSize() const; | |
66 | 68 | Q_SIGNALS: |
67 | 69 | void audioFormatChanged(); |
68 | 70 | public: |
67 | 67 | ChannelLayout_Stereo, |
68 | 68 | ChannelLayout_Unsupported //ok. now it's not complete |
69 | 69 | }; |
70 | ||
71 | static const qint64 kHz = 1000000LL; | |
72 | ||
70 | 73 | //typedef qint64 ChannelLayout; //currently use latest FFmpeg's |
71 | 74 | // TODO: constexpr |
72 | 75 | friend int RawSampleSize(SampleFormat fmt) { return fmt & ((1<<(kSize+1)) - 1); } |
56 | 56 | * then you can use clone(). |
57 | 57 | */ |
58 | 58 | AudioFrame clone() const; |
59 | AudioFrame mid(int pos, int len = -1) const; | |
60 | void prepend(AudioFrame &other); | |
59 | 61 | AudioFormat format() const; |
60 | 62 | void setSamplesPerChannel(int samples); |
61 | 63 | // may change after resampling |
81 | 81 | void frameEncoded(const QtAV::Packet& packet); |
82 | 82 | void startTimeChanged(qint64 value); |
83 | 83 | // internal use only |
84 | void requestToEncode(const AudioFrame& frame); | |
84 | void requestToEncode(const QtAV::AudioFrame& frame); | |
85 | 85 | protected Q_SLOTS: |
86 | 86 | void encode(const QtAV::AudioFrame& frame = AudioFrame()); |
87 | 87 | protected: |
0 | 0 | /****************************************************************************** |
1 | 1 | QtAV: Multimedia framework based on Qt and FFmpeg |
2 | Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com> | |
2 | Copyright (C) 2012-2018 Wang Bin <wbsecg1@gmail.com> | |
3 | 3 | |
4 | 4 | * This file is part of QtAV |
5 | 5 | |
60 | 60 | */ |
61 | 61 | int bytesPerLine(int plane = 0) const; |
62 | 62 | // the whole frame data. may be empty unless clone() or allocate is called |
63 | // real data starts with dataAlignment() aligned address | |
63 | 64 | QByteArray frameData() const; |
65 | int dataAlignment() const; | |
66 | uchar* frameDataPtr(int* size = NULL) const { | |
67 | const int a = dataAlignment(); | |
68 | uchar* p = (uchar*)frameData().constData(); | |
69 | const int offset = (a - ((quintptr)p & (a-1))) & (a-1); | |
70 | if (size) | |
71 | *size = frameData().size() - offset; | |
72 | return p+offset; | |
73 | } | |
64 | 74 | // deep copy 1 plane data |
65 | 75 | QByteArray data(int plane = 0) const; |
66 | 76 | uchar* bits(int plane = 0); |
0 | 0 | /****************************************************************************** |
1 | 1 | QtAV: Multimedia framework based on Qt and FFmpeg |
2 | Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com> | |
2 | Copyright (C) 2012-2018 Wang Bin <wbsecg1@gmail.com> | |
3 | 3 | |
4 | 4 | * This file is part of QtAV (from 2014) |
5 | 5 | |
114 | 114 | * shader manager and material will be reset |
115 | 115 | */ |
116 | 116 | void resetGL(); |
117 | void updateViewport(); | |
117 | 118 | }; |
118 | 119 | |
119 | 120 |
0 | 0 | /****************************************************************************** |
1 | 1 | QtAV: Multimedia framework based on Qt and FFmpeg |
2 | Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com> | |
2 | Copyright (C) 2012-2018 Wang Bin <wbsecg1@gmail.com> | |
3 | 3 | |
4 | 4 | * This file is part of QtAV |
5 | 5 | |
167 | 167 | #ifndef QByteArrayLiteral |
168 | 168 | #define QByteArrayLiteral(str) QByteArray(str, sizeof(str) - 1) |
169 | 169 | #endif |
170 | /* | |
171 | * msvc sucks! can not deal with (defined(QTAV_HAVE_##FEATURE) && QTAV_HAVE_##FEATURE) | |
172 | */ | |
170 | ||
173 | 171 | // TODO: internal use. move to a private header |
174 | #define QTAV_HAVE(FEATURE) (defined QTAV_HAVE_##FEATURE && QTAV_HAVE_##FEATURE) | |
172 | #define QTAV_HAVE(FEATURE) (QTAV_HAVE_##FEATURE+0) | |
175 | 173 | |
176 | 174 | #ifndef Q_DECL_OVERRIDE |
177 | 175 | #define Q_DECL_OVERRIDE |
0 | 0 | /****************************************************************************** |
1 | 1 | QtAV: Multimedia framework based on Qt and FFmpeg |
2 | Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com> | |
2 | Copyright (C) 2012-2017 Wang Bin <wbsecg1@gmail.com> | |
3 | 3 | |
4 | 4 | * This file is part of QtAV (from 2013) |
5 | 5 | |
105 | 105 | */ |
106 | 106 | int gop_size; |
107 | 107 | QString pix_fmt; |
108 | int rotate; | |
108 | 109 | /// return current absolute time (seconds since epcho |
109 | 110 | qint64 frameDisplayed(qreal pts); // used to compute currentDisplayFPS() |
110 | 111 | private: |
0 | 0 | /****************************************************************************** |
1 | 1 | QtAV: Multimedia framework based on Qt and FFmpeg |
2 | Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com> | |
2 | Copyright (C) 2012-2018 Wang Bin <wbsecg1@gmail.com> | |
3 | 3 | |
4 | 4 | * This file is part of QtAV |
5 | 5 | |
51 | 51 | VideoFrame(); |
52 | 52 | //must set planes and linesize manually if data is empty |
53 | 53 | // must set planes and linesize manually |
54 | VideoFrame(int width, int height, const VideoFormat& format, const QByteArray& data = QByteArray()); | |
54 | // alignment: data ptr alignment | |
55 | VideoFrame(int width, int height, const VideoFormat& format, const QByteArray& data = QByteArray(), int alignment = 1); | |
55 | 56 | VideoFrame(const QImage& image); |
56 | 57 | VideoFrame(const VideoFrame &other); |
57 | 58 | ~VideoFrame(); |
66 | 66 | void setPosition(qint64 value); |
67 | 67 | qint64 position() const; |
68 | 68 | |
69 | virtual bool event(QEvent *e); | |
70 | 69 | Q_SIGNALS: |
71 | 70 | void frameExtracted(const QtAV::VideoFrame& frame); // parameter: VideoFrame, bool changed? |
72 | 71 | void sourceChanged(); |
73 | 72 | void asyncChanged(); |
74 | void error(); // clear preview image in a slot | |
73 | void error(const QString &errorMessage); ///< emitted with a helpful error message -- connect to this to show empty image in preview widget | |
74 | void aborted(const QString &abortMessage); ///< emitted when aborting the current preview -- if user requested a new preview this usually gets emitted. connect to this to show empty preview | |
75 | 75 | void autoExtractChanged(); |
76 | 76 | /*! |
77 | 77 | * \brief positionChanged |
79 | 79 | */ |
80 | 80 | void positionChanged(); |
81 | 81 | void precisionChanged(); |
82 | ||
83 | void aboutToExtract(qint64 pos); | |
84 | 82 | |
85 | 83 | public Q_SLOTS: |
86 | 84 | /*! |
0 | 0 | /****************************************************************************** |
1 | 1 | QtAV: Multimedia framework based on Qt and FFmpeg |
2 | 2 | solve the version problem and diffirent api in FFmpeg and libav |
3 | Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com> | |
3 | Copyright (C) 2012-2018 Wang Bin <wbsecg1@gmail.com> | |
4 | 4 | |
5 | 5 | * This file is part of QtAV |
6 | 6 | |
58 | 58 | #include <libavutil/parseutils.h> |
59 | 59 | #include <libavutil/pixdesc.h> |
60 | 60 | #include <libavutil/avstring.h> |
61 | #include <libavfilter/version.h> | |
62 | ||
63 | #define AVCODEC_STATIC_REGISTER FFMPEG_MODULE_CHECK(LIBAVCODEC, 58, 10, 100) | |
64 | #define AVFORMAT_STATIC_REGISTER FFMPEG_MODULE_CHECK(LIBAVFORMAT, 58, 9, 100) | |
61 | 65 | |
62 | 66 | #if !FFMPEG_MODULE_CHECK(LIBAVUTIL, 51, 73, 101) |
63 | 67 | #include <libavutil/channel_layout.h> |
78 | 82 | #endif //QTAV_HAVE(AVRESAMPLE) |
79 | 83 | |
80 | 84 | #if QTAV_HAVE(AVFILTER) |
85 | #if LIBAVFILTER_VERSION_INT < AV_VERSION_INT(3,8,0) | |
81 | 86 | #include <libavfilter/avfiltergraph.h> /*code is here for old version*/ |
87 | #else | |
82 | 88 | #include <libavfilter/avfilter.h> |
89 | #endif | |
83 | 90 | #include <libavfilter/buffersink.h> |
84 | 91 | #include <libavfilter/buffersrc.h> |
85 | 92 | #endif //QTAV_HAVE(AVFILTER) |
455 | 462 | } } while(0) |
456 | 463 | |
457 | 464 | #endif //QTAV_COMPAT_H |
465 | ||
466 | #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(56,33,0) | |
467 | #define AV_CODEC_FLAG_GLOBAL_HEADER CODEC_FLAG_GLOBAL_HEADER | |
468 | #endif | |
469 | ||
470 | #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(56,56,100) | |
471 | #define AV_INPUT_BUFFER_MIN_SIZE FF_MIN_BUFFER_SIZE | |
472 | #endif | |
473 | ||
474 | #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(56,56,100) | |
475 | #define AV_INPUT_BUFFER_PADDING_SIZE FF_INPUT_BUFFER_PADDING_SIZE | |
476 | #endif |
70 | 70 | public: |
71 | 71 | AudioEncoderPrivate() |
72 | 72 | : AVEncoderPrivate() |
73 | , frame_size(0) | |
73 | 74 | { |
74 | 75 | bit_rate = 64000; |
75 | 76 | } |
78 | 79 | |
79 | 80 | AudioResampler *resampler; |
80 | 81 | AudioFormat format, format_used; |
82 | ||
83 | int frame_size; // used if avctx->frame_size == 0 | |
81 | 84 | }; |
82 | 85 | |
83 | 86 | class Q_AV_PRIVATE_EXPORT VideoEncoderPrivate : public AVEncoderPrivate |
0 | 0 | /****************************************************************************** |
1 | 1 | QtAV: Multimedia framework based on Qt and FFmpeg |
2 | Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com> | |
2 | Copyright (C) 2012-2018 Wang Bin <wbsecg1@gmail.com> | |
3 | 3 | |
4 | 4 | * This file is part of QtAV (from 2013) |
5 | 5 | |
35 | 35 | public: |
36 | 36 | FramePrivate() |
37 | 37 | : timestamp(0) |
38 | , data_align(1) | |
38 | 39 | {} |
39 | 40 | virtual ~FramePrivate() {} |
40 | 41 | |
43 | 44 | QVariantMap metadata; |
44 | 45 | QByteArray data; |
45 | 46 | qreal timestamp; |
47 | int data_align; | |
46 | 48 | }; |
47 | 49 | |
48 | 50 | } //namespace QtAV |
0 | 0 | /****************************************************************************** |
1 | 1 | QtAV: Media play library based on Qt and FFmpeg |
2 | Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com> | |
2 | Copyright (C) 2012-2017 Wang Bin <wbsecg1@gmail.com> | |
3 | 3 | |
4 | 4 | * This file is part of QtAV |
5 | 5 | |
27 | 27 | #include <QtCore/QRect> |
28 | 28 | #include <QtAV/VideoFrame.h> |
29 | 29 | #include <QtGui/QColor> |
30 | ||
30 | #include "QtAV/Statistics.h" | |
31 | 31 | /*TODO: |
32 | 32 | * Region of Interest(ROI) |
33 | 33 | * use matrix to compute out rect, mapped point etc |
53 | 53 | , out_aspect_ratio_mode(VideoRenderer::VideoAspectRatio) |
54 | 54 | , out_aspect_ratio(0) |
55 | 55 | , quality(VideoRenderer::QualityBest) |
56 | , orientation(0) | |
57 | 56 | , preferred_format(VideoFormat::Format_RGB32) |
58 | 57 | , force_preferred(false) |
59 | 58 | , brightness(0) |
61 | 60 | , hue(0) |
62 | 61 | , saturation(0) |
63 | 62 | , bg_color(0, 0, 0) |
63 | , orientation(0) | |
64 | 64 | { |
65 | 65 | //conv.setInFormat(PIX_FMT_YUV420P); |
66 | 66 | //conv.setOutFormat(PIX_FMT_BGR32); //TODO: why not RGB32? |
78 | 78 | return out_rect0 != out_rect; |
79 | 79 | } |
80 | 80 | // dar: displayed aspect ratio in video renderer orientation |
81 | const qreal dar = (orientation % 180) ? 1.0/outAspectRatio : outAspectRatio; | |
81 | int rotate = orientation; | |
82 | if (statistics) { | |
83 | rotate += int(statistics->video_only.rotate); | |
84 | } | |
85 | const qreal dar = (rotate % 180) ? 1.0/outAspectRatio : outAspectRatio; | |
82 | 86 | //qDebug("out rect: %f %dx%d ==>", out_aspect_ratio, out_rect.width(), out_rect.height()); |
83 | 87 | if (rendererAspectRatio >= dar) { //equals to original video aspect ratio here, also equals to out ratio |
84 | 88 | //renderer is too wide, use renderer's height, horizonal align center |
96 | 100 | return out_rect0 != out_rect; |
97 | 101 | } |
98 | 102 | virtual void setupQuality() {} |
103 | int rotation() const { | |
104 | if (!statistics) | |
105 | return orientation; | |
106 | return statistics->video_only.rotate + orientation; | |
107 | } | |
99 | 108 | |
100 | 109 | //draw background when necessary, for example, renderer is resized. Then set to false |
101 | 110 | bool update_background; |
113 | 122 | //out_rect: the displayed video frame out_rect in the renderer |
114 | 123 | QRect out_rect; //TODO: out_out_rect |
115 | 124 | QRectF roi; |
116 | int orientation; | |
117 | 125 | |
118 | 126 | VideoFrame video_frame; |
119 | 127 | VideoFormat::PixelFormat preferred_format; |
121 | 129 | |
122 | 130 | qreal brightness, contrast, hue, saturation; |
123 | 131 | QColor bg_color; |
132 | private: | |
133 | int orientation; | |
134 | friend class VideoRenderer; | |
124 | 135 | }; |
125 | 136 | |
126 | 137 | } //namespace QtAV |
22 | 22 | #define QTAV_VERSION_H |
23 | 23 | |
24 | 24 | #define QTAV_MAJOR 1 //((QTAV_VERSION&0xff0000)>>16) |
25 | #define QTAV_MINOR 12 //((QTAV_VERSION&0xff00)>>8) | |
25 | #define QTAV_MINOR 13 //((QTAV_VERSION&0xff00)>>8) | |
26 | 26 | #define QTAV_PATCH 0 //(QTAV_VERSION&0xff) |
27 | 27 | |
28 | 28 |
18 | 18 | BEGIN |
19 | 19 | BLOCK "000004b0" |
20 | 20 | BEGIN |
21 | VALUE "CompanyName", "Shanghai University->S3 Graphics->Deepin | wbsecg1@gmail.com" | |
21 | VALUE "CompanyName", "wbsecg1@gmail.com" | |
22 | 22 | VALUE "FileDescription", "QtAV Multimedia framework. http://qtav.org" |
23 | 23 | VALUE "FileVersion", QTAV_VERSION_STR ".0" |
24 | VALUE "LegalCopyright", "Copyright (C) 2012-2017 WangBin, wbsecg1@gmail.com" | |
24 | VALUE "LegalCopyright", "Copyright (C) 2012-2019 WangBin, wbsecg1@gmail.com" | |
25 | 25 | VALUE "InternalName", "QtAV" |
26 | 26 | VALUE "OriginalFilename", "QtAV.dll" |
27 | 27 | VALUE "ProductName", "QtAV" |
0 | 0 | /****************************************************************************** |
1 | 1 | QtAV: Multimedia framework based on Qt and FFmpeg |
2 | Copyright (C) 2012-2017 Wang Bin <wbsecg1@gmail.com> | |
2 | Copyright (C) 2012-2019 Wang Bin <wbsecg1@gmail.com> | |
3 | 3 | |
4 | 4 | * This file is part of QtAV |
5 | 5 | |
189 | 189 | { |
190 | 190 | static QString about = QString::fromLatin1("<img src='qrc:/QtAV.svg'><h3>QtAV " QTAV_VERSION_STR_LONG "</h3>\n" |
191 | 191 | "<p>%1</p><p>%2</p><p>%3 </p>" |
192 | "<p>Copyright (C) 2012-2016 Wang Bin (aka. Lucas Wang) <a href='mailto:wbsecg1@gmail.com'>wbsecg1@gmail.com</a></p>\n" | |
192 | "<p>Copyright (C) 2012-2019 Wang Bin (aka. Lucas Wang) <a href='mailto:wbsecg1@gmail.com'>wbsecg1@gmail.com</a></p>\n" | |
193 | 193 | "<p>%4: <a href='http://qtav.org/donate.html'>http://qtav.org/donate.html</a></p>\n" |
194 | 194 | "<p>%5: <a href='https://github.com/wang-bin/QtAV'>https://github.com/wang-bin/QtAV</a></p>\n" |
195 | 195 | "<p>%6: <a href='http://qtav.org'>http://qtav.org</a></p>" |
196 | 196 | ).arg(QObject::tr("Multimedia framework base on Qt and FFmpeg.\n")) |
197 | 197 | .arg(QObject::tr("Distributed under the terms of LGPLv2.1 or later.\n")) |
198 | .arg(QObject::tr("Shanghai University->S3 Graphics->Deepin->PPTV, Shanghai, China")) | |
198 | .arg(QObject::tr("Shanghai, China")) | |
199 | 199 | .arg(QObject::tr("Donate")) |
200 | 200 | .arg(QObject::tr("Source")) |
201 | 201 | .arg(QObject::tr("Home page")); |
274 | 274 | void* obj = const_cast<void*>(reinterpret_cast<const void*>(avformat_get_class())); |
275 | 275 | opts = Internal::optionsToString((void*)&obj); |
276 | 276 | opts.append(ushort('\n')); |
277 | av_register_all(); | |
277 | #if AVFORMAT_STATIC_REGISTER | |
278 | const AVInputFormat *i = NULL; | |
279 | void* it = NULL; | |
280 | while ((i = av_demuxer_iterate(&it))) { | |
281 | #else | |
278 | 282 | AVInputFormat *i = NULL; |
283 | av_register_all(); // MUST register all input/output formats | |
279 | 284 | while ((i = av_iformat_next(i))) { |
285 | #endif | |
280 | 286 | QString opt(Internal::optionsToString((void*)&i->priv_class).trimmed()); |
281 | 287 | if (opt.isEmpty()) |
282 | 288 | continue; |
284 | 290 | .arg(QLatin1String(i->name)) |
285 | 291 | .arg(opt)); |
286 | 292 | } |
293 | #if AVFORMAT_STATIC_REGISTER | |
294 | const AVOutputFormat *o = NULL; | |
295 | it = NULL; | |
296 | while ((o = av_muxer_iterate(&it))) { | |
297 | #else | |
298 | av_register_all(); // MUST register all input/output formats | |
287 | 299 | AVOutputFormat *o = NULL; |
288 | 300 | while ((o = av_oformat_next(o))) { |
301 | #endif | |
289 | 302 | QString opt(Internal::optionsToString((void*)&o->priv_class).trimmed()); |
290 | 303 | if (opt.isEmpty()) |
291 | 304 | continue; |
304 | 317 | void* obj = const_cast<void*>(reinterpret_cast<const void*>(avcodec_get_class())); |
305 | 318 | opts = Internal::optionsToString((void*)&obj); |
306 | 319 | opts.append(ushort('\n')); |
320 | const AVCodec* c = NULL; | |
321 | #if AVCODEC_STATIC_REGISTER | |
322 | void* it = NULL; | |
323 | while ((c = av_codec_iterate(&it))) { | |
324 | #else | |
307 | 325 | avcodec_register_all(); |
308 | AVCodec* c = NULL; | |
309 | while ((c=av_codec_next(c))) { | |
326 | while ((c = av_codec_next(c))) { | |
327 | #endif | |
310 | 328 | QString opt(Internal::optionsToString((void*)&c->priv_class).trimmed()); |
311 | 329 | if (opt.isEmpty()) |
312 | 330 | continue; |
0 | 0 | /****************************************************************************** |
1 | 1 | QtAV: Multimedia framework based on Qt and FFmpeg |
2 | Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com> | |
2 | Copyright (C) 2012-2017 Wang Bin <wbsecg1@gmail.com> | |
3 | 3 | |
4 | 4 | * This file is part of QtAV (from 2013) |
5 | 5 | |
55 | 55 | , coded_width(0) |
56 | 56 | , coded_height(0) |
57 | 57 | , gop_size(0) |
58 | , rotate(0) | |
58 | 59 | , d(new Private()) |
59 | 60 | { |
60 | 61 | } |
65 | 66 | , coded_width(v.coded_width) |
66 | 67 | , coded_height(v.coded_height) |
67 | 68 | , gop_size(v.gop_size) |
69 | , rotate(v.rotate) | |
68 | 70 | , d(v.d) |
69 | 71 | { |
70 | 72 | } |
76 | 78 | coded_width = v.coded_width; |
77 | 79 | coded_height = v.coded_height; |
78 | 80 | gop_size = v.gop_size; |
81 | rotate = v.rotate; | |
79 | 82 | d = v.d; |
80 | 83 | return *this; |
81 | 84 | } |
0 | 0 | /****************************************************************************** |
1 | 1 | VideoCapture.cpp: description |
2 | Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com> | |
2 | Copyright (C) 2012-2018 Wang Bin <wbsecg1@gmail.com> | |
3 | 3 | |
4 | 4 | * This file is part of QtAV |
5 | 5 | |
83 | 83 | QMetaObject::invokeMethod(cap, "failed"); |
84 | 84 | return; |
85 | 85 | } |
86 | if (file.write(frame.frameData()) <= 0) { | |
86 | int sz = 0; | |
87 | const char* data = (const char*)frame.frameDataPtr(&sz); | |
88 | if (file.write(data, sz) <= 0) { | |
87 | 89 | qWarning("VideoCapture is failed to write captured frame with original format"); |
88 | 90 | QMetaObject::invokeMethod(cap, "failed"); |
89 | 91 | file.close(); |
0 | 0 | /****************************************************************************** |
1 | 1 | QtAV: Multimedia framework based on Qt and FFmpeg |
2 | Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com> | |
2 | Copyright (C) 2012-2018 Wang Bin <wbsecg1@gmail.com> | |
3 | 3 | |
4 | 4 | * This file is part of QtAV |
5 | 5 | |
153 | 153 | { |
154 | 154 | } |
155 | 155 | |
156 | VideoFrame::VideoFrame(int width, int height, const VideoFormat &format, const QByteArray& data) | |
156 | VideoFrame::VideoFrame(int width, int height, const VideoFormat &format, const QByteArray& data, int alignment) | |
157 | 157 | : Frame(new VideoFramePrivate(width, height, format)) |
158 | 158 | { |
159 | 159 | Q_D(VideoFrame); |
160 | 160 | d->data = data; |
161 | d->data_align = alignment; | |
161 | 162 | } |
162 | 163 | |
163 | 164 | VideoFrame::VideoFrame(const QImage& image) |
352 | 353 | VideoFrame f(to(VideoFormat(VideoFormat::pixelFormatFromImageFormat(fmt)), dstSize, roi)); |
353 | 354 | if (!f) |
354 | 355 | return QImage(); |
355 | QImage image((const uchar*)f.frameData().constData(), f.width(), f.height(), f.bytesPerLine(0), fmt); | |
356 | QImage image(f.frameDataPtr(), f.width(), f.height(), f.bytesPerLine(0), fmt); | |
356 | 357 | return image.copy(); |
357 | 358 | } |
358 | 359 | |
394 | 395 | qWarning() << "VideoFrame::to error: " << format() << "=>" << fmt; |
395 | 396 | return VideoFrame(); |
396 | 397 | } |
397 | VideoFrame f(w, h, fmt, conv.outData()); | |
398 | VideoFrame f(w, h, fmt, conv.outData(), ImageConverter::DataAlignment); | |
398 | 399 | f.setBits(conv.outPlanes()); |
399 | 400 | f.setBytesPerLine(conv.outLineSizes()); |
400 | 401 | if (fmt.isRGB()) { |
25 | 25 | #include <QtCore/QScopedPointer> |
26 | 26 | #include <QtCore/QStringList> |
27 | 27 | #include <QtCore/QThread> |
28 | #include <QtCore/QMutex> | |
29 | #include <QtCore/QMutexLocker> | |
28 | 30 | #include "QtAV/VideoCapture.h" |
29 | 31 | #include "QtAV/VideoDecoder.h" |
30 | 32 | #include "QtAV/AVDemuxer.h" |
32 | 34 | #include "utils/BlockingQueue.h" |
33 | 35 | #include "utils/Logger.h" |
34 | 36 | |
35 | // TODO: event and signal do not work | |
36 | #define ASYNC_SIGNAL 0 | |
37 | #define ASYNC_EVENT 0 | |
38 | #define ASYNC_TASK 1 | |
39 | 37 | namespace QtAV { |
40 | 38 | |
41 | 39 | class ExtractThread : public QThread { |
42 | 40 | public: |
43 | 41 | ExtractThread(QObject *parent = 0) |
44 | 42 | : QThread(parent) |
43 | , timeout_ms(50UL) | |
45 | 44 | , stop(false) |
46 | 45 | { |
47 | tasks.setCapacity(1); // avoid too frequent | |
46 | // avoid too frequent -- we only care about the latest request | |
47 | // whether it be for a frame or to stop thread or whatever else. | |
48 | tasks.setCapacity(1); | |
48 | 49 | } |
49 | 50 | ~ExtractThread() { |
50 | 51 | waitStop(); |
56 | 57 | wait(); |
57 | 58 | } |
58 | 59 | |
60 | unsigned long timeout_ms; | |
61 | ||
59 | 62 | void addTask(QRunnable* t) { |
60 | if (tasks.size() >= tasks.capacity()) { | |
61 | QRunnable *task = tasks.take(); //clear only for seek task | |
62 | if (task->autoDelete()) | |
63 | // Note that while a simpler solution would have been to not use | |
64 | // a custom 'Task' mechanism but rather to use signals | |
65 | // or QEvent posting to this thread -- the approach here has a very | |
66 | // advantageous property. Namely, duplicate repeated requests for | |
67 | // a frame end up getting dropped and only the latest request gets | |
68 | // serviced. This interoperates well with eg VideoPreviewWidget | |
69 | // which really only cares about the latest frame requested by the user. | |
70 | while (tasks.size() >= tasks.capacity() && tasks.capacity() > 0) { | |
71 | // Race condition existed here -- to avoid it we need to take() | |
72 | // with a timeout (to make sure it doesn't hang) and check return value. | |
73 | // This is because extractor thread is also calling .take() at the same time, | |
74 | // and in rare cases the above expression in the while loop evaluates to true | |
75 | // while by the time the below line executes the underlying queue may become empty. | |
76 | // This led to very occasional hangs. Hence this .take() call was modified to include | |
77 | // a timeout. | |
78 | QRunnable *task = tasks.take(timeout_ms); //clear for seek & stop task | |
79 | if (task && task->autoDelete()) | |
63 | 80 | delete task; |
64 | 81 | } |
65 | tasks.put(t); | |
82 | if (!tasks.put(t,timeout_ms)) { | |
83 | qWarning("ExtractThread::addTask -- added a task to an already-full queue! FIXME!"); | |
84 | } | |
66 | 85 | } |
67 | 86 | void scheduleStop() { |
68 | 87 | class StopTask : public QRunnable { |
69 | 88 | public: |
70 | 89 | StopTask(ExtractThread* t) : thread(t) {} |
71 | void run() { thread->stop = true;} | |
90 | void run() { thread->stop = true; } | |
72 | 91 | private: |
73 | 92 | ExtractThread *thread; |
74 | 93 | }; |
77 | 96 | |
78 | 97 | protected: |
79 | 98 | virtual void run() { |
80 | #if ASYNC_TASK | |
81 | 99 | while (!stop) { |
82 | 100 | QRunnable *task = tasks.take(); |
83 | if (!task) | |
84 | return; | |
85 | task->run(); | |
86 | if (task->autoDelete()) | |
87 | delete task; | |
88 | } | |
89 | #else | |
90 | exec(); | |
91 | #endif //ASYNC_TASK | |
101 | if (task) { | |
102 | task->run(); | |
103 | if (task->autoDelete()) | |
104 | delete task; | |
105 | } | |
106 | } | |
107 | qDebug("ExtractThread exiting..."); | |
92 | 108 | } |
93 | 109 | public: |
94 | 110 | volatile bool stop; |
102 | 118 | { |
103 | 119 | public: |
104 | 120 | VideoFrameExtractorPrivate() |
105 | : extracted(false) | |
106 | , abort_seek(false) | |
121 | : abort_seek(false) | |
107 | 122 | , async(true) |
108 | 123 | , has_video(true) |
109 | 124 | , auto_extract(true) |
134 | 149 | #if QTAV_HAVE(VDA) |
135 | 150 | // << QStringLiteral("VDA") // only 1 app can use VDA at a given time |
136 | 151 | #endif //QTAV_HAVE(VDA) |
137 | << QStringLiteral("FFmpeg"); | |
152 | #if QTAV_HAVE(VIDEOTOOLBOX) | |
153 | // << QStringLiteral("VideoToolbox") | |
154 | #endif //QTAV_HAVE(VIDEOTOOLBOX) | |
155 | << QStringLiteral("FFmpeg"); | |
138 | 156 | } |
139 | 157 | ~VideoFrameExtractorPrivate() { |
140 | 158 | // stop first before demuxer and decoder close to avoid running new seek task after demuxer is closed. |
167 | 185 | else |
168 | 186 | precision = kDefaultPrecision; |
169 | 187 | } |
188 | demuxer.setStreamIndex(AVDemuxer::VideoStream, 0); | |
170 | 189 | foreach (const QString& c, codecs) { |
171 | 190 | VideoDecoder *vd = VideoDecoder::create(c.toUtf8().constData()); |
172 | 191 | if (!vd) |
173 | 192 | continue; |
174 | 193 | decoder.reset(vd); |
175 | decoder->setCodecContext(demuxer.videoCodecContext()); | |
176 | if (!decoder->open()) { | |
194 | AVCodecContext *cctx = demuxer.videoCodecContext(); | |
195 | if (cctx) decoder->setCodecContext(demuxer.videoCodecContext()); | |
196 | if (!cctx || !decoder->open()) { | |
177 | 197 | decoder.reset(0); |
178 | 198 | continue; |
179 | 199 | } |
188 | 208 | } |
189 | 209 | |
190 | 210 | // return the key frame position |
191 | bool extractInPrecision(qint64 value, int range) { | |
211 | bool extractInPrecision(qint64 value, int range, QString & err, bool & aborted) { | |
192 | 212 | abort_seek = false; |
213 | err = ""; | |
214 | aborted = false; | |
193 | 215 | frame = VideoFrame(); |
194 | 216 | if (value < demuxer.startTime()) |
195 | 217 | value += demuxer.startTime(); |
201 | 223 | bool warn_out_of_range = true; |
202 | 224 | while (!demuxer.atEnd()) { |
203 | 225 | if (abort_seek) { |
226 | err = "abort seek before read"; | |
204 | 227 | qDebug("VideoFrameExtractor abort seek before read"); |
228 | aborted = true; | |
205 | 229 | return false; |
206 | 230 | } |
207 | 231 | if (!demuxer.readFrame()) |
235 | 259 | } |
236 | 260 | if (!pkt.isValid()) { |
237 | 261 | qWarning("VideoFrameExtractor failed to get a packet at %lld", value); |
262 | err = QString().sprintf("failed to get a packet at %lld",value); | |
238 | 263 | return false; |
239 | 264 | } |
240 | 265 | decoder->flush(); //must flush otherwise old frames will be decoded at the beginning |
244 | 269 | while (k < 2 && !frame.isValid()) { |
245 | 270 | if (abort_seek) { |
246 | 271 | qDebug("VideoFrameExtractor abort seek before decoding key frames"); |
272 | err = "abort seek before decoding key frames"; | |
273 | aborted = true; | |
247 | 274 | return false; |
248 | 275 | } |
249 | 276 | //qWarning("invalid key frame!!!!! undecoded: %d", decoder->undecodedSize()); |
267 | 294 | while (!demuxer.atEnd()) { |
268 | 295 | if (abort_seek) { |
269 | 296 | qDebug("VideoFrameExtractor abort seek after key frame before read"); |
297 | err = "abort seek after key frame before read"; | |
298 | aborted = true; | |
270 | 299 | return false; |
271 | 300 | } |
272 | 301 | if (!demuxer.readFrame()) |
297 | 326 | if (!decoder->decode(pkt)) { |
298 | 327 | qWarning("!!!!!!!!!decode failed!!!!"); |
299 | 328 | frame = VideoFrame(); |
329 | err = "decode failed"; | |
300 | 330 | return false; |
301 | 331 | } |
302 | 332 | // store the last decoded frame because next frame may be out of range |
319 | 349 | if (diff > range && t > pts) { |
320 | 350 | qWarning("out pts out of range. diff=%lld, range=%d", diff, range); |
321 | 351 | frame = VideoFrame(); |
352 | err = QString().sprintf("out pts out of range. diff=%lld, range=%d", diff, range); | |
322 | 353 | return false; |
323 | 354 | } |
324 | 355 | } |
325 | 356 | ++seek_count; |
326 | 357 | // now we get the final frame |
327 | 358 | if (demuxer.atEnd()) |
328 | releaseResourceInternal(); | |
359 | releaseResourceInternal(false); | |
329 | 360 | return true; |
330 | 361 | } |
331 | void releaseResourceInternal() { | |
362 | void releaseResourceInternal(bool releaseFrame = true) { | |
363 | if (releaseFrame) frame = VideoFrame(); | |
332 | 364 | seek_count = 0; |
333 | 365 | // close codec context first. |
334 | 366 | decoder.reset(0); |
347 | 379 | thread.addTask(new Cleaner(this)); |
348 | 380 | } |
349 | 381 | |
350 | bool extracted; | |
351 | 382 | volatile bool abort_seek; |
352 | 383 | bool async; |
353 | 384 | bool has_video; |
355 | 386 | bool auto_extract; |
356 | 387 | bool auto_precision; |
357 | 388 | int seek_count; |
358 | qint64 position; | |
359 | int precision; | |
360 | QString source; | |
389 | qint64 position; ///< only read/written by this->thread(), never read by extractor thread so no lock necessary | |
390 | volatile int precision; ///< is volatile because may be written by this->thread() and read by extractor thread but typical use is to not modify it while extract is running | |
391 | QString source; ///< is written-to by this->thread() but may be read by extractor thread; important: apparently two threads accessing QString is supported by Qt according to wang-bin, so we aren't guarding this with a lock | |
361 | 392 | AVDemuxer demuxer; |
362 | 393 | QScopedPointer<VideoDecoder> decoder; |
363 | VideoFrame frame; | |
394 | VideoFrame frame; ///< important: we only allow the extract thread to modify this value | |
364 | 395 | QStringList codecs; |
365 | 396 | ExtractThread thread; |
366 | 397 | static QVariantHash dec_opt_framedrop, dec_opt_normal; |
373 | 404 | QObject(parent) |
374 | 405 | { |
375 | 406 | DPTR_D(VideoFrameExtractor); |
376 | moveToThread(&d.thread); | |
377 | 407 | d.thread.start(); |
378 | connect(this, SIGNAL(aboutToExtract(qint64)), SLOT(extractInternal(qint64))); | |
379 | 408 | } |
380 | 409 | |
381 | 410 | void VideoFrameExtractor::setSource(const QString url) |
386 | 415 | d.source = url; |
387 | 416 | d.has_video = true; |
388 | 417 | Q_EMIT sourceChanged(); |
389 | d.frame = VideoFrame(); | |
390 | 418 | d.safeReleaseResource(); |
391 | 419 | } |
392 | 420 | |
431 | 459 | if (qAbs(value - d.position) < precision()) { |
432 | 460 | return; |
433 | 461 | } |
434 | d.frame = VideoFrame(); | |
435 | d.extracted = false; | |
436 | 462 | d.position = value; |
437 | 463 | Q_EMIT positionChanged(); |
438 | 464 | if (!autoExtract()) |
453 | 479 | d.auto_precision = value < 0; |
454 | 480 | // explain why value (p0) is used but not the actual decoded position (p) |
455 | 481 | // it's key frame finding rule |
456 | if (value >= 0) | |
482 | if (value >= 0) { | |
457 | 483 | d.precision = value; |
484 | } | |
458 | 485 | Q_EMIT precisionChanged(); |
459 | 486 | } |
460 | 487 | |
461 | 488 | int VideoFrameExtractor::precision() const |
462 | 489 | { |
463 | 490 | return d_func().precision; |
464 | } | |
465 | ||
466 | bool VideoFrameExtractor::event(QEvent *e) | |
467 | { | |
468 | //qDebug("event: %d", e->type()); | |
469 | if (e->type() != QEvent::User) | |
470 | return QObject::event(e); | |
471 | extractInternal(position()); // FIXME: wrong position | |
472 | return true; | |
473 | 491 | } |
474 | 492 | |
475 | 493 | void VideoFrameExtractor::extract() |
479 | 497 | extractInternal(position()); |
480 | 498 | return; |
481 | 499 | } |
482 | #if ASYNC_SIGNAL | |
483 | else { | |
484 | Q_EMIT aboutToExtract(position()); | |
485 | return; | |
486 | } | |
487 | #endif | |
488 | #if ASYNC_TASK | |
489 | 500 | class ExtractTask : public QRunnable { |
490 | 501 | public: |
491 | 502 | ExtractTask(VideoFrameExtractor *e, qint64 t) |
499 | 510 | VideoFrameExtractor *extractor; |
500 | 511 | qint64 position; |
501 | 512 | }; |
513 | // We want to abort the previous extract() since we are | |
514 | // only interested in the latest request. | |
515 | // So, abort_seek is a 'previous frame extract abort mechanism' | |
516 | // -- this flag is repeatedly checked by extractInPrecision() | |
517 | // (called by extractInternal()) and if true, method returns early. | |
518 | // Note if seek/decode is aborted, aborted() signal will be emitted. | |
502 | 519 | d.abort_seek = true; |
503 | 520 | d.thread.addTask(new ExtractTask(this, position())); |
504 | return; | |
505 | #endif | |
506 | #if ASYNC_EVENT | |
507 | qApp->postEvent(this, new QEvent(QEvent::User)); | |
508 | #endif //ASYNC_EVENT | |
509 | 521 | } |
510 | 522 | |
511 | 523 | void VideoFrameExtractor::extractInternal(qint64 pos) |
513 | 525 | DPTR_D(VideoFrameExtractor); |
514 | 526 | int precision_old = precision(); |
515 | 527 | if (!d.checkAndOpen()) { |
516 | Q_EMIT error(); | |
528 | Q_EMIT error("Cannot open file"); | |
517 | 529 | //qWarning("can not open decoder...."); |
518 | return; // error handling | |
530 | return; | |
519 | 531 | } |
520 | 532 | if (precision_old != precision()) { |
521 | 533 | Q_EMIT precisionChanged(); |
522 | 534 | } |
523 | d.extracted = d.extractInPrecision(pos, precision()); | |
524 | if (!d.extracted) { | |
525 | Q_EMIT error(); | |
535 | bool extractOk = false, isAborted = true; | |
536 | QString err; | |
537 | extractOk = d.extractInPrecision(pos, precision(), err, isAborted); | |
538 | if (!extractOk) { | |
539 | if (isAborted) | |
540 | Q_EMIT aborted(QString().sprintf("Abort at position %lld: %s",pos,err.toLatin1().constData())); | |
541 | else | |
542 | Q_EMIT error(QString().sprintf("Cannot extract frame at position %lld: %s",pos,err.toLatin1().constData())); | |
526 | 543 | return; |
527 | 544 | } |
528 | 545 | Q_EMIT frameExtracted(d.frame); |
0 | 0 | /****************************************************************************** |
1 | 1 | QtAV: Multimedia framework based on Qt and FFmpeg |
2 | Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com> | |
2 | Copyright (C) 2012-2017 Wang Bin <wbsecg1@gmail.com> | |
3 | 3 | |
4 | 4 | * This file is part of QtAV |
5 | 5 | |
242 | 242 | DPTR_D(VideoThread); |
243 | 243 | if (!d.dec || !d.dec->isAvailable() || !d.outputSet) |
244 | 244 | return; |
245 | resetState(); | |
245 | // resetState(); // we can't reset the thread state from here | |
246 | 246 | if (d.capture->autoSave()) { |
247 | 247 | d.capture->setCaptureName(QFileInfo(d.statistics->url).completeBaseName()); |
248 | 248 | } |
319 | 319 | if (!pkt.isValid()) { |
320 | 320 | // may be we should check other information. invalid packet can come from |
321 | 321 | wait_key_frame = true; |
322 | qDebug("Invalid packet! flush video codec context!!!!!!!!!! video packet queue size: %d", d.packets.size()); | |
322 | qDebug("Invalid packet! flush video codec context!!!!!!!!!! video packet queue size: %d", d.packets.size()); | |
323 | 323 | d.dec->flush(); //d.dec instead of dec because d.dec maybe changed in processNextTask() but dec is not |
324 | 324 | d.render_pts0 = pkt.pts; |
325 | 325 | sync_id = pkt.position; |
487 | 487 | dec->setOptions(*dec_opt); |
488 | 488 | if (!dec->decode(pkt)) { |
489 | 489 | d.pts_history.push_back(d.pts_history.back()); |
490 | qWarning("Decode video failed. undecoded: %d/%d", dec->undecodedSize(), pkt.data.size()); | |
490 | //qWarning("Decode video failed. undecoded: %d/%d", dec->undecodedSize(), pkt.data.size()); | |
491 | 491 | if (pkt.isEOF()) { |
492 | 492 | Q_EMIT eofDecoded(); |
493 | 493 | qDebug("video decode eof done. d.render_pts0: %.3f", d.render_pts0); |
522 | 522 | continue; |
523 | 523 | } |
524 | 524 | pkt_data = pkt.data.constData(); |
525 | if (frame.timestamp() <= 0) | |
525 | if (frame.timestamp() < 0) | |
526 | 526 | frame.setTimestamp(pkt.pts); // pkt.pts is wrong. >= real timestamp |
527 | 527 | const qreal pts = frame.timestamp(); |
528 | 528 | d.pts_history.push_back(pts); |
0 | 0 | /****************************************************************************** |
1 | 1 | QtAV: Multimedia framework based on Qt and FFmpeg |
2 | Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com> | |
2 | Copyright (C) 2012-2018 Wang Bin <wbsecg1@gmail.com> | |
3 | 3 | |
4 | 4 | * This file is part of QtAV |
5 | 5 | |
46 | 46 | AVDecoder::AVDecoder(AVDecoderPrivate &d) |
47 | 47 | :DPTR_INIT(&d) |
48 | 48 | { |
49 | #if !AVCODEC_STATIC_REGISTER | |
49 | 50 | avcodec_register_all(); // avcodec_find_decoder will always be used |
51 | #endif | |
50 | 52 | } |
51 | 53 | |
52 | 54 | AVDecoder::~AVDecoder() |
0 | 0 | /****************************************************************************** |
1 | 1 | QtAV: Multimedia framework based on Qt and FFmpeg |
2 | Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com> | |
2 | Copyright (C) 2012-2018 Wang Bin <wbsecg1@gmail.com> | |
3 | 3 | |
4 | 4 | * This file is part of QtAV |
5 | 5 | |
42 | 42 | static QStringList codecs; |
43 | 43 | if (!codecs.isEmpty()) |
44 | 44 | return codecs; |
45 | const AVCodec* c = NULL; | |
46 | #if AVCODEC_STATIC_REGISTER | |
47 | void* it = NULL; | |
48 | while ((c = av_codec_iterate(&it))) { | |
49 | #else | |
45 | 50 | avcodec_register_all(); |
46 | AVCodec* c = NULL; | |
47 | while ((c=av_codec_next(c))) { | |
51 | while ((c = av_codec_next(c))) { | |
52 | #endif | |
48 | 53 | if (!av_codec_is_decoder(c) || c->type != AVMEDIA_TYPE_AUDIO) |
49 | 54 | continue; |
50 | 55 | codecs.append(QString::fromLatin1(c->name)); |
0 | 0 | /****************************************************************************** |
1 | 1 | QtAV: Multimedia framework based on Qt and FFmpeg |
2 | Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com> | |
2 | Copyright (C) 2012-2018 Wang Bin <wbsecg1@gmail.com> | |
3 | 3 | |
4 | 4 | * This file is part of QtAV |
5 | 5 | |
62 | 62 | : AudioDecoderPrivate() |
63 | 63 | , frame(av_frame_alloc()) |
64 | 64 | { |
65 | #if !AVCODEC_STATIC_REGISTER | |
65 | 66 | avcodec_register_all(); |
67 | #endif | |
66 | 68 | } |
67 | 69 | ~AudioDecoderFFmpegPrivate() { |
68 | 70 | if (frame) { |
0 | 0 | /****************************************************************************** |
1 | 1 | QtAV: Multimedia framework based on Qt and FFmpeg |
2 | Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com> | |
2 | Copyright (C) 2012-2018 Wang Bin <wbsecg1@gmail.com> | |
3 | 3 | |
4 | 4 | * This file is part of QtAV (from 2015) |
5 | 5 | |
44 | 44 | static QStringList codecs; |
45 | 45 | if (!codecs.isEmpty()) |
46 | 46 | return codecs; |
47 | const AVCodec* c = NULL; | |
48 | #if AVCODEC_STATIC_REGISTER | |
49 | void* it = NULL; | |
50 | while ((c = av_codec_iterate(&it))) { | |
51 | #else | |
47 | 52 | avcodec_register_all(); |
48 | AVCodec* c = NULL; | |
49 | while ((c=av_codec_next(c))) { | |
53 | while ((c = av_codec_next(c))) { | |
54 | #endif | |
50 | 55 | if (!av_codec_is_encoder(c) || c->type != AVMEDIA_TYPE_AUDIO) |
51 | 56 | continue; |
52 | 57 | codecs.append(QString::fromLatin1(c->name)); |
79 | 84 | return d_func().format_used; |
80 | 85 | } |
81 | 86 | |
87 | int AudioEncoder::frameSize() const | |
88 | { | |
89 | return d_func().frame_size; | |
90 | } | |
82 | 91 | } //namespace QtAV |
52 | 52 | public: |
53 | 53 | AudioEncoderFFmpegPrivate() |
54 | 54 | : AudioEncoderPrivate() |
55 | , frame_size(0) | |
56 | 55 | { |
57 | 56 | avcodec_register_all(); |
58 | 57 | // NULL: codec-specific defaults won't be initialized, which may result in suboptimal default settings (this is important mainly for encoders, e.g. libx264). |
61 | 60 | bool open() Q_DECL_OVERRIDE; |
62 | 61 | bool close() Q_DECL_OVERRIDE; |
63 | 62 | |
64 | int frame_size; // used if avctx->frame_size == 0 | |
65 | 63 | QByteArray buffer; |
66 | 64 | }; |
67 | 65 | |
152 | 150 | } else { |
153 | 151 | buffer_size = frame_size*format_used.bytesPerSample()*format_used.channels()*2+200; |
154 | 152 | } |
155 | if (buffer_size < FF_MIN_BUFFER_SIZE) | |
156 | buffer_size = FF_MIN_BUFFER_SIZE; | |
153 | if (buffer_size < AV_INPUT_BUFFER_MIN_SIZE) | |
154 | buffer_size = AV_INPUT_BUFFER_MIN_SIZE; | |
157 | 155 | buffer.resize(buffer_size); |
158 | 156 | return true; |
159 | 157 | } |
0 | 0 | /****************************************************************************** |
1 | 1 | QtAV: Multimedia framework based on Qt and FFmpeg |
2 | Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com> | |
2 | Copyright (C) 2012-2017 Wang Bin <wbsecg1@gmail.com> | |
3 | 3 | |
4 | 4 | * This file is part of QtAV (from 2016) |
5 | 5 | |
60 | 60 | InteropResource* InteropResource::create(InteropType type) |
61 | 61 | { |
62 | 62 | if (type == InteropAuto) { |
63 | #ifdef Q_OS_MACX | |
63 | type = InteropCVOpenGLES; | |
64 | #if defined(Q_OS_MACX) | |
64 | 65 | type = InteropIOSurface; |
65 | #else | |
66 | type = InteropCVPixelBuffer; | |
67 | type = InteropCVOpenGLES; | |
66 | #endif | |
67 | #if defined(__builtin_available) | |
68 | if (__builtin_available(iOS 11, macOS 10.6, *)) | |
69 | type = InteropIOSurface; | |
68 | 70 | #endif |
69 | 71 | } |
70 | 72 | switch (type) { |
71 | 73 | case InteropCVPixelBuffer: return CreateInteropCVPixelbuffer(); |
72 | #ifdef Q_OS_MACX | |
74 | #if defined(Q_OS_MACX) || defined(__IPHONE_11_0) | |
73 | 75 | case InteropIOSurface: return CreateInteropIOSurface(); |
74 | 76 | //case InteropCVOpenGL: return CreateInteropCVOpenGL(); |
75 | 77 | #else |
0 | /****************************************************************************** | |
1 | QtAV: Multimedia framework based on Qt and FFmpeg | |
2 | Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com> | |
3 | ||
4 | * This file is part of QtAV (from 2016) | |
5 | ||
6 | This library is free software; you can redistribute it and/or | |
7 | modify it under the terms of the GNU Lesser General Public | |
8 | License as published by the Free Software Foundation; either | |
9 | version 2.1 of the License, or (at your option) any later version. | |
10 | ||
11 | This library is distributed in the hope that it will be useful, | |
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
14 | Lesser General Public License for more details. | |
15 | ||
16 | You should have received a copy of the GNU Lesser General Public | |
17 | License along with this library; if not, write to the Free Software | |
18 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
19 | ******************************************************************************/ | |
20 | ||
21 | #include "SurfaceInteropCV.h" | |
22 | #include <IOSurface/IOSurface.h> | |
23 | #include "QtAV/VideoFrame.h" | |
24 | #include "opengl/OpenGLHelper.h" | |
25 | ||
26 | namespace QtAV { | |
27 | namespace cv { | |
28 | VideoFormat::PixelFormat format_from_cv(int cv); | |
29 | ||
30 | // https://www.opengl.org/registry/specs/APPLE/rgb_422.txt | |
31 | // https://www.opengl.org/registry/specs/APPLE/ycbcr_422.txt uyvy: UNSIGNED_SHORT_8_8_REV_APPLE, yuy2: GL_UNSIGNED_SHORT_8_8_APPLE | |
32 | // check extension GL_APPLE_rgb_422 and rectangle? | |
33 | class InteropResourceIOSurface Q_DECL_FINAL : public InteropResource | |
34 | { | |
35 | public: | |
36 | bool stridesForWidth(int cvfmt, int width, int* strides, VideoFormat::PixelFormat* outFmt) Q_DECL_OVERRIDE; | |
37 | bool mapToTexture2D() const Q_DECL_OVERRIDE { return false;} | |
38 | bool map(CVPixelBufferRef buf, GLuint *tex, int w, int h, int plane) Q_DECL_OVERRIDE; | |
39 | GLuint createTexture(CVPixelBufferRef, const VideoFormat &fmt, int plane, int planeWidth, int planeHeight) Q_DECL_OVERRIDE | |
40 | { | |
41 | Q_UNUSED(fmt); | |
42 | Q_UNUSED(plane); | |
43 | Q_UNUSED(planeWidth); | |
44 | Q_UNUSED(planeHeight); | |
45 | GLuint tex = 0; | |
46 | DYGL(glGenTextures(1, &tex)); | |
47 | return tex; | |
48 | } | |
49 | }; | |
50 | ||
51 | InteropResource* CreateInteropIOSurface() | |
52 | { | |
53 | return new InteropResourceIOSurface(); | |
54 | } | |
55 | ||
56 | bool InteropResourceIOSurface::stridesForWidth(int cvfmt, int width, int *strides, VideoFormat::PixelFormat* outFmt) | |
57 | { | |
58 | switch (cvfmt) { | |
59 | case '2vuy': | |
60 | case 'yuvs': { | |
61 | *outFmt = VideoFormat::Format_VYU; | |
62 | if (strides[0] <= 0) | |
63 | strides[0] = 4*width; //RGB layout: BRGX | |
64 | else | |
65 | strides[0] *= 2; | |
66 | } | |
67 | break; | |
68 | default: | |
69 | return InteropResource::stridesForWidth(cvfmt, width, strides, outFmt); | |
70 | } | |
71 | return true; | |
72 | } | |
73 | ||
74 | bool InteropResourceIOSurface::map(CVPixelBufferRef buf, GLuint *tex, int w, int h, int plane) | |
75 | { | |
76 | Q_UNUSED(w); | |
77 | Q_UNUSED(h); | |
78 | const OSType pixfmt = CVPixelBufferGetPixelFormatType(buf); | |
79 | GLint iformat; | |
80 | GLenum format, dtype; | |
81 | getParametersGL(pixfmt, &iformat, &format, &dtype, plane); | |
82 | switch (pixfmt) { | |
83 | case '2vuy': | |
84 | case 'yuvs': | |
85 | iformat = GL_RGB8; // ES2 requires internal format and format are the same. OSX can use internal format GL_RGB or sized GL_RGB8 | |
86 | format = GL_RGB_422_APPLE; | |
87 | dtype = pixfmt == '2vuy' ? GL_UNSIGNED_SHORT_8_8_APPLE : GL_UNSIGNED_SHORT_8_8_REV_APPLE; | |
88 | break; | |
89 | // macOS: GL_RGBA8, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV | |
90 | // GL_YCBCR_422_APPLE: convert to rgb texture internally (bt601). only supports OSX | |
91 | // GL_RGB_422_APPLE: raw yuv422 texture | |
92 | case 'BGRA': | |
93 | iformat = GL_RGBA8; | |
94 | format = GL_BGRA; | |
95 | dtype = GL_UNSIGNED_INT_8_8_8_8_REV; | |
96 | break; | |
97 | default: | |
98 | break; | |
99 | } | |
100 | const GLenum target = GL_TEXTURE_RECTANGLE; | |
101 | DYGL(glBindTexture(target, *tex)); | |
102 | const int planeW = CVPixelBufferGetWidthOfPlane(buf, plane); | |
103 | const int planeH = CVPixelBufferGetHeightOfPlane(buf, plane); | |
104 | //qDebug("map plane%d. %dx%d, gl %d %d %d", plane, planeW, planeH, iformat, format, dtype); | |
105 | ||
106 | const IOSurfaceRef surface = CVPixelBufferGetIOSurface(buf); | |
107 | CGLError err = CGLTexImageIOSurface2D(CGLGetCurrentContext(), target, iformat, planeW, planeH, format, dtype, surface, plane); | |
108 | if (err != kCGLNoError) { | |
109 | qWarning("error creating IOSurface texture at plane %d: %s", plane, CGLErrorString(err)); | |
110 | } | |
111 | DYGL(glBindTexture(target, 0)); | |
112 | return true; | |
113 | } | |
114 | } // namespace cv | |
115 | } // namespace QtAV |
0 | /****************************************************************************** | |
1 | QtAV: Multimedia framework based on Qt and FFmpeg | |
2 | Copyright (C) 2012-2017 Wang Bin <wbsecg1@gmail.com> | |
3 | ||
4 | * This file is part of QtAV (from 2016) | |
5 | ||
6 | This library is free software; you can redistribute it and/or | |
7 | modify it under the terms of the GNU Lesser General Public | |
8 | License as published by the Free Software Foundation; either | |
9 | version 2.1 of the License, or (at your option) any later version. | |
10 | ||
11 | This library is distributed in the hope that it will be useful, | |
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
14 | Lesser General Public License for more details. | |
15 | ||
16 | You should have received a copy of the GNU Lesser General Public | |
17 | License along with this library; if not, write to the Free Software | |
18 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
19 | ******************************************************************************/ | |
20 | #define IOS_USE_PRIVATE 0 // private symbols are forbidden by app store | |
21 | #include "SurfaceInteropCV.h" | |
22 | #ifdef Q_OS_IOS | |
23 | #import <OpenGLES/EAGL.h> | |
24 | #include <CoreVideo/CVOpenGLESTextureCache.h> | |
25 | # ifdef __IPHONE_11_0 // always defined in new sdk | |
26 | # import <OpenGLES/EAGLIOSurface.h> | |
27 | # endif //__IPHONE_11_0 | |
28 | # if COREVIDEO_SUPPORTS_IOSURFACE && IOS_USE_PRIVATE | |
29 | // declare the private API if IOSurface is supported in SDK. Thus we can use IOSurface on any iOS version even with old SDK and compiler | |
30 | @interface EAGLContext() | |
31 | - (BOOL)texImageIOSurface:(IOSurfaceRef)ioSurface target:(NSUInteger)target internalFormat:(NSUInteger)internalFormat width:(uint32_t)width height:(uint32_t)height format:(NSUInteger)format type:(NSUInteger)type plane:(uint32_t)plane invert:(BOOL)invert NS_AVAILABLE_IOS(4_0); // confirmed in iOS5.1 | |
32 | @end | |
33 | # endif //COREVIDEO_SUPPORTS_IOSURFACE | |
34 | #else | |
35 | #include <IOSurface/IOSurface.h> | |
36 | #endif //Q_OS_IOS | |
37 | ||
38 | #include "QtAV/VideoFrame.h" | |
39 | #include "opengl/OpenGLHelper.h" | |
40 | ||
41 | namespace QtAV { | |
42 | namespace cv { | |
43 | VideoFormat::PixelFormat format_from_cv(int cv); | |
44 | ||
45 | // https://www.opengl.org/registry/specs/APPLE/rgb_422.txt | |
46 | // https://www.opengl.org/registry/specs/APPLE/ycbcr_422.txt uyvy: UNSIGNED_SHORT_8_8_REV_APPLE, yuy2: GL_UNSIGNED_SHORT_8_8_APPLE | |
47 | // check extension GL_APPLE_rgb_422 and rectangle? | |
48 | class InteropResourceIOSurface Q_DECL_FINAL : public InteropResource | |
49 | { | |
50 | public: | |
51 | bool stridesForWidth(int cvfmt, int width, int* strides, VideoFormat::PixelFormat* outFmt) Q_DECL_OVERRIDE; | |
52 | bool mapToTexture2D() const Q_DECL_OVERRIDE { return false;} | |
53 | bool map(CVPixelBufferRef buf, GLuint *tex, int w, int h, int plane) Q_DECL_OVERRIDE; | |
54 | GLuint createTexture(CVPixelBufferRef, const VideoFormat &fmt, int plane, int planeWidth, int planeHeight) Q_DECL_OVERRIDE | |
55 | { | |
56 | Q_UNUSED(fmt); | |
57 | Q_UNUSED(plane); | |
58 | Q_UNUSED(planeWidth); | |
59 | Q_UNUSED(planeHeight); | |
60 | GLuint tex = 0; | |
61 | DYGL(glGenTextures(1, &tex)); | |
62 | return tex; | |
63 | } | |
64 | }; | |
65 | ||
66 | InteropResource* CreateInteropIOSurface() | |
67 | { | |
68 | return new InteropResourceIOSurface(); | |
69 | } | |
70 | ||
71 | bool InteropResourceIOSurface::stridesForWidth(int cvfmt, int width, int *strides, VideoFormat::PixelFormat* outFmt) | |
72 | { | |
73 | switch (cvfmt) { | |
74 | case '2vuy': | |
75 | case 'yuvs': { | |
76 | *outFmt = VideoFormat::Format_VYU; | |
77 | if (strides[0] <= 0) | |
78 | strides[0] = 4*width; //RGB layout: BRGX | |
79 | else | |
80 | strides[0] *= 2; | |
81 | } | |
82 | break; | |
83 | default: | |
84 | return InteropResource::stridesForWidth(cvfmt, width, strides, outFmt); | |
85 | } | |
86 | return true; | |
87 | } | |
88 | ||
89 | bool InteropResourceIOSurface::map(CVPixelBufferRef buf, GLuint *tex, int w, int h, int plane) | |
90 | { | |
91 | Q_UNUSED(w); | |
92 | Q_UNUSED(h); | |
93 | #if COREVIDEO_SUPPORTS_IOSURFACE // IOSurface header is not included otherwise | |
94 | const OSType pixfmt = CVPixelBufferGetPixelFormatType(buf); | |
95 | GLint iformat; | |
96 | GLenum format, dtype; | |
97 | getParametersGL(pixfmt, &iformat, &format, &dtype, plane); | |
98 | switch (pixfmt) { | |
99 | case '2vuy': | |
100 | case 'yuvs': | |
101 | // ES2 requires internal format and format are the same. desktop can use internal format GL_RGB or sized GL_RGB8 | |
102 | #ifdef Q_OS_IOS | |
103 | iformat = GL_RGB_422_APPLE; | |
104 | #else | |
105 | iformat = GL_RGB8; | |
106 | #endif | |
107 | format = GL_RGB_422_APPLE; | |
108 | dtype = pixfmt == '2vuy' ? GL_UNSIGNED_SHORT_8_8_APPLE : GL_UNSIGNED_SHORT_8_8_REV_APPLE; | |
109 | break; | |
110 | // macOS: GL_RGBA8, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV | |
111 | // GL_YCBCR_422_APPLE: convert to rgb texture internally (bt601). only supports OSX | |
112 | // GL_RGB_422_APPLE: raw yuv422 texture | |
113 | case 'BGRA': | |
114 | #ifdef Q_OS_IOS | |
115 | iformat = GL_RGBA; | |
116 | format = GL_RGBA; | |
117 | #else | |
118 | iformat = GL_RGBA8; | |
119 | format = GL_BGRA; | |
120 | dtype = GL_UNSIGNED_INT_8_8_8_8_REV; | |
121 | #endif | |
122 | break; | |
123 | default: | |
124 | break; | |
125 | } | |
126 | const GLenum target = GL_TEXTURE_RECTANGLE; | |
127 | DYGL(glBindTexture(target, *tex)); | |
128 | const int planeW = CVPixelBufferGetWidthOfPlane(buf, plane); | |
129 | const int planeH = CVPixelBufferGetHeightOfPlane(buf, plane); | |
130 | //qDebug("map plane%d. %dx%d, gl %d %d %d", plane, planeW, planeH, iformat, format, dtype); | |
131 | ||
132 | const IOSurfaceRef surface = CVPixelBufferGetIOSurface(buf); // available in ios11 sdk, ios4 runtime | |
133 | #ifdef Q_OS_IOS | |
134 | BOOL ok = false; | |
135 | # ifdef __IPHONE_11_0 | |
136 | # if __has_builtin(__builtin_available) // xcode9: runtime check @available is required, or -Wno-unguarded-availability-new | |
137 | if (@available(iOS 11, *)) //symbol is available in iOS5+, but crashes at runtime | |
138 | # else | |
139 | if (false) | |
140 | # endif // __has_builtin(__builtin_available) | |
141 | ok = [[EAGLContext currentContext] texImageIOSurface:surface target:target internalFormat:iformat width:planeW height:planeH format:format type:dtype plane:plane]; | |
142 | else // fallback to old private api if runtime version < 11 | |
143 | # endif //__IPHONE_11_0 | |
144 | { | |
145 | #if IOS_USE_PRIVATE | |
146 | ok = [[EAGLContext currentContext] texImageIOSurface:surface target:target internalFormat:iformat width:planeW height:planeH format:format type:dtype plane:plane invert:NO]; | |
147 | #endif //IOS_USE_PRIVATE | |
148 | } | |
149 | if (!ok) { | |
150 | qWarning("error creating IOSurface texture at plane %d", plane); | |
151 | } | |
152 | #else | |
153 | CGLError err = CGLTexImageIOSurface2D(CGLGetCurrentContext(), target, iformat, planeW, planeH, format, dtype, surface, plane); | |
154 | if (err != kCGLNoError) { | |
155 | qWarning("error creating IOSurface texture at plane %d: %s", plane, CGLErrorString(err)); | |
156 | } | |
157 | #endif // Q_OS_IOS | |
158 | DYGL(glBindTexture(target, 0)); | |
159 | #endif //COREVIDEO_SUPPORTS_IOSURFACE | |
160 | return true; | |
161 | } | |
162 | } // namespace cv | |
163 | } // namespace QtAV |
0 | 0 | /****************************************************************************** |
1 | 1 | QtAV: Multimedia framework based on Qt and FFmpeg |
2 | Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com> | |
2 | Copyright (C) 2012-2018 Wang Bin <wbsecg1@gmail.com> | |
3 | 3 | |
4 | 4 | * This file is part of QtAV |
5 | 5 | |
92 | 92 | static QStringList codecs; |
93 | 93 | if (!codecs.isEmpty()) |
94 | 94 | return codecs; |
95 | const AVCodec* c = NULL; | |
96 | #if AVCODEC_STATIC_REGISTER | |
97 | void* it = NULL; | |
98 | while ((c = av_codec_iterate(&it))) { | |
99 | #else | |
95 | 100 | avcodec_register_all(); |
96 | AVCodec* c = NULL; | |
97 | while ((c=av_codec_next(c))) { | |
101 | while ((c = av_codec_next(c))) { | |
102 | #endif | |
98 | 103 | if (!av_codec_is_decoder(c) || c->type != AVMEDIA_TYPE_VIDEO) |
99 | 104 | continue; |
100 | 105 | codecs.append(QString::fromLatin1(c->name)); |
0 | 0 | /****************************************************************************** |
1 | 1 | QtAV: Media play library based on Qt and FFmpeg |
2 | Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com> | |
2 | Copyright (C) 2012-2018 Wang Bin <wbsecg1@gmail.com> | |
3 | 3 | |
4 | 4 | * This file is part of QtAV (from 2016) |
5 | 5 | |
33 | 33 | |
34 | 34 | static bool check_ffmpeg_hevc_dxva2() |
35 | 35 | { |
36 | #if !AVCODEC_STATIC_REGISTER | |
36 | 37 | avcodec_register_all(); |
38 | #endif | |
37 | 39 | AVHWAccel *hwa = av_hwaccel_next(0); |
38 | 40 | while (hwa) { |
39 | 41 | if (strncmp("hevc_dxva2", hwa->name, 10) == 0) |
0 | 0 | /****************************************************************************** |
1 | 1 | QtAV: Multimedia framework based on Qt and FFmpeg |
2 | Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com> | |
2 | Copyright (C) 2012-2017 Wang Bin <wbsecg1@gmail.com> | |
3 | 3 | |
4 | 4 | * This file is part of QtAV (from 2013) |
5 | 5 | |
146 | 146 | .arg(patch>=100?QStringLiteral("FFmpeg"):QStringLiteral("Libav")) |
147 | 147 | .arg(QTAV_VERSION_MAJOR(avcodec_version())).arg(QTAV_VERSION_MINOR(avcodec_version())).arg(patch); |
148 | 148 | } |
149 | virtual VideoFrame frame() Q_DECL_OVERRIDE Q_DECL_FINAL; | |
150 | 149 | |
151 | 150 | // TODO: av_opt_set in setter |
152 | 151 | void setSkipLoopFilter(DiscardType value); |
282 | 281 | return VideoDecoderId_FFmpeg; |
283 | 282 | } |
284 | 283 | |
285 | VideoFrame VideoDecoderFFmpeg::frame() | |
286 | { | |
287 | DPTR_D(VideoDecoderFFmpeg); | |
288 | if (d.frame->width <= 0 || d.frame->height <= 0 || !d.codec_ctx) | |
289 | return VideoFrame(); | |
290 | // it's safe if width, height, pixfmt will not change, only data change | |
291 | VideoFrame frame(d.frame->width, d.frame->height, VideoFormat((int)d.codec_ctx->pix_fmt)); | |
292 | frame.setDisplayAspectRatio(d.getDAR(d.frame)); | |
293 | frame.setBits(d.frame->data); | |
294 | frame.setBytesPerLine(d.frame->linesize); | |
295 | // in s. TODO: what about AVFrame.pts? av_frame_get_best_effort_timestamp? move to VideoFrame::from(AVFrame*) | |
296 | frame.setTimestamp((double)d.frame->pkt_pts/1000.0); | |
297 | frame.setMetaData(QStringLiteral("avbuf"), QVariant::fromValue(AVFrameBuffersRef(new AVFrameBuffers(d.frame)))); | |
298 | d.updateColorDetails(&frame); | |
299 | if (frame.format().hasPalette()) { | |
300 | frame.setMetaData(QStringLiteral("pallete"), QByteArray((const char*)d.frame->data[1], 256*4)); | |
301 | } | |
302 | return frame; | |
303 | } | |
304 | ||
305 | 284 | void VideoDecoderFFmpeg::setSkipLoopFilter(DiscardType value) |
306 | 285 | { |
307 | 286 | DPTR_D(VideoDecoderFFmpeg); |
0 | 0 | /****************************************************************************** |
1 | 1 | QtAV: Multimedia framework based on Qt and FFmpeg |
2 | Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com> | |
2 | Copyright (C) 2012-2017 Wang Bin <wbsecg1@gmail.com> | |
3 | 3 | |
4 | 4 | * This file is part of QtAV (from 2014) |
5 | 5 | |
131 | 131 | //qDebug("pic_type=%c", av_get_picture_type_char(d.frame->pict_type)); |
132 | 132 | d.undecoded_size = qMin(packet.data.size() - ret, packet.data.size()); |
133 | 133 | if (ret < 0) { |
134 | qWarning("[VideoDecoderFFmpegBase] %s", av_err2str(ret)); | |
134 | //qWarning("[VideoDecoderFFmpegBase] %s", av_err2str(ret)); | |
135 | 135 | return false; |
136 | 136 | } |
137 | 137 | if (!got_frame_ptr) { |
147 | 147 | return true; |
148 | 148 | } |
149 | 149 | |
150 | VideoFrame VideoDecoderFFmpegBase::frame() | |
151 | { | |
152 | DPTR_D(VideoDecoderFFmpegBase); | |
153 | if (d.frame->width <= 0 || d.frame->height <= 0 || !d.codec_ctx) | |
154 | return VideoFrame(); | |
155 | // it's safe if width, height, pixfmt will not change, only data change | |
156 | VideoFrame frame(d.frame->width, d.frame->height, VideoFormat((int)d.codec_ctx->pix_fmt)); | |
157 | frame.setDisplayAspectRatio(d.getDAR(d.frame)); | |
158 | frame.setBits(d.frame->data); | |
159 | frame.setBytesPerLine(d.frame->linesize); | |
160 | // in s. TODO: what about AVFrame.pts? av_frame_get_best_effort_timestamp? move to VideoFrame::from(AVFrame*) | |
161 | frame.setTimestamp((double)d.frame->pkt_pts/1000.0); | |
162 | frame.setMetaData(QStringLiteral("avbuf"), QVariant::fromValue(AVFrameBuffersRef(new AVFrameBuffers(d.frame)))); | |
163 | d.updateColorDetails(&frame); | |
164 | if (frame.format().hasPalette()) { | |
165 | frame.setMetaData(QStringLiteral("pallete"), QByteArray((const char*)d.frame->data[1], 256*4)); | |
166 | } | |
167 | return frame; | |
168 | } | |
150 | 169 | } //namespace QtAV |
0 | 0 | /****************************************************************************** |
1 | 1 | QtAV: Multimedia framework based on Qt and FFmpeg |
2 | Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com> | |
2 | Copyright (C) 2012-2018 Wang Bin <wbsecg1@gmail.com> | |
3 | 3 | |
4 | 4 | * This file is part of QtAV (from 2014) |
5 | 5 | |
34 | 34 | DPTR_DECLARE_PRIVATE(VideoDecoderFFmpegBase) |
35 | 35 | public: |
36 | 36 | virtual bool decode(const Packet& packet) Q_DECL_OVERRIDE; |
37 | virtual VideoFrame frame() Q_DECL_OVERRIDE; | |
37 | 38 | protected: |
38 | 39 | VideoDecoderFFmpegBase(VideoDecoderFFmpegBasePrivate &d); |
39 | 40 | private: |
49 | 50 | , width(0) |
50 | 51 | , height(0) |
51 | 52 | { |
53 | #if !AVCODEC_STATIC_REGISTER | |
52 | 54 | avcodec_register_all(); |
55 | #endif | |
53 | 56 | frame = av_frame_alloc(); |
54 | 57 | } |
55 | 58 | virtual ~VideoDecoderFFmpegBasePrivate() { |
0 | 0 | /****************************************************************************** |
1 | 1 | QtAV: Multimedia framework based on Qt and FFmpeg |
2 | Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com> | |
2 | Copyright (C) 2012-2017 Wang Bin <wbsecg1@gmail.com> | |
3 | 3 | |
4 | 4 | * This file is part of QtAV (from 2013) |
5 | 5 | |
154 | 154 | |
155 | 155 | AVPixelFormat VideoDecoderFFmpegHWPrivate::getFormat(struct AVCodecContext *avctx, const AVPixelFormat *pi_fmt) |
156 | 156 | { |
157 | #ifdef AV_HWACCEL_FLAG_ALLOW_SOFTWARE | |
158 | avctx->hwaccel_flags |= AV_HWACCEL_FLAG_ALLOW_SOFTWARE; | |
159 | #endif | |
157 | 160 | bool can_hwaccel = false; |
158 | 161 | for (size_t i = 0; pi_fmt[i] != QTAV_PIX_FMT_C(NONE); i++) { |
159 | 162 | const AVPixFmtDescriptor *dsc = av_pix_fmt_desc_get(pi_fmt[i]); |
191 | 194 | end: |
192 | 195 | qWarning("hardware acceleration is not available" ); |
193 | 196 | /* Fallback to default behaviour */ |
197 | #if QTAV_HAVE(AVBUFREF) | |
198 | avctx->get_buffer2 = avcodec_default_get_buffer2; | |
199 | #endif | |
194 | 200 | return avcodec_default_get_format(avctx, pi_fmt); |
195 | 201 | } |
196 | 202 |
0 | 0 | /****************************************************************************** |
1 | 1 | QtAV: Multimedia framework based on Qt and FFmpeg |
2 | Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com> | |
2 | Copyright (C) 2012-2017 Wang Bin <wbsecg1@gmail.com> | |
3 | 3 | |
4 | 4 | * This file is part of QtAV (from 2013) |
5 | 5 | |
62 | 62 | codec_ctx->reget_buffer = reget_buffer; |
63 | 63 | #endif //QTAV_HAVE(AVBUFREF) |
64 | 64 | } |
65 | ||
66 | virtual bool open() Q_DECL_OVERRIDE { return prepare();} | |
67 | virtual void close() Q_DECL_OVERRIDE {restore();} | |
65 | 68 | // return hwaccel_context or null |
66 | 69 | virtual void* setup(AVCodecContext* avctx) = 0; |
67 | 70 |
0 | 0 | /****************************************************************************** |
1 | 1 | QtAV: Media play library based on Qt and FFmpeg |
2 | Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com> | |
2 | Copyright (C) 2012-2017 Wang Bin <wbsecg1@gmail.com> | |
3 | 3 | |
4 | 4 | * This file is part of QtAV (from 2016) |
5 | 5 | |
18 | 18 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
19 | 19 | ******************************************************************************/ |
20 | 20 | |
21 | #include "VideoDecoderFFmpegBase.h" | |
21 | #include "VideoDecoderFFmpegHW.h" | |
22 | #include "VideoDecoderFFmpegHW_p.h" | |
22 | 23 | #if FFMPEG_MODULE_CHECK(LIBAVCODEC, 57, 28, 100) |
23 | 24 | #define QTAV_HAVE_MEDIACODEC 1 |
24 | 25 | #endif |
28 | 29 | #include <QtAndroidExtras> |
29 | 30 | extern "C" { |
30 | 31 | #include <libavcodec/jni.h> |
32 | #include <libavcodec/mediacodec.h> | |
31 | 33 | } |
34 | ||
35 | // QtAV's fastest mediacodec decoding/rendering code is private because all other projects I know require java code or is opengl incompatible(chrome, firefox, xbmc, vlc etc.). Maybe my code is the best implementation. | |
36 | #ifdef MEDIACODEC_TEXTURE | |
37 | #include "QtAV/SurfaceInterop.h" | |
38 | #include "opengl/OpenGLHelper.h" | |
39 | #include "MediaCodecTextureStandalone.h" | |
40 | #endif | |
32 | 41 | |
33 | 42 | namespace QtAV { |
34 | 43 | class VideoDecoderMediaCodecPrivate; |
35 | class VideoDecoderMediaCodec : public VideoDecoderFFmpegBase | |
44 | class VideoDecoderMediaCodec Q_DECL_FINAL : public VideoDecoderFFmpegHW | |
36 | 45 | { |
37 | 46 | DPTR_DECLARE_PRIVATE(VideoDecoderMediaCodec) |
38 | 47 | public: |
39 | 48 | VideoDecoderMediaCodec(); |
49 | ||
40 | 50 | VideoDecoderId id() const Q_DECL_OVERRIDE; |
41 | 51 | QString description() const Q_DECL_OVERRIDE; |
42 | 52 | VideoFrame frame() Q_DECL_OVERRIDE; |
53 | ||
54 | void flush() Q_DECL_OVERRIDE { // workaroudn for EAGAIN error in avcodec_receive_frame | |
55 | if (copyMode() == ZeroCopy) { | |
56 | ||
57 | } else { | |
58 | VideoDecoderFFmpegHW::flush(); | |
59 | } | |
60 | } | |
43 | 61 | }; |
44 | 62 | |
45 | 63 | extern VideoDecoderId VideoDecoderId_MediaCodec; |
46 | 64 | FACTORY_REGISTER(VideoDecoder, MediaCodec, "MediaCodec") |
47 | 65 | |
48 | class VideoDecoderMediaCodecPrivate Q_DECL_FINAL : public VideoDecoderFFmpegBasePrivate | |
66 | class VideoDecoderMediaCodecPrivate Q_DECL_FINAL : public VideoDecoderFFmpegHWPrivate | |
49 | 67 | { |
50 | 68 | public: |
69 | ~VideoDecoderMediaCodecPrivate() { | |
70 | #ifdef MEDIACODEC_TEXTURE | |
71 | if (pool_) | |
72 | api_->texture_pool_release(&pool_); | |
73 | #endif | |
74 | } | |
75 | void close() Q_DECL_OVERRIDE { | |
76 | restore(); | |
77 | #ifdef MEDIACODEC_TEXTURE | |
78 | if (codec_ctx) | |
79 | av_mediacodec_default_free(codec_ctx); | |
80 | #endif | |
81 | } | |
82 | bool enableFrameRef() const Q_DECL_OVERRIDE { return true;} | |
83 | ||
84 | void* setup(AVCodecContext *avctx) Q_DECL_OVERRIDE { | |
85 | if (copy_mode != VideoDecoderFFmpegHW::ZeroCopy) | |
86 | return nullptr; | |
87 | #ifdef MEDIACODEC_TEXTURE | |
88 | api_->set_jvm(QAndroidJniEnvironment::javaVM()); | |
89 | if (!pool_) | |
90 | pool_ = api_->texture_pool_create(); | |
91 | av_mediacodec_default_free(avctx); | |
92 | AVMediaCodecContext *mc = av_mediacodec_alloc_context(); | |
93 | AV_ENSURE(av_mediacodec_default_init(avctx, mc, api_->texture_pool_ensure_surface(pool_)), nullptr); | |
94 | #endif | |
95 | return avctx->hwaccel_context; // set in av_mediacodec_default_init | |
96 | } | |
97 | ||
98 | bool getBuffer(void **, uint8_t **data) {return true;} | |
99 | void releaseBuffer(void *, uint8_t *) {} | |
100 | AVPixelFormat vaPixelFormat() const { | |
101 | if (copy_mode == VideoDecoderFFmpegHW::ZeroCopy) | |
102 | return AV_PIX_FMT_MEDIACODEC; | |
103 | return AV_PIX_FMT_NONE; | |
104 | } | |
105 | #ifdef MEDIACODEC_TEXTURE | |
106 | const MdkMediaCodecTextureAPI* api_ = mdk_mediacodec_get_api("qtav.nonfree.mediacodec"); | |
107 | MdkMediaCodecTextureAPI::TexturePool *pool_ = nullptr; | |
108 | #endif | |
51 | 109 | }; |
52 | 110 | |
53 | 111 | VideoDecoderMediaCodec::VideoDecoderMediaCodec() |
54 | : VideoDecoderFFmpegBase(*new VideoDecoderMediaCodecPrivate()) | |
112 | : VideoDecoderFFmpegHW(*new VideoDecoderMediaCodecPrivate()) | |
55 | 113 | { |
56 | setCodecName("h264_mediacodec"); | |
114 | setProperty("hwaccel", "mediacodec"); | |
115 | #ifdef MEDIACODEC_TEXTURE | |
116 | setProperty("copyMode", "ZeroCopy"); | |
117 | #endif | |
57 | 118 | av_jni_set_java_vm(QAndroidJniEnvironment::javaVM(), NULL); |
58 | 119 | } |
59 | 120 | |
67 | 128 | return QStringLiteral("MediaCodec"); |
68 | 129 | } |
69 | 130 | |
131 | static void av_mediacodec_render_buffer(void *buf) | |
132 | { | |
133 | av_mediacodec_release_buffer((AVMediaCodecBuffer*)buf, 1); | |
134 | } | |
135 | ||
136 | static void av_mediacodec_buffer_unref(void* buf) | |
137 | { | |
138 | av_buffer_unref((AVBufferRef**)&buf); | |
139 | } | |
140 | ||
70 | 141 | VideoFrame VideoDecoderMediaCodec::frame() |
71 | 142 | { |
72 | 143 | DPTR_D(VideoDecoderMediaCodec); |
73 | 144 | if (d.frame->width <= 0 || d.frame->height <= 0 || !d.codec_ctx) |
74 | 145 | return VideoFrame(); |
75 | 146 | // it's safe if width, height, pixfmt will not change, only data change |
76 | VideoFrame frame(d.frame->width, d.frame->height, VideoFormat((int)d.codec_ctx->pix_fmt)); | |
147 | if (copyMode() != ZeroCopy) { | |
148 | VideoFrame frame(d.frame->width, d.frame->height, VideoFormat((int)d.codec_ctx->pix_fmt)); | |
149 | frame.setDisplayAspectRatio(d.getDAR(d.frame)); | |
150 | frame.setBits(d.frame->data); | |
151 | frame.setBytesPerLine(d.frame->linesize); | |
152 | // in s. TODO: what about AVFrame.pts? av_frame_get_best_effort_timestamp? move to VideoFrame::from(AVFrame*) | |
153 | frame.setTimestamp((double)d.frame->pkt_pts/1000.0); | |
154 | frame.setMetaData(QStringLiteral("avbuf"), QVariant::fromValue(AVFrameBuffersRef(new AVFrameBuffers(d.frame)))); | |
155 | d.updateColorDetails(&frame); | |
156 | return frame; | |
157 | } | |
158 | // print width height | |
159 | VideoFrame frame(d.frame->width, d.frame->height, VideoFormat::Format_RGB32); | |
160 | frame.setBytesPerLine(d.frame->width*4); | |
77 | 161 | frame.setDisplayAspectRatio(d.getDAR(d.frame)); |
78 | frame.setBits(d.frame->data); | |
79 | frame.setBytesPerLine(d.frame->linesize); | |
80 | // in s. TODO: what about AVFrame.pts? av_frame_get_best_effort_timestamp? move to VideoFrame::from(AVFrame*) | |
81 | frame.setTimestamp((double)d.frame->pkt_pts/1000.0); | |
82 | frame.setMetaData(QStringLiteral("avbuf"), QVariant::fromValue(AVFrameBuffersRef(new AVFrameBuffers(d.frame)))); | |
83 | d.updateColorDetails(&frame); | |
162 | frame.setTimestamp(d.frame->pkt_pts/1000.0); | |
163 | #ifdef MEDIACODEC_TEXTURE | |
164 | class MediaCodecTextureInterop : public VideoSurfaceInterop | |
165 | { | |
166 | const MdkMediaCodecTextureAPI* api_ = nullptr; | |
167 | MdkMediaCodecTextureAPI::Texture *tex_ = nullptr; | |
168 | public: | |
169 | MediaCodecTextureInterop(const MdkMediaCodecTextureAPI *api, MdkMediaCodecTextureAPI::Texture *mt) : api_(api), tex_(mt) {} | |
170 | ~MediaCodecTextureInterop() { | |
171 | api_->texture_release(&tex_); | |
172 | } | |
173 | ||
174 | void* map(SurfaceType, const VideoFormat &, void *handle, int plane) { | |
175 | Q_UNUSED(plane); | |
176 | GLuint* t = reinterpret_cast<GLuint*>(handle); | |
177 | *t = api_->texture_to_gl(tex_, nullptr, nullptr); | |
178 | return t; | |
179 | } | |
180 | }; | |
181 | assert(d.frame->buf[0] && d.frame->data[3] && "No AVMediaCodecBuffer or ref in AVFrame"); | |
182 | AVBufferRef* bufref = av_buffer_ref(d.frame->buf[0]); | |
183 | AVMediaCodecBuffer *mcbuf = (AVMediaCodecBuffer*)d.frame->data[3]; | |
184 | MdkMediaCodecTextureAPI::Texture* mt = d.api_->texture_pool_feed_avbuffer(d.pool_, d.frame->width, d.frame->height, av_mediacodec_buffer_unref, bufref, av_mediacodec_render_buffer, mcbuf); | |
185 | ||
186 | MediaCodecTextureInterop *interop = new MediaCodecTextureInterop(d.api_, mt); | |
187 | frame.setMetaData(QStringLiteral("surface_interop"), QVariant::fromValue(VideoSurfaceInteropPtr((interop)))); | |
188 | #endif | |
84 | 189 | return frame; |
85 | 190 | } |
86 | 191 | } //namespace QtAV |
0 | 0 | /****************************************************************************** |
1 | 1 | QtAV: Multimedia framework based on Qt and FFmpeg |
2 | Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com> | |
2 | Copyright (C) 2012-2017 Wang Bin <wbsecg1@gmail.com> | |
3 | 3 | |
4 | 4 | * This file is part of QtAV (from 2015) |
5 | 5 | |
147 | 147 | VideoDecoderVideoToolbox::VideoDecoderVideoToolbox() |
148 | 148 | : VideoDecoderFFmpegHW(*new VideoDecoderVideoToolboxPrivate()) |
149 | 149 | { |
150 | #if 1//!AV_MODULE_CHECK(LIBAVCODEC, 57, 30, 1, 89, 100) // ffmpeg3.3 | |
150 | 151 | setProperty("threads", 1); // to avoid crash at av_videotoolbox_alloc_context/av_videotoolbox_default_free. I have no idea how the are called |
152 | #endif | |
151 | 153 | // dynamic properties about static property details. used by UI |
152 | 154 | setProperty("detail_format", tr("Output pixel format from decoder. Performance NV12 > UYVY > BGRA > YUV420P > YUYV.\nOSX < 10.7 only supports UYVY, BGRA and YUV420p")); |
153 | 155 | setProperty("detail_interop" |
173 | 175 | VideoFrame VideoDecoderVideoToolbox::frame() |
174 | 176 | { |
175 | 177 | DPTR_D(VideoDecoderVideoToolbox); |
178 | if (!d.codec_ctx->hwaccel_context) { | |
179 | return VideoDecoderFFmpegBase::frame(); | |
180 | } | |
176 | 181 | CVPixelBufferRef cv_buffer = (CVPixelBufferRef)d.frame->data[3]; |
177 | 182 | if (!cv_buffer) { |
178 | 183 | qDebug("Frame buffer is empty."); |
285 | 290 | |
286 | 291 | bool VideoDecoderVideoToolboxPrivate::getBuffer(void **opaque, uint8_t **data) |
287 | 292 | { |
293 | qDebug("vt getbuffer"); | |
288 | 294 | *data = (uint8_t *)1; // dummy. it's AVFrame.data[0], must be non null required by ffmpeg |
289 | 295 | Q_UNUSED(opaque); |
290 | 296 | return true; |
313 | 319 | break; |
314 | 320 | } |
315 | 321 | switch (codec_ctx->codec_id) { |
322 | case AV_CODEC_ID_HEVC: | |
316 | 323 | case AV_CODEC_ID_H264: |
317 | 324 | case AV_CODEC_ID_H263: |
318 | 325 | case AV_CODEC_ID_MPEG4: |
0 | 0 | /****************************************************************************** |
1 | 1 | QtAV: Multimedia framework based on Qt and FFmpeg |
2 | Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com> | |
2 | Copyright (C) 2012-2018 Wang Bin <wbsecg1@gmail.com> | |
3 | 3 | |
4 | 4 | * This file is part of QtAV (from 2015) |
5 | 5 | |
45 | 45 | static QStringList codecs; |
46 | 46 | if (!codecs.isEmpty()) |
47 | 47 | return codecs; |
48 | const AVCodec* c = NULL; | |
49 | #if AVCODEC_STATIC_REGISTER | |
50 | void* it = NULL; | |
51 | while ((c = av_codec_iterate(&it))) { | |
52 | #else | |
48 | 53 | avcodec_register_all(); |
49 | AVCodec* c = NULL; | |
50 | while ((c=av_codec_next(c))) { | |
54 | while ((c = av_codec_next(c))) { | |
55 | #endif | |
51 | 56 | if (!av_codec_is_encoder(c) || c->type != AVMEDIA_TYPE_VIDEO) |
52 | 57 | continue; |
53 | 58 | codecs.append(QString::fromLatin1(c->name)); |
0 | 0 | /****************************************************************************** |
1 | 1 | QtAV: Multimedia framework based on Qt and FFmpeg |
2 | Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com> | |
2 | Copyright (C) 2012-2018 Wang Bin <wbsecg1@gmail.com> | |
3 | 3 | |
4 | 4 | * This file is part of QtAV (from 2015) |
5 | 5 | |
89 | 89 | : VideoEncoderPrivate() |
90 | 90 | , nb_encoded(0) |
91 | 91 | { |
92 | #if !AVCODEC_STATIC_REGISTER | |
92 | 93 | avcodec_register_all(); |
94 | #endif | |
93 | 95 | // NULL: codec-specific defaults won't be initialized, which may result in suboptimal default settings (this is important mainly for encoders, e.g. libx264). |
94 | 96 | avctx = avcodec_alloc_context3(NULL); |
95 | 97 | } |
244 | 246 | applyOptionsForContext(); |
245 | 247 | AV_ENSURE_OK(avcodec_open2(avctx, codec, &dict), false); |
246 | 248 | // from mpv ao_lavc |
247 | const int buffer_size = qMax<int>(qMax<int>(width*height*6+200, FF_MIN_BUFFER_SIZE), sizeof(AVPicture));//?? | |
249 | const int buffer_size = qMax<int>(qMax<int>(width*height*6+200, AV_INPUT_BUFFER_MIN_SIZE), sizeof(AVPicture));//?? | |
248 | 250 | buffer.resize(buffer_size); |
249 | 251 | return true; |
250 | 252 | } |
0 | 0 | /****************************************************************************** |
1 | 1 | QtAV: Multimedia framework based on Qt and FFmpeg |
2 | Copyright (C) 2012-2017 Wang Bin <wbsecg1@gmail.com> | |
2 | Copyright (C) 2012-2018 Wang Bin <wbsecg1@gmail.com> | |
3 | 3 | |
4 | 4 | * This file is part of QtAV |
5 | 5 | |
109 | 109 | { 0x37, 192}, // Kepler Generation (SM 3.7) GK21x class |
110 | 110 | { 0x50, 128}, // Maxwell Generation (SM 5.0) GM10x class |
111 | 111 | { 0x52, 128}, // Maxwell Generation (SM 5.2) GM20x class //enc: h264 yuv444p, hevc (libavcodec/nvenc.c) |
112 | { 0x53, 128}, // Maxwell Generation (SM 5.3) GM20x class | |
113 | { 0x60, 64 }, // Pascal Generation (SM 6.0) GP100 class | |
114 | { 0x61, 128}, // Pascal Generation (SM 6.1) GP10x class | |
115 | { 0x62, 128}, // Pascal Generation (SM 6.2) GP10x class | |
116 | { 0x70, 64 }, // Volta Generation (SM 7.0) GV100 class | |
112 | 117 | { -1, -1 } |
113 | 118 | }; |
114 | 119 | int index = 0; |
119 | 124 | ++index; |
120 | 125 | } |
121 | 126 | // If we don't find the values, we default use the previous one to run properly |
122 | printf("MapSMtoCores for SM %d.%d is undefined. Default to use %d Cores/SM\n", major, minor, nGpuArchCoresPerSM[7].Cores); | |
127 | printf("MapSMtoCores for SM %d.%d is undefined. Default to use %d Cores/SM\n", major, minor, nGpuArchCoresPerSM[index-1].Cores); | |
123 | 128 | return nGpuArchCoresPerSM[index - 1].Cores; |
124 | 129 | } |
125 | 130 |
0 | /****************************************************************************** | |
1 | QtAV: Multimedia framework based on Qt and FFmpeg | |
2 | Copyright (C) 2012-2017 Wang Bin <wbsecg1@gmail.com> | |
3 | ||
4 | * This file is part of QtAV (from 2017) | |
5 | ||
6 | This library is free software; you can redistribute it and/or | |
7 | modify it under the terms of the GNU Lesser General Public | |
8 | License as published by the Free Software Foundation; either | |
9 | version 2.1 of the License, or (at your option) any later version. | |
10 | ||
11 | This library is distributed in the hope that it will be useful, | |
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
14 | Lesser General Public License for more details. | |
15 | ||
16 | You should have received a copy of the GNU Lesser General Public | |
17 | License along with this library; if not, write to the Free Software | |
18 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
19 | ******************************************************************************/ | |
20 | ||
21 | #include "DXVAHDVP.h" | |
22 | #define DX_LOG_COMPONENT "D3D9VP" | |
23 | #include "utils/DirectXHelper.h" | |
24 | #include "utils/Logger.h" | |
25 | #include "directx/DXVAHDVP.h" | |
26 | ||
27 | namespace QtAV { | |
28 | namespace dx { | |
29 | ||
30 | DXVAHDVP::DXVAHDVP(ComPtr<IDirect3DDevice9> dev) | |
31 | : m_dev(NULL) | |
32 | , m_w(0) | |
33 | , m_h(0) | |
34 | , m_cs(ColorSpace_BT709) | |
35 | , m_range(ColorRange_Limited) | |
36 | , fDXVAHD_CreateDevice(NULL) | |
37 | { | |
38 | DX_ENSURE(dev.As(&m_dev)); | |
39 | fDXVAHD_CreateDevice = (PDXVAHD_CreateDevice)GetProcAddress(GetModuleHandle(TEXT("dxva2.dll")), "DXVAHD_CreateDevice"); | |
40 | } | |
41 | ||
42 | void DXVAHDVP::setOutput(IDirect3DSurface9 *surface) | |
43 | { | |
44 | m_out = surface; | |
45 | } | |
46 | ||
47 | void DXVAHDVP::setSourceRect(const QRect &r) | |
48 | { | |
49 | m_srcRect = r; | |
50 | } | |
51 | ||
52 | void DXVAHDVP::setColorSpace(ColorSpace value) | |
53 | { | |
54 | m_cs = value; | |
55 | } | |
56 | ||
57 | void DXVAHDVP::setColorRange(ColorRange value) | |
58 | { | |
59 | m_range = value; | |
60 | } | |
61 | ||
62 | bool DXVAHDVP::process(IDirect3DSurface9 *surface) | |
63 | { | |
64 | if (!surface || !m_out) | |
65 | return false; | |
66 | D3DSURFACE_DESC desc; | |
67 | surface->GetDesc(&desc); | |
68 | if (!ensureResource(desc.Width, desc.Height, desc.Format)) | |
69 | return false; | |
70 | ||
71 | if (!m_srcRect.isEmpty()) { | |
72 | const RECT sr = {m_srcRect.x(), m_srcRect.y(), m_srcRect.width(), m_srcRect.height()}; | |
73 | DXVAHD_STREAM_STATE_SOURCE_RECT_DATA r; | |
74 | r.Enable = 1; | |
75 | r.SourceRect = sr; | |
76 | DX_ENSURE(m_vp->SetVideoProcessStreamState(0/*stream index*/, DXVAHD_STREAM_STATE_SOURCE_RECT, sizeof(r), &r), false); | |
77 | } | |
78 | #if 0 | |
79 | DXVAHD_STREAM_STATE_D3DFORMAT_DATA d3df = {m_fmt}; | |
80 | DX_ENSURE(m_vp->SetVideoProcessStreamState(0/*stream index*/, DXVAHD_STREAM_STATE_D3DFORMAT, sizeof(d3df), &d3df), false); | |
81 | DXVAHD_STREAM_STATE_FRAME_FORMAT_DATA ff = {DXVAHD_FRAME_FORMAT_PROGRESSIVE}; | |
82 | DX_ENSURE(m_vp->SetVideoProcessStreamState(0/*stream index*/, DXVAHD_STREAM_STATE_FRAME_FORMAT, sizeof(ff), &ff), false); | |
83 | DXVAHD_STREAM_STATE_DESTINATION_RECT_DATA dstr = { true, {0, 0, desc.Width, desc.Height} }; | |
84 | DX_ENSURE(m_vp->SetVideoProcessStreamState(0/*stream index*/, DXVAHD_STREAM_STATE_DESTINATION_RECT, sizeof(dstr), &dstr), false); | |
85 | DXVAHD_BLT_STATE_TARGET_RECT_DATA tgtr = {true, {0, 0, desc.Width, desc.Height} }; | |
86 | DX_ENSURE(m_vp->SetVideoProcessBltState(DXVAHD_BLT_STATE_TARGET_RECT, sizeof(tgtr), &tgtr), false); | |
87 | for (int i = 0; i < 7; ++i) { | |
88 | DXVAHD_STREAM_STATE_FILTER_DATA flt = {false, DXVAHD_FILTER(i)}; | |
89 | DXVAHD_STREAM_STATE state = static_cast<DXVAHD_STREAM_STATE>(DXVAHD_STREAM_STATE_FILTER_BRIGHTNESS + i); | |
90 | DX_ENSURE(m_vp->SetVideoProcessStreamState(0/*stream index*/, state, sizeof(flt), &flt), false); | |
91 | } | |
92 | #endif | |
93 | ||
94 | DXVAHD_STREAM_STATE_OUTPUT_RATE_DATA rate; | |
95 | ZeroMemory(&rate, sizeof(rate)); | |
96 | rate.OutputRate = DXVAHD_OUTPUT_RATE_NORMAL; | |
97 | DX_ENSURE(m_vp->SetVideoProcessStreamState(0/*stream index*/, DXVAHD_STREAM_STATE_OUTPUT_RATE, sizeof(rate), &rate), false); | |
98 | ||
99 | DXVAHD_STREAM_STATE_INPUT_COLOR_SPACE_DATA ics; | |
100 | ZeroMemory(&ics, sizeof(ics)); | |
101 | ics.Type = 0; // 0: video, 1: graphics | |
102 | ics.RGB_Range = 0; // full | |
103 | ics.YCbCr_Matrix = m_cs == ColorSpace_BT601 ? 0 : 1; //0: bt601, 1: bt709 | |
104 | ics.YCbCr_xvYCC = m_range == ColorRange_Full ? 1 : 0; | |
105 | DX_ENSURE(m_vp->SetVideoProcessStreamState(0/*stream index*/, DXVAHD_STREAM_STATE_INPUT_COLOR_SPACE, sizeof(ics), &ics), false); | |
106 | ||
107 | DXVAHD_BLT_STATE_OUTPUT_COLOR_SPACE_DATA cs; | |
108 | ZeroMemory(&cs, sizeof(cs)); | |
109 | cs.Usage = 0; // output usage. 0: for playback (default), 1: video processing (e.g. video editor) | |
110 | cs.RGB_Range = 1; // | |
111 | cs.YCbCr_Matrix = 1; //0: bt601, 1: bt709 | |
112 | cs.YCbCr_xvYCC = 1; | |
113 | DX_ENSURE(m_vp->SetVideoProcessBltState(DXVAHD_BLT_STATE_OUTPUT_COLOR_SPACE, sizeof(cs), &cs), false); | |
114 | ||
115 | DXVAHD_STREAM_DATA stream; | |
116 | ZeroMemory(&stream, sizeof(stream)); | |
117 | stream.Enable = true; | |
118 | stream.OutputIndex = 0; | |
119 | stream.InputFrameOrField = 0; | |
120 | stream.pInputSurface = surface; | |
121 | DX_ENSURE(m_vp->VideoProcessBltHD(m_out.Get(), 0, 1, &stream), false); | |
122 | return true; | |
123 | } | |
124 | ||
125 | bool DXVAHDVP::ensureResource(UINT width, UINT height, D3DFORMAT format) | |
126 | { | |
127 | const bool dirty = width != m_w || height != m_h || m_fmt != format; | |
128 | if (dirty || !m_viddev) { | |
129 | if (!fDXVAHD_CreateDevice) | |
130 | return false; | |
131 | DXVAHD_RATIONAL fps = {0, 0}; | |
132 | DXVAHD_CONTENT_DESC desc; | |
133 | desc.InputFrameFormat = DXVAHD_FRAME_FORMAT_PROGRESSIVE; | |
134 | desc.InputFrameRate = fps; | |
135 | desc.InputWidth = width; | |
136 | desc.InputHeight = height; | |
137 | desc.OutputFrameRate = fps; | |
138 | desc.OutputWidth = width; | |
139 | desc.OutputHeight = height; | |
140 | DX_ENSURE(fDXVAHD_CreateDevice(m_dev.Get(), &desc, DXVAHD_DEVICE_USAGE_PLAYBACK_NORMAL, NULL, &m_viddev), false); | |
141 | } | |
142 | ||
143 | // TODO: check when format is changed, or record supported formats | |
144 | DXVAHD_VPDEVCAPS caps; | |
145 | DX_ENSURE(m_viddev->GetVideoProcessorDeviceCaps(&caps), false); | |
146 | QScopedPointer<DXVAHD_VPCAPS, QScopedPointerArrayDeleter<DXVAHD_VPCAPS>> pVPCaps(new (std::nothrow) DXVAHD_VPCAPS[caps.VideoProcessorCount]); | |
147 | DX_ENSURE(m_viddev->GetVideoProcessorCaps(caps.VideoProcessorCount, pVPCaps.data()), false); | |
148 | QScopedPointer<D3DFORMAT, QScopedPointerArrayDeleter<D3DFORMAT>> fmts(new (std::nothrow) D3DFORMAT[caps.InputFormatCount]); | |
149 | DX_ENSURE(m_viddev->GetVideoProcessorInputFormats(caps.InputFormatCount, fmts.data()), false); | |
150 | bool fmt_found = false; | |
151 | for (UINT i = 0; i < caps.InputFormatCount; ++i) { | |
152 | if (fmts.data()[i] == format) { | |
153 | fmt_found = true; | |
154 | break; | |
155 | } | |
156 | } | |
157 | if (!fmt_found) { | |
158 | qDebug("input format is not supported by DXVAHD"); | |
159 | return false; | |
160 | } | |
161 | if (dirty || !m_vp) | |
162 | DX_ENSURE(m_viddev->CreateVideoProcessor(&pVPCaps.data()[0].VPGuid, &m_vp), false); | |
163 | m_w = width; | |
164 | m_h = height; | |
165 | m_fmt = format; | |
166 | return true; | |
167 | } | |
168 | } //namespace dx | |
169 | } //namespace QtAV |
0 | /****************************************************************************** | |
1 | QtAV: Multimedia framework based on Qt and FFmpeg | |
2 | Copyright (C) 2012-2017 Wang Bin <wbsecg1@gmail.com> | |
3 | ||
4 | * This file is part of QtAV (from 2017) | |
5 | ||
6 | This library is free software; you can redistribute it and/or | |
7 | modify it under the terms of the GNU Lesser General Public | |
8 | License as published by the Free Software Foundation; either | |
9 | version 2.1 of the License, or (at your option) any later version. | |
10 | ||
11 | This library is distributed in the hope that it will be useful, | |
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
14 | Lesser General Public License for more details. | |
15 | ||
16 | You should have received a copy of the GNU Lesser General Public | |
17 | License along with this library; if not, write to the Free Software | |
18 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
19 | ******************************************************************************/ | |
20 | ||
21 | #ifndef QTAV_D3D9VPP_H | |
22 | #define QTAV_D3D9VPP_H | |
23 | #include <QtCore/QRect> | |
24 | #include "directx/dxcompat.h" | |
25 | #include <d3d9.h> | |
26 | #include <dxvahd.h> | |
27 | #include <wrl/client.h> | |
28 | #include <QtAV/QtAV_Global.h> | |
29 | using namespace Microsoft::WRL; | |
30 | // https://msdn.microsoft.com/en-us/library/windows/desktop/ee663581(v=vs.85).aspx | |
31 | ||
32 | namespace QtAV { | |
33 | namespace dx { | |
34 | ||
35 | class DXVAHDVP | |
36 | { | |
37 | public: | |
38 | // brightness, contrast, hue, saturation, rotation, source/dest rect | |
39 | DXVAHDVP(ComPtr<IDirect3DDevice9> dev); | |
40 | void setOutput(IDirect3DSurface9* surface); | |
41 | void setSourceRect(const QRect& r); | |
42 | // input color space and range | |
43 | void setColorSpace(ColorSpace value); | |
44 | void setColorRange(ColorRange value); | |
45 | bool process(IDirect3DSurface9 *surface); | |
46 | private: | |
47 | bool ensureResource(UINT width, UINT height, D3DFORMAT format); | |
48 | ||
49 | ComPtr<IDirect3DDevice9Ex> m_dev; | |
50 | ComPtr<IDXVAHD_Device> m_viddev; | |
51 | ComPtr<IDXVAHD_VideoProcessor> m_vp; | |
52 | ComPtr<IDirect3DSurface9> m_out; | |
53 | UINT m_w, m_h; //enumerator | |
54 | ColorSpace m_cs; | |
55 | ColorRange m_range; | |
56 | QRect m_srcRect; | |
57 | PDXVAHD_CreateDevice fDXVAHD_CreateDevice; | |
58 | D3DFORMAT m_fmt; | |
59 | }; | |
60 | } //namespace dx | |
61 | } //namespace QtAV | |
62 | #endif //QTAV_D3D9VPP_H |
50 | 50 | EGLInteropResource() |
51 | 51 | : egl(new EGL()) |
52 | 52 | , vp(0) |
53 | , boundTex(0) | |
53 | 54 | {} |
54 | 55 | ~EGLInteropResource(); |
55 | 56 | VideoFormat::PixelFormat format(DXGI_FORMAT) const Q_DECL_OVERRIDE { return VideoFormat::Format_RGB32;} |
62 | 63 | EGL* egl; |
63 | 64 | dx::D3D11VP *vp; |
64 | 65 | ComPtr<ID3D11Texture2D> d3dtex; |
66 | GLuint boundTex; | |
65 | 67 | }; |
66 | 68 | |
67 | 69 | InteropResource* CreateInteropEGL() { return new EGLInteropResource();} |
163 | 165 | vp->setSourceRect(QRect(0, 0, w, h)); |
164 | 166 | if (!vp->process(surface.Get(), index)) |
165 | 167 | return false; |
168 | if (boundTex == tex) | |
169 | return true; | |
166 | 170 | DYGL(glBindTexture(GL_TEXTURE_2D, tex)); |
167 | eglBindTexImage(egl->dpy, egl->surface, EGL_BACK_BUFFER); | |
171 | if (boundTex) | |
172 | EGL_WARN(eglReleaseTexImage(egl->dpy, egl->surface, EGL_BACK_BUFFER)); | |
173 | EGL_WARN(eglBindTexImage(egl->dpy, egl->surface, EGL_BACK_BUFFER)); | |
168 | 174 | DYGL(glBindTexture(GL_TEXTURE_2D, 0)); |
175 | boundTex = tex; | |
169 | 176 | return true; |
170 | 177 | } |
171 | 178 | } //namespace d3d11 |
33 | 33 | class AudioEncodeFilterPrivate Q_DECL_FINAL : public AudioFilterPrivate |
34 | 34 | { |
35 | 35 | public: |
36 | AudioEncodeFilterPrivate() : enc(0), start_time(0), async(false), finishing(0) {} | |
36 | AudioEncodeFilterPrivate() : enc(0), start_time(0), async(false), finishing(0), leftOverAudio() {} | |
37 | 37 | ~AudioEncodeFilterPrivate() { |
38 | 38 | if (enc) { |
39 | 39 | enc->close(); |
46 | 46 | bool async; |
47 | 47 | QAtomicInt finishing; |
48 | 48 | QThread enc_thread; |
49 | AudioFrame leftOverAudio; | |
49 | 50 | }; |
50 | 51 | |
51 | 52 | AudioEncodeFilter::AudioEncodeFilter(QObject *parent) |
52 | 53 | : AudioFilter(*new AudioEncodeFilterPrivate(), parent) |
53 | 54 | { |
55 | #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) | |
54 | 56 | connect(this, SIGNAL(requestToEncode(QtAV::AudioFrame)), this, SLOT(encode(QtAV::AudioFrame))); |
57 | #else | |
58 | connect(this, &AudioEncodeFilter::requestToEncode, this, &AudioEncodeFilter::encode); | |
59 | #endif | |
55 | 60 | connect(this, SIGNAL(finished()), &d_func().enc_thread, SLOT(quit())); |
56 | 61 | } |
57 | 62 | |
169 | 174 | AudioFrame f(frame); |
170 | 175 | if (f.format() != d.enc->audioFormat()) |
171 | 176 | f = f.to(d.enc->audioFormat()); |
172 | if (!d.enc->encode(f)) { | |
173 | if (f.timestamp() == std::numeric_limits<qreal>::max()) { | |
174 | Q_EMIT finished(); | |
175 | d.finishing = 0; | |
176 | } | |
177 | return; | |
178 | } | |
179 | if (!d.enc->encoded().isValid()) | |
180 | return; | |
181 | Q_EMIT frameEncoded(d.enc->encoded()); | |
177 | ||
178 | if (d.leftOverAudio.isValid()) { | |
179 | f.prepend(d.leftOverAudio); | |
180 | d.leftOverAudio = AudioFrame(); | |
181 | } | |
182 | ||
183 | int frameSizeEncoder = d.enc->frameSize() ? d.enc->frameSize() : f.samplesPerChannel(); | |
184 | int frameSize = f.samplesPerChannel(); | |
185 | ||
186 | QList<AudioFrame> audioFrames; | |
187 | for (int i = 0; i < frameSize; i += frameSizeEncoder) { | |
188 | if (frameSize - i >= frameSizeEncoder) { | |
189 | audioFrames.append(f.mid(i, frameSizeEncoder)); | |
190 | } else { | |
191 | d.leftOverAudio = f.mid(i); | |
192 | } | |
193 | } | |
194 | ||
195 | for (int i = 0; i < audioFrames.length(); i++) { | |
196 | if (!d.enc->encode(audioFrames.at(i))) { | |
197 | if (f.timestamp() == std::numeric_limits<qreal>::max()) { | |
198 | Q_EMIT finished(); | |
199 | d.finishing = 0; | |
200 | } | |
201 | return; | |
202 | } | |
203 | if (!d.enc->encoded().isValid()) | |
204 | return; | |
205 | Q_EMIT frameEncoded(d.enc->encoded()); | |
206 | } | |
182 | 207 | } |
183 | 208 | |
184 | 209 | |
203 | 228 | VideoEncodeFilter::VideoEncodeFilter(QObject *parent) |
204 | 229 | : VideoFilter(*new VideoEncodeFilterPrivate(), parent) |
205 | 230 | { |
206 | connect(this, SIGNAL(requestToEncode(QtAV::VideoFrame)), this, SLOT(encode(QtAV::VideoFrame))); | |
231 | #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) | |
232 | connect(this, SIGNAL(requestToEncode(QtAV::VideoFrame)), this, SLOT(encode(QtAV::VideoFrame))); | |
233 | #else | |
234 | connect(this, &VideoEncodeFilter::requestToEncode, this, &VideoEncodeFilter::encode); | |
235 | #endif | |
207 | 236 | connect(this, SIGNAL(finished()), &d_func().enc_thread, SLOT(quit())); |
208 | 237 | } |
209 | 238 | |
291 | 320 | return; |
292 | 321 | // encode delayed frames can pass an invalid frame |
293 | 322 | if (!d.enc->isOpen() && frame.isValid()) { |
294 | d.enc->setWidth(frame.width()); | |
295 | d.enc->setHeight(frame.height()); | |
323 | if (d.enc->width() == 0) { | |
324 | d.enc->setWidth(frame.width()); | |
325 | } | |
326 | if (d.enc->height() == 0) { | |
327 | d.enc->setHeight(frame.height()); | |
328 | } | |
296 | 329 | if (!d.enc->open()) { // TODO: error() |
297 | 330 | qWarning("Failed to open video encoder"); |
298 | 331 | return; |
309 | 342 | d.finishing = 0; |
310 | 343 | return; |
311 | 344 | } |
312 | if (d.enc->width() != frame.width() || d.enc->height() != frame.height()) { | |
313 | qWarning("Frame size (%dx%d) and video encoder size (%dx%d) mismatch! Close encoder please.", d.enc->width(), d.enc->height(), frame.width(), frame.height()); | |
314 | return; | |
315 | } | |
316 | 345 | if (frame.timestamp()*1000.0 < startTime()) |
317 | 346 | return; |
318 | 347 | // TODO: async |
319 | 348 | VideoFrame f(frame); |
320 | if (f.pixelFormat() != d.enc->pixelFormat()) | |
321 | f = f.to(d.enc->pixelFormat()); | |
349 | if (f.pixelFormat() != d.enc->pixelFormat() || d.enc->width() != f.width() || d.enc->height() != f.height()) | |
350 | f = f.to(d.enc->pixelFormat(), QSize(d.enc->width(), d.enc->height())); | |
322 | 351 | if (!d.enc->encode(f)) { |
323 | 352 | if (f.timestamp() == std::numeric_limits<qreal>::max()) { |
324 | 353 | Q_EMIT finished(); |
121 | 121 | { |
122 | 122 | DPTR_D(FilterManager); |
123 | 123 | QList<Filter*>& fs = d.afilter_player_map[player]; |
124 | bool ret = false; | |
124 | 125 | if (fs.removeAll(filter) > 0) { |
125 | if (fs.isEmpty()) | |
126 | d.afilter_player_map.remove(player); | |
127 | return true; | |
128 | } | |
129 | return false; | |
126 | ret = true; | |
127 | } | |
128 | if (fs.isEmpty()) | |
129 | d.afilter_player_map.remove(player); | |
130 | return ret; | |
130 | 131 | } |
131 | 132 | |
132 | 133 | bool FilterManager::unregisterVideoFilter(Filter *filter, AVPlayer *player) |
133 | 134 | { |
134 | 135 | DPTR_D(FilterManager); |
135 | 136 | QList<Filter*>& fs = d.vfilter_player_map[player]; |
137 | bool ret = false; | |
136 | 138 | if (fs.removeAll(filter) > 0) { |
137 | if (fs.isEmpty()) | |
138 | d.vfilter_player_map.remove(player); | |
139 | return true; | |
140 | } | |
141 | return false; | |
139 | ret = true; | |
140 | } | |
141 | if (fs.isEmpty()) | |
142 | d.vfilter_player_map.remove(player); | |
143 | return ret; | |
142 | 144 | } |
143 | 145 | |
144 | 146 | bool FilterManager::unregisterFilter(Filter *filter, AVOutput *output) |
145 | 147 | { |
146 | 148 | DPTR_D(FilterManager); |
147 | 149 | QList<Filter*>& fs = d.filter_out_map[output]; |
148 | return fs.removeAll(filter) > 0; | |
150 | bool ret = fs.removeAll(filter) > 0; | |
151 | if (fs.isEmpty()) d.filter_out_map.remove(output); | |
152 | return ret; | |
149 | 153 | } |
150 | 154 | |
151 | 155 | bool FilterManager::uninstallFilter(Filter *filter) |
152 | 156 | { |
153 | 157 | DPTR_D(FilterManager); |
154 | QMap<AVPlayer*, QList<Filter*> >::iterator it = d.vfilter_player_map.begin(); | |
155 | while (it != d.vfilter_player_map.end()) { | |
158 | QMap<AVPlayer*, QList<Filter*> > map1(d.vfilter_player_map); // NB: copy it for iteration because called code may modify map -- which caused crashes | |
159 | QMap<AVPlayer*, QList<Filter*> >::iterator it = map1.begin(); | |
160 | while (it != map1.end()) { | |
156 | 161 | if (uninstallVideoFilter(filter, it.key())) |
157 | 162 | return true; |
158 | 163 | ++it; |
159 | 164 | } |
160 | it = d.afilter_player_map.begin(); | |
161 | while (it != d.afilter_player_map.end()) { | |
165 | QMap<AVPlayer *, QList<Filter *> > map2(d.afilter_player_map); // copy to avoid crashes when called-code modifies map | |
166 | it = map2.begin(); | |
167 | while (it != map2.end()) { | |
162 | 168 | if (uninstallAudioFilter(filter, it.key())) |
163 | 169 | return true; |
164 | 170 | ++it; |
165 | 171 | } |
166 | QMap<AVOutput*, QList<Filter*> >::iterator it2 = d.filter_out_map.begin(); | |
167 | while (it2 != d.filter_out_map.end()) { | |
172 | QMap<AVOutput*, QList<Filter*> > map3(d.filter_out_map); // copy to avoid crashes | |
173 | QMap<AVOutput*, QList<Filter*> >::iterator it2 = map3.begin(); | |
174 | while (it2 != map3.end()) { | |
168 | 175 | if (uninstallFilter(filter, it2.key())) |
169 | 176 | return true; |
170 | 177 | ++it2; |
119 | 119 | // pixel_aspect==sar, pixel_aspect is more compatible |
120 | 120 | QString buffersrc_args = args; |
121 | 121 | qDebug("buffersrc_args=%s", buffersrc_args.toUtf8().constData()); |
122 | AVFilter *buffersrc = avfilter_get_by_name(video ? "buffer" : "abuffer"); | |
122 | #if LIBAVFILTER_VERSION_INT >= AV_VERSION_INT(7,0,0) | |
123 | const | |
124 | #endif | |
125 | AVFilter *buffersrc = avfilter_get_by_name(video ? "buffer" : "abuffer"); | |
123 | 126 | Q_ASSERT(buffersrc); |
124 | 127 | AV_ENSURE_OK(avfilter_graph_create_filter(&in_filter_ctx, |
125 | 128 | buffersrc, |
127 | 130 | filter_graph) |
128 | 131 | , false); |
129 | 132 | /* buffer video sink: to terminate the filter chain. */ |
133 | #if LIBAVFILTER_VERSION_INT >= AV_VERSION_INT(7,0,0) | |
134 | const | |
135 | #endif | |
130 | 136 | AVFilter *buffersink = avfilter_get_by_name(video ? "buffersink" : "abuffersink"); |
131 | 137 | Q_ASSERT(buffersink); |
132 | 138 | AV_ENSURE_OK(avfilter_graph_create_filter(&out_filter_ctx, buffersink, "out", |
0 | 0 | /****************************************************************************** |
1 | 1 | QtAV: Multimedia framework based on Qt and FFmpeg |
2 | Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com> | |
2 | Copyright (C) 2012-2018 Wang Bin <wbsecg1@gmail.com> | |
3 | 3 | |
4 | 4 | * This file is part of QtAV (from 2015) |
5 | 5 | |
23 | 23 | #include "QtAV/private/mkid.h" |
24 | 24 | #include "QtAV/private/factory.h" |
25 | 25 | #include <QtCore/QFile> |
26 | #include <qpa/qplatformnativeinterface.h> | |
27 | 26 | #include <QtGui/QGuiApplication> |
28 | 27 | #include <QtAndroidExtras> |
29 | 28 | #include "utils/Logger.h" |
29 | #include "jmi/jmi.h" | |
30 | 30 | |
31 | 31 | // TODO: how to get filename and find subtitles? |
32 | 32 | //http://stackoverflow.com/questions/5657411/android-getting-a-file-uri-from-a-content-uri |
45 | 45 | QString name() const Q_DECL_OVERRIDE { return QLatin1String(kName);} |
46 | 46 | const QStringList& protocols() const Q_DECL_OVERRIDE |
47 | 47 | { |
48 | static QStringList p = QStringList() << QStringLiteral("content"); // "file:" is supported too but we use QFile | |
48 | static QStringList p = QStringList() << QStringLiteral("content") << QStringLiteral("android.resource"); // "file:" is supported too but we use QFile | |
49 | 49 | return p; |
50 | 50 | } |
51 | 51 | virtual bool isSeekable() const Q_DECL_OVERRIDE; |
62 | 62 | void onUrlChanged() Q_DECL_OVERRIDE; |
63 | 63 | |
64 | 64 | private: |
65 | QAndroidJniObject app_ctx; | |
66 | 65 | QFile qt_file; |
67 | 66 | // if use Java.io.InputStream, record pos |
68 | 67 | }; |
73 | 72 | AndroidIO::AndroidIO() |
74 | 73 | : MediaIO() |
75 | 74 | { |
76 | QPlatformNativeInterface *interface = QGuiApplication::platformNativeInterface(); | |
77 | jobject activity = (jobject)interface->nativeResourceForIntegration("QtActivity"); | |
78 | app_ctx = QAndroidJniObject(activity).callObjectMethod("getApplicationContext","()Landroid/content/Context;"); | |
75 | jmi::javaVM(QAndroidJniEnvironment::javaVM()); // nativeResourceForIntegration("javaVM") | |
79 | 76 | } |
80 | 77 | |
81 | 78 | bool AndroidIO::isSeekable() const |
105 | 102 | void AndroidIO::onUrlChanged() |
106 | 103 | { |
107 | 104 | qt_file.close(); |
108 | QAndroidJniObject content_resolver = app_ctx.callObjectMethod("getContentResolver", "()Landroid/content/ContentResolver;"); | |
109 | if (!content_resolver.isValid()) { | |
110 | qWarning("getContentResolver error"); | |
105 | if (url().isEmpty()) | |
106 | return; | |
107 | struct Application final: jmi::ClassTag { static std::string name() {return "android/app/Application";}}; | |
108 | jmi::JObject<Application> app_ctx(jmi::android::application()); | |
109 | ||
110 | struct ContentResolver final: jmi::ClassTag { static std::string name() { return "android/content/ContentResolver";}}; | |
111 | struct GetContentResolver final: jmi::MethodTag { static const char* name() {return "getContentResolver";}}; | |
112 | jmi::JObject<ContentResolver> cr = app_ctx.call<jmi::JObject<ContentResolver>, GetContentResolver>(); | |
113 | if (!cr.error().empty()) { | |
114 | qWarning("getContentResolver error: %s", cr.error().data()); | |
111 | 115 | return; |
112 | 116 | } |
113 | QAndroidJniObject s = QAndroidJniObject::fromString(url()); | |
114 | QAndroidJniObject uri = QAndroidJniObject::callStaticObjectMethod("android/net/Uri", "parse", "(Ljava/lang/String;)Landroid/net/Uri;", s.object<jstring>()); | |
115 | //input_stream = content_resolver.callObjectMethod("openInputStream", "(Landroid.net.Uri;)Ljava.io.InputStream;", uri.object<jobject>()); // TODO: why error? | |
116 | //qDebug() << "onUrlChanged InputStream: " << input_stream.toString(); | |
117 | s = QAndroidJniObject::fromString(QStringLiteral("r")); | |
118 | QAndroidJniObject pfd = content_resolver.callObjectMethod("openFileDescriptor", "(Landroid/net/Uri;Ljava/lang/String;)Landroid/os/ParcelFileDescriptor;", uri.object<jobject>(), s.object<jstring>()); | |
119 | if (!pfd.isValid()) { | |
120 | qWarning("openFileDescriptor error"); | |
117 | struct Uri final: jmi::ClassTag { static std::string name() { return "android/net/Uri";}}; | |
118 | struct Parse final: jmi::MethodTag { static const char* name() {return "parse";}}; | |
119 | jmi::JObject<Uri> uri = jmi::JObject<Uri>::callStatic<jmi::JObject<Uri>, Parse>(url().toUtf8().constData()); // move? | |
120 | // openInputStream? | |
121 | struct ParcelFileDescriptor final: jmi::ClassTag { static std::string name() { return "android/os/ParcelFileDescriptor";}}; | |
122 | // AssetFileDescriptor supported schemes: content, android.resource, file | |
123 | // ParcelFileDescriptor supported schemes: content, file | |
124 | #if 1 | |
125 | struct AssetFileDescriptor final: jmi::ClassTag { static std::string name() { return "android/content/res/AssetFileDescriptor";}}; | |
126 | struct OpenAssetFileDescriptor final: jmi::MethodTag { static const char* name() {return "openAssetFileDescriptor";}}; | |
127 | jmi::JObject<AssetFileDescriptor> afd = cr.call<jmi::JObject<AssetFileDescriptor>, OpenAssetFileDescriptor>(std::move(uri), "r"); // TODO: rw | |
128 | if (!afd.error().empty()) { | |
129 | qWarning("openAssetFileDescriptor error: %s", afd.error().data()); | |
121 | 130 | return; |
122 | 131 | } |
123 | int fd = pfd.callMethod<int>("detachFd", "()I"); | |
132 | struct GetParcelFileDescriptor final: jmi::MethodTag { static const char* name() {return "getParcelFileDescriptor";}}; | |
133 | jmi::JObject<ParcelFileDescriptor> pfd = afd.call<jmi::JObject<ParcelFileDescriptor>, GetParcelFileDescriptor>(); | |
134 | #else | |
135 | struct OpenFileDescriptor final: jmi::MethodTag { static const char* name() {return "openFileDescriptor";}}; | |
136 | jmi::JObject<ParcelFileDescriptor> pfd = cr.call<jmi::JObject<ParcelFileDescriptor>, OpenFileDescriptor>(std::move(uri), "r"); | |
137 | #endif | |
138 | if (!pfd.error().empty()) { | |
139 | qWarning("get ParcelFileDescriptor error: %s", pfd.error().data()); | |
140 | return; | |
141 | } | |
142 | struct DetachFd final: jmi::MethodTag { static const char* name() {return "detachFd";}}; | |
143 | int fd = pfd.call<int,DetachFd>(); | |
124 | 144 | qt_file.open(fd, QIODevice::ReadOnly); |
125 | 145 | } |
126 | 146 | } //namespace QtAV |
50 | 50 | |
51 | 51 | !rc_file { |
52 | 52 | RC_ICONS = QtAV.ico |
53 | QMAKE_TARGET_COMPANY = "Shanghai University->S3 Graphics->Deepin | wbsecg1@gmail.com" | |
53 | QMAKE_TARGET_COMPANY = "wbsecg1@gmail.com" | |
54 | 54 | QMAKE_TARGET_DESCRIPTION = "QtAV Multimedia framework. http://qtav.org" |
55 | QMAKE_TARGET_COPYRIGHT = "Copyright (C) 2012-2017 WangBin, wbsecg1@gmail.com" | |
55 | QMAKE_TARGET_COPYRIGHT = "Copyright (C) 2012-2019 WangBin, wbsecg1@gmail.com" | |
56 | 56 | QMAKE_TARGET_PRODUCT = "QtAV" |
57 | 57 | } else:win32 { |
58 | 58 | RC_FILE = QtAV.rc |
117 | 117 | DEFINES += __STDC_CONSTANT_MACROS |
118 | 118 | android { |
119 | 119 | CONFIG *= config_opensl |
120 | !no_gui_private:qtHaveModule(androidextras) { #qt5.2 has QAndroidJniObject | |
121 | QT *= androidextras gui-private #QPlatformNativeInterface get "QtActivity" | |
120 | SOURCES *= jmi/jmi.cpp | |
121 | qtHaveModule(androidextras) { #qt5.2 has QAndroidJniObject | |
122 | QT *= androidextras #QPlatformNativeInterface get "QtActivity" | |
122 | 123 | SOURCES *= io/AndroidIO.cpp |
123 | 124 | SOURCES *= codec/video/VideoDecoderMediaCodec.cpp |
125 | exists($$[QT_INSTALL_HEADERS]/MediaCodecTextureStandalone.h) { | |
126 | DEFINES *= MEDIACODEC_TEXTURE | |
127 | LIBS *= -lqtav-mediacodec | |
128 | } | |
124 | 129 | } |
125 | 130 | } |
126 | 131 | config_x11 { |
151 | 156 | ios { |
152 | 157 | LIBS += -framework AVFoundation |
153 | 158 | } else { |
154 | LIBS += -framework QTKit | |
155 | 159 | # assume avdevice targets to the same version as Qt and always >= 10.6 |
156 | 160 | !isEqual(QMAKE_MACOSX_DEPLOYMENT_TARGET, 10.6): LIBS += -framework AVFoundation |
157 | 161 | } |
161 | 165 | config_avfilter { |
162 | 166 | DEFINES += QTAV_HAVE_AVFILTER=1 |
163 | 167 | LIBS += -lavfilter |
168 | mac:!ios:static_ffmpeg: LIBS += -framework AppKit | |
164 | 169 | } |
165 | 170 | config_ipp { |
166 | 171 | DEFINES += QTAV_HAVE_IPP=1 |
181 | 186 | CONFIG *= config_openal |
182 | 187 | SOURCES += output/audio/AudioOutputAudioToolbox.cpp |
183 | 188 | LIBS += -framework AudioToolbox |
189 | LIBS += -Wl,-unexported_symbols_list,$$PWD/unexport.list | |
190 | } else:!win32 { | |
191 | #LIBS += -Wl,--exclude-libs,ALL | |
184 | 192 | } |
185 | 193 | win32: { |
186 | 194 | HEADERS += output/audio/xaudio2_compat.h |
320 | 328 | } |
321 | 329 | mac { |
322 | 330 | HEADERS *= codec/video/SurfaceInteropCV.h |
323 | SOURCES *= codec/video/SurfaceInteropCV.cpp | |
331 | SOURCES *= codec/video/SurfaceInteropCV.cpp \ | |
332 | codec/video/SurfaceInteropIOSurface.mm | |
324 | 333 | ios { |
325 | 334 | OBJECTIVE_SOURCES *= codec/video/SurfaceInteropCVOpenGLES.mm |
326 | 335 | } else { |
327 | CONFIG += config_vda | |
328 | SOURCES *= codec/video/SurfaceInteropIOSurface.cpp | |
336 | #CONFIG += config_vda | |
329 | 337 | #SOURCES *= codec/video/SurfaceInteropCVOpenGL.cpp |
330 | LIBS += -framework IOSurface | |
331 | 338 | } |
332 | 339 | LIBS += -framework CoreVideo -framework CoreFoundation |
333 | 340 | } |
0 | 0 | /****************************************************************************** |
1 | 1 | QtAV: Multimedia framework based on Qt and FFmpeg |
2 | Copyright (C) 2012-2017 Wang Bin <wbsecg1@gmail.com> | |
2 | Copyright (C) 2012-2018 Wang Bin <wbsecg1@gmail.com> | |
3 | 3 | |
4 | 4 | * This file is part of QtAV (from 2014) |
5 | 5 | |
172 | 172 | gr->updateGeometry(geometry); |
173 | 173 | } |
174 | 174 | |
175 | OpenGLVideo::OpenGLVideo() {} | |
175 | OpenGLVideo::OpenGLVideo() { | |
176 | #if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0) | |
177 | // TODO: system resolution change | |
178 | connect(QGuiApplication::instance(), SIGNAL(primaryScreenChanged(QScreen*)), this, SLOT(updateViewport())); | |
179 | #endif | |
180 | } | |
176 | 181 | |
177 | 182 | bool OpenGLVideo::isSupported(VideoFormat::PixelFormat pixfmt) |
178 | 183 | { |
205 | 210 | d.material->setSaturation(s); |
206 | 211 | #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) |
207 | 212 | d.manager = ctx->findChild<ShaderManager*>(QStringLiteral("__qtav_shader_manager")); |
208 | QSizeF surfaceSize = ctx->surface()->size(); | |
209 | #if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0) | |
210 | surfaceSize *= ctx->screen()->devicePixelRatio(); | |
211 | #else | |
212 | surfaceSize *= qApp->devicePixelRatio(); //TODO: window()->devicePixelRatio() is the window screen's | |
213 | #endif | |
214 | #else | |
215 | QSizeF surfaceSize = QSizeF(ctx->device()->width(), ctx->device()->height()); | |
216 | #endif | |
217 | setProjectionMatrixToRect(QRectF(QPointF(), surfaceSize)); | |
213 | #endif | |
214 | updateViewport(); | |
218 | 215 | if (d.manager) |
219 | 216 | return; |
220 | 217 | // TODO: what if ctx is delete? |
348 | 345 | DPTR_D(OpenGLVideo); |
349 | 346 | Q_ASSERT(d.manager); |
350 | 347 | Q_EMIT beforeRendering(); |
351 | DYGL(glViewport(d.rect.x(), d.rect.y(), d.rect.width(), d.rect.height())); // viewport was used in gpu filters is wrong, qt quick fbo item's is right(so must ensure setProjectionMatrixToRect was called correctly) | |
352 | 348 | const qint64 mt = d.material->type(); |
353 | 349 | if (d.material_type != mt) { |
354 | 350 | qDebug() << "material changed: " << VideoMaterial::typeName(d.material_type) << " => " << VideoMaterial::typeName(mt); |
359 | 355 | VideoShader *shader = d.user_shader; |
360 | 356 | if (!shader) |
361 | 357 | shader = d.manager->prepareMaterial(d.material, mt); //TODO: print shader type name if changed. prepareMaterial(,sample_code, pp_code) |
358 | DYGL(glViewport(d.rect.x(), d.rect.y(), d.rect.width(), d.rect.height())); // viewport was used in gpu filters is wrong, qt quick fbo item's is right(so must ensure setProjectionMatrixToRect was called correctly) | |
362 | 359 | shader->update(d.material); |
363 | 360 | shader->program()->setUniformValue(shader->matrixLocation(), transform*d.matrix); |
364 | 361 | // uniform end. attribute begin |
386 | 383 | d_func().resetGL(); |
387 | 384 | } |
388 | 385 | |
386 | void OpenGLVideo::updateViewport() | |
387 | { | |
388 | DPTR_D(OpenGLVideo); | |
389 | if (!d.ctx) | |
390 | return; | |
391 | #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) | |
392 | QSizeF surfaceSize = d.ctx->surface()->size(); | |
393 | #if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0) | |
394 | surfaceSize *= d.ctx->screen()->devicePixelRatio(); | |
395 | #else | |
396 | surfaceSize *= qApp->devicePixelRatio(); //TODO: window()->devicePixelRatio() is the window screen's | |
397 | #endif | |
398 | #else | |
399 | QSizeF surfaceSize = QSizeF(d.ctx->device()->width(), d.ctx->device()->height()); | |
400 | #endif | |
401 | setProjectionMatrixToRect(QRectF(QPointF(), surfaceSize)); | |
402 | } | |
389 | 403 | } //namespace QtAV |
220 | 220 | //} |
221 | 221 | // buffers_free.ref(); |
222 | 222 | } |
223 | DWORD status; | |
224 | stream_buf->GetStatus(&status); | |
223 | //DWORD status; | |
224 | //stream_buf->GetStatus(&status); | |
225 | 225 | //qDebug("status: %lu", status); |
226 | return; | |
227 | if (status & DSBSTATUS_LOOPING) { | |
226 | //return; | |
227 | //if (status & DSBSTATUS_LOOPING) { | |
228 | 228 | // sound will loop even if buffer is finished |
229 | DX_ENSURE(stream_buf->Stop()); | |
229 | //DX_ENSURE(stream_buf->Stop()); | |
230 | 230 | // reset positions to ensure the notification positions and played buffer matches |
231 | DX_ENSURE(stream_buf->SetCurrentPosition(0)); | |
232 | write_offset = 0; | |
233 | } | |
231 | //DX_ENSURE(stream_buf->SetCurrentPosition(0)); | |
232 | //write_offset = 0; | |
233 | //} | |
234 | 234 | } |
235 | 235 | |
236 | 236 | bool AudioOutputDSound::write(const QByteArray &data) |
26 | 26 | #include <SLES/OpenSLES_Android.h> |
27 | 27 | #include <SLES/OpenSLES_AndroidConfiguration.h> |
28 | 28 | #include <sys/system_properties.h> |
29 | #include <cmath> | |
29 | 30 | #endif |
30 | 31 | #include "QtAV/private/mkid.h" |
31 | 32 | #include "QtAV/private/factory.h" |
323 | 324 | // Volume interface |
324 | 325 | //SL_ENSURE((*m_playerObject)->GetInterface(m_playerObject, SL_IID_VOLUME, &m_volumeItf), false); |
325 | 326 | |
326 | sem.release(buffer_count - sem.available()); | |
327 | /* | |
328 | * DO NOT call sem.release(buffer_count - sem.available()) because playInitialData() will enqueue buffers and then sem.release() is called in callback. | |
329 | * Otherwise, Enqueue() the 1st(or more) real buffer may report SL_RESULT_BUFFER_INSUFFICIENT and noise will be played. | |
330 | * Can not Enqueue() internally here because buffers are managed in AudioOutput to ensure 0-copy | |
331 | */ | |
327 | 332 | return true; |
328 | 333 | } |
329 | 334 |
0 | 0 | /****************************************************************************** |
1 | 1 | QtAV: Multimedia framework based on Qt and FFmpeg |
2 | Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com> | |
2 | Copyright (C) 2012-2017 Wang Bin <wbsecg1@gmail.com> | |
3 | 3 | |
4 | 4 | * This file is part of QtAV (from 2014) |
5 | 5 | |
48 | 48 | { |
49 | 49 | matrix.setToIdentity(); |
50 | 50 | matrix.scale((GLfloat)out_rect.width()/(GLfloat)renderer_width, (GLfloat)out_rect.height()/(GLfloat)renderer_height, 1); |
51 | if (orientation) | |
52 | matrix.rotate(orientation, 0, 0, 1); // Z axis | |
51 | if (rotation()) | |
52 | matrix.rotate(rotation(), 0, 0, 1); // Z axis | |
53 | 53 | } |
54 | 54 | |
55 | 55 | OpenGLRendererBase::OpenGLRendererBase(OpenGLRendererBasePrivate &d) |
0 | 0 | /****************************************************************************** |
1 | 1 | QtAV: Multimedia framework based on Qt and FFmpeg |
2 | Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com> | |
2 | Copyright (C) 2012-2017 Wang Bin <wbsecg1@gmail.com> | |
3 | 3 | |
4 | 4 | * This file is part of QtAV |
5 | 5 | |
99 | 99 | if (d.pixmap.isNull()) |
100 | 100 | return; |
101 | 101 | QRect roi = realROI(); |
102 | if (orientation() == 0) { | |
102 | if (d.rotation() == 0) { | |
103 | 103 | //assume that the image data is already scaled to out_size(NOT renderer size!) |
104 | 104 | if (roi.size() == d.out_rect.size()) { |
105 | 105 | d.painter->drawPixmap(d.out_rect.topLeft(), d.pixmap, roi); |
115 | 115 | d.painter->save(); |
116 | 116 | d.painter->translate(rendererWidth()/2, rendererHeight()/2); |
117 | 117 | // TODO: why rotate then scale gives wrong result? |
118 | if (orientation() % 180) | |
118 | if (d.rotation() % 180) | |
119 | 119 | d.painter->scale((qreal)d.out_rect.width()/(qreal)rendererHeight(), (qreal)d.out_rect.height()/(qreal)rendererWidth()); |
120 | 120 | else |
121 | 121 | d.painter->scale((qreal)d.out_rect.width()/(qreal)rendererWidth(), (qreal)d.out_rect.height()/(qreal)rendererHeight()); |
122 | d.painter->rotate(orientation()); | |
122 | d.painter->rotate(d.rotation()); | |
123 | 123 | d.painter->translate(-rendererWidth()/2, -rendererHeight()/2); |
124 | 124 | d.painter->drawPixmap(QRect(0, 0, rendererWidth(), rendererHeight()), d.pixmap, roi); |
125 | 125 | d.painter->restore(); |
0 | 0 | /****************************************************************************** |
1 | 1 | QtAV: Multimedia framework based on Qt and FFmpeg |
2 | Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com> | |
2 | Copyright (C) 2012-2017 Wang Bin <wbsecg1@gmail.com> | |
3 | 3 | |
4 | 4 | * This file is part of QtAV (from 2014) |
5 | 5 | |
39 | 39 | #if defined(Q_OS_DARWIN) |
40 | 40 | avwidgets.setFileName(QStringLiteral("QtAVWidgets.framework/QtAVWidgets")); //no dylib check |
41 | 41 | #elif defined(Q_OS_WIN) |
42 | avwidgets.setFileName(QStringLiteral("QtAVWidgets").append(QString::number(QTAV_VERSION_MAJOR(QtAV_Version())))); | |
42 | avwidgets.setFileName(QStringLiteral("QtAVWidgets") | |
43 | # ifndef QT_NO_DEBUG | |
44 | .append("d") | |
45 | # endif | |
46 | .append(QString::number(QTAV_VERSION_MAJOR(QtAV_Version())))); | |
43 | 47 | #else |
44 | 48 | avwidgets.setFileNameAndVersion(QStringLiteral("QtAVWidgets"), QTAV_VERSION_MAJOR(QtAV_Version())); |
45 | 49 | #endif |
0 | 0 | /****************************************************************************** |
1 | 1 | QtAV: Multimedia framework based on Qt and FFmpeg |
2 | Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com> | |
2 | Copyright (C) 2012-2017 Wang Bin <wbsecg1@gmail.com> | |
3 | 3 | |
4 | 4 | * This file is part of QtAV |
5 | 5 | |
287 | 287 | |
288 | 288 | void VideoRenderer::setOrientation(int value) |
289 | 289 | { |
290 | DPTR_D(VideoRenderer); | |
290 | 291 | // currently only supports a multiple of 90 |
291 | 292 | value = (value + 360) % 360; |
292 | 293 | if (value % 90) |
293 | 294 | return; |
294 | DPTR_D(VideoRenderer); | |
295 | 295 | if (d.orientation == value) |
296 | 296 | return; |
297 | 297 | int old = orientation(); |
311 | 311 | |
312 | 312 | int VideoRenderer::orientation() const |
313 | 313 | { |
314 | return d_func().orientation; | |
314 | DPTR_D(const VideoRenderer); | |
315 | return d.orientation; | |
315 | 316 | } |
316 | 317 | |
317 | 318 | // only qpainter and opengl based renderers support orientation. |
504 | 505 | */ |
505 | 506 | if (d.video_frame.isValid()) { |
506 | 507 | drawFrame(); |
508 | //qDebug("render elapsed: %lld", et.elapsed()); | |
507 | 509 | if (d.statistics) { |
508 | 510 | d.statistics->video_only.frameDisplayed(d.video_frame.timestamp()); |
509 | 511 | d.statistics->video.current_time = QTime(0, 0, 0).addMSecs(int(d.video_frame.timestamp() * 1000.0)); |
0 | 0 | /****************************************************************************** |
1 | 1 | QtAV: Multimedia framework based on Qt and FFmpeg |
2 | Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com> | |
2 | Copyright (C) 2012-2018 Wang Bin <wbsecg1@gmail.com> | |
3 | 3 | |
4 | 4 | * This file is part of QtAV (from 2014) |
5 | 5 | |
79 | 79 | QStringList ffmpeg_supported_sub_extensions_by_codec() |
80 | 80 | { |
81 | 81 | QStringList exts; |
82 | AVCodec *c = av_codec_next(NULL); | |
83 | while (c) { | |
84 | if (c->type != AVMEDIA_TYPE_SUBTITLE) { | |
85 | c = av_codec_next(c); | |
82 | const AVCodec* c = NULL; | |
83 | #if AVCODEC_STATIC_REGISTER | |
84 | void* it = NULL; | |
85 | while ((c = av_codec_iterate(&it))) { | |
86 | #else | |
87 | avcodec_register_all(); | |
88 | while ((c = av_codec_next(c))) { | |
89 | #endif | |
90 | if (c->type != AVMEDIA_TYPE_SUBTITLE) | |
86 | 91 | continue; |
87 | } | |
88 | 92 | qDebug("sub codec: %s", c->name); |
89 | AVInputFormat *i = av_iformat_next(NULL); | |
90 | while (i) { | |
93 | #if AVFORMAT_STATIC_REGISTER | |
94 | const AVInputFormat *i = NULL; | |
95 | void* it2 = NULL; | |
96 | while ((i = av_demuxer_iterate(&it2))) { | |
97 | #else | |
98 | av_register_all(); // MUST register all input/output formats | |
99 | AVInputFormat *i = NULL; | |
100 | while ((i = av_iformat_next(i))) { | |
101 | #endif | |
91 | 102 | if (!strcmp(i->name, c->name)) { |
92 | 103 | qDebug("found iformat"); |
93 | 104 | if (i->extensions) { |
98 | 109 | } |
99 | 110 | break; |
100 | 111 | } |
101 | i = av_iformat_next(i); | |
102 | 112 | } |
103 | 113 | if (!i) { |
104 | 114 | //qDebug("codec name '%s' is not found in AVInputFormat, just append codec name", c->name); |
105 | 115 | //exts.append(c->name); |
106 | 116 | } |
107 | c = av_codec_next(c); | |
108 | 117 | } |
109 | 118 | return exts; |
110 | 119 | } |
112 | 121 | QStringList ffmpeg_supported_sub_extensions() |
113 | 122 | { |
114 | 123 | QStringList exts; |
124 | #if AVFORMAT_STATIC_REGISTER | |
125 | const AVInputFormat *i = NULL; | |
126 | void* it = NULL; | |
127 | while ((i = av_demuxer_iterate(&it))) { | |
128 | #else | |
129 | av_register_all(); // MUST register all input/output formats | |
115 | 130 | AVInputFormat *i = NULL; |
116 | 131 | while ((i = av_iformat_next(i))) { |
132 | #endif | |
117 | 133 | // strstr parameters can not be null |
118 | 134 | if (i->long_name && strstr(i->long_name, "subtitle")) { |
119 | 135 | if (i->extensions) { |
126 | 142 | // AVCodecDescriptor.name and AVCodec.name may be different. avcodec_get_name() use AVCodecDescriptor if possible |
127 | 143 | QStringList codecs; |
128 | 144 | const AVCodec* c = NULL; |
145 | #if AVCODEC_STATIC_REGISTER | |
146 | it = NULL; | |
147 | while ((c = av_codec_iterate(&it))) { | |
148 | #else | |
149 | avcodec_register_all(); | |
129 | 150 | while ((c = av_codec_next(c))) { |
151 | #endif | |
130 | 152 | if (c->type == AVMEDIA_TYPE_SUBTITLE) |
131 | 153 | codecs.append(QString::fromLatin1(c->name)); |
132 | 154 | } |
248 | 270 | codec_ctx->time_base.den = 1000; |
249 | 271 | if (!data.isEmpty()) { |
250 | 272 | av_free(codec_ctx->extradata); |
251 | codec_ctx->extradata = (uint8_t*)av_mallocz(data.size() + FF_INPUT_BUFFER_PADDING_SIZE); | |
273 | codec_ctx->extradata = (uint8_t*)av_mallocz(data.size() + AV_INPUT_BUFFER_PADDING_SIZE); | |
252 | 274 | if (!codec_ctx->extradata) |
253 | 275 | return false; |
254 | 276 | codec_ctx->extradata_size = data.size(); |
47 | 47 | */ |
48 | 48 | void setThreshold(int min); //wake up and enqueue |
49 | 49 | |
50 | void put(const T& t); | |
51 | T take(); | |
50 | /*! \brief put | |
51 | * put t into the queue. Will block if blockFull is set to true (and optionally can specify a wait timeout in ms). | |
52 | * \param t the item to copy into the queue | |
53 | * \param wait_timeout_ms this parameter is used if blockFull == true (the default). | |
54 | * If the queue is full, optionally wait for the queue to not be full a maximum of wait_timeout_ms milliseconds, | |
55 | * and return false if timeout expires. ULONG_MAX means wait indefinitely. | |
56 | * \return true if item was successfully placed in the queue and the queue was not full. | |
57 | * false is returned if the queue is (still) full. The item is still placed into a full queue! | |
58 | * Note that even a 'full' queue will accept new items and t WILL be placed in the queue regardless of return value. | |
59 | */ | |
60 | bool put(const T& t, unsigned long wait_timeout_ms = ULONG_MAX); | |
61 | /*! \brief take | |
62 | * Dequeue 1 item from queue, optionally blocking. | |
63 | * \param wait_timeout_ms this parameter is used if blockEmpty == true (the default). | |
64 | * If the queue is empty, optionally wait for the queue to not be empty a maximum of wait_timeout_ms milliseconds. | |
65 | * ULONG_MAX means wait indefinitely. | |
66 | * \param isValid a pointer to a bool (optional). If isValid is set to true after a call, the returned item is valid. False means the queue was empty or the timeout expired. | |
67 | * \return the item taken. It may not be valid if the queue was empty and timeout expired. Check optional isValid flag to determine if that is the case. | |
68 | */ | |
69 | T take(unsigned long wait_timeout_ms = ULONG_MAX, bool *isValid = 0); | |
52 | 70 | void setBlocking(bool block); //will wake if false. called when no more data can enqueue |
53 | 71 | void blockEmpty(bool block); |
54 | 72 | void blockFull(bool block); |
132 | 150 | } |
133 | 151 | |
134 | 152 | template <typename T, template <typename> class Container> |
135 | void BlockingQueue<T, Container>::put(const T& t) | |
136 | { | |
153 | bool BlockingQueue<T, Container>::put(const T& t, unsigned long timeout_ms) | |
154 | { | |
155 | bool ret = true; | |
137 | 156 | QWriteLocker locker(&lock); |
138 | 157 | Q_UNUSED(locker); |
139 | 158 | if (checkFull()) { |
159 | ret = false; | |
140 | 160 | //qDebug("queue full"); //too frequent |
141 | 161 | if (full_callback) { |
142 | 162 | full_callback->call(); |
143 | 163 | } |
144 | 164 | if (block_full) |
145 | cond_full.wait(&lock); | |
165 | ret = cond_full.wait(&lock, timeout_ms); | |
166 | // uncomment here to reject placing items into a full queue -- update API docs if you do this. | |
167 | // if (!ret) return false; | |
146 | 168 | } |
147 | 169 | queue.enqueue(t); |
148 | 170 | onPut(t); // emit bufferProgressChanged here if buffering |
152 | 174 | } else { |
153 | 175 | //qDebug("buffering: %d/%d~%d", queue.size(), thres, cap); |
154 | 176 | } |
155 | } | |
156 | ||
157 | template <typename T, template <typename> class Container> | |
158 | T BlockingQueue<T, Container>::take() | |
159 | { | |
177 | return ret; | |
178 | } | |
179 | ||
180 | template <typename T, template <typename> class Container> | |
181 | T BlockingQueue<T, Container>::take(unsigned long timeout_ms, bool *isValid) | |
182 | { | |
183 | if (isValid) *isValid = false; | |
160 | 184 | QWriteLocker locker(&lock); |
161 | 185 | Q_UNUSED(locker); |
162 | 186 | if (checkEmpty()) {//TODO:always block? |
165 | 189 | empty_callback->call(); |
166 | 190 | } |
167 | 191 | if (block_empty) |
168 | cond_empty.wait(&lock); //block when empty only | |
192 | cond_empty.wait(&lock,timeout_ms); //block when empty only | |
169 | 193 | } |
170 | 194 | if (checkEmpty()) { |
171 | 195 | //qWarning("Queue is still empty"); |
175 | 199 | return T(); |
176 | 200 | } |
177 | 201 | T t(queue.dequeue()); |
202 | if (isValid) *isValid = true; | |
178 | 203 | cond_full.wakeOne(); |
179 | 204 | onTake(t); // emit start buffering here if empty |
180 | 205 | return t; |
83 | 83 | D3DPRESENT_PARAMETERS d3dpp; |
84 | 84 | InitParameters(&d3dpp); |
85 | 85 | // D3DCREATE_MULTITHREADED is required by gl interop. https://www.opengl.org/registry/specs/NV/DX_interop.txt |
86 | // D3DCREATE_SOFTWARE_VERTEXPROCESSING in other dxva decoders. D3DCREATE_HARDWARE_VERTEXPROCESSING in cuda samples | |
87 | DWORD flags = D3DCREATE_FPU_PRESERVE | D3DCREATE_MULTITHREADED | D3DCREATE_MIXED_VERTEXPROCESSING; | |
86 | // D3DCREATE_SOFTWARE_VERTEXPROCESSING in other dxva decoders. D3DCREATE_HARDWARE_VERTEXPROCESSING is required by cuda in cuD3D9CtxCreate() | |
87 | DWORD flags = D3DCREATE_FPU_PRESERVE | D3DCREATE_MULTITHREADED | D3DCREATE_HARDWARE_VERTEXPROCESSING; | |
88 | 88 | IDirect3DDevice9Ex *d3d9dev = NULL; |
89 | 89 | // mpv: |
90 | 90 | /* Direct3D needs a HWND to create a device, even without using ::Present |
42 | 42 | set(HEADERS ${SDK_HEADERS}) |
43 | 43 | if(WIN32) |
44 | 44 | set(RC_FILE ${CMAKE_CURRENT_BINARY_DIR}/${MODULE}.rc) |
45 | configure_file(${CMAKE_SOURCE_DIR}/cmake/QtAV.rc.in ${RC_FILE}) | |
45 | configure_file(${QTAV_SOURCE_DIR}/cmake/QtAV.rc.in ${RC_FILE}) | |
46 | 46 | endif() |
47 | 47 | # add HEADERS for moc |
48 | 48 | add_library(${MODULE} SHARED ${SOURCES} ${RESOURCES_SOURCES} ${HEADERS} ${RC_FILE}) |
0 | 0 | /****************************************************************************** |
1 | 1 | QtAV: Multimedia framework based on Qt and FFmpeg |
2 | Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com> | |
2 | Copyright (C) 2012-2017 Wang Bin <wbsecg1@gmail.com> | |
3 | 3 | |
4 | 4 | * This file is part of QtAV |
5 | 5 | |
21 | 21 | #include "QtAVWidgets/GraphicsItemRenderer.h" |
22 | 22 | #include "QtAV/private/QPainterRenderer_p.h" |
23 | 23 | #include "QtAV/FilterContext.h" |
24 | #define QTAV_HAVE_OPENGL (!defined QT_NO_OPENGL && (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) || defined(QT_OPENGL_LIB))) | |
24 | #if !defined QT_NO_OPENGL && (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) || defined(QT_OPENGL_LIB)) | |
25 | #define QTAV_HAVE_OPENGL 1 | |
26 | #else | |
27 | #define QTAV_HAVE_OPENGL 0 | |
28 | #endif | |
25 | 29 | #if QTAV_HAVE(OPENGL) |
26 | 30 | #include "QtAV/OpenGLVideo.h" |
27 | 31 | #else |
33 | 37 | #include <QEvent> |
34 | 38 | #include <QKeyEvent> |
35 | 39 | #include <QGraphicsSceneEvent> |
40 | #include <QtCore/QCoreApplication> | |
36 | 41 | #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) |
37 | 42 | #include <QtGui/QSurface> |
38 | 43 | #endif |
50 | 55 | void setupAspectRatio() { |
51 | 56 | matrix.setToIdentity(); |
52 | 57 | matrix.scale((GLfloat)out_rect.width()/(GLfloat)renderer_width, (GLfloat)out_rect.height()/(GLfloat)renderer_height, 1); |
53 | if (orientation) | |
54 | matrix.rotate(orientation, 0, 0, 1); // Z axis | |
58 | if (rotation()) | |
59 | matrix.rotate(rotation(), 0, 0, 1); // Z axis | |
55 | 60 | } |
56 | 61 | // return true if opengl is enabled and context is ready. may called by non-rendering thread |
57 | 62 | bool checkGL() { |
125 | 130 | { |
126 | 131 | preparePixmap(frame); |
127 | 132 | } |
128 | scene()->update(sceneBoundingRect()); //TODO: thread? | |
133 | // moved to event | |
134 | // scene()->update(sceneBoundingRect()); //TODO: thread? | |
135 | QCoreApplication::postEvent(this, new QEvent(QEvent::User)); | |
129 | 136 | //update(); //does not cause an immediate paint. my not redraw. |
130 | 137 | return true; |
131 | 138 | } |
172 | 179 | } else { |
173 | 180 | qWarning("FilterContext not available!"); |
174 | 181 | } |
182 | // save painter state, switch to native opengl painting | |
183 | painter->save(); | |
184 | painter->beginNativePainting(); | |
185 | ||
175 | 186 | handlePaintEvent(); |
187 | ||
188 | // end native painting, restore state | |
189 | painter->endNativePainting(); | |
190 | painter->restore(); | |
191 | ||
176 | 192 | d.painter = 0; //painter may be not available outside this function |
177 | 193 | if (ctx) |
178 | 194 | ctx->painter = 0; |
289 | 305 | #if CONFIG_GRAPHICSWIDGET |
290 | 306 | bool GraphicsItemRenderer::event(QEvent *event) |
291 | 307 | { |
292 | setFocus(); //WHY: Force focus | |
293 | QEvent::Type type = event->type(); | |
294 | qDebug("GraphicsItemRenderer event type = %d", type); | |
295 | if (type == QEvent::KeyPress) { | |
296 | qDebug("KeyPress Event. key=%d", static_cast<QKeyEvent*>(event)->key()); | |
297 | } | |
308 | if (e->type() == QEvent::User) { | |
309 | scene()->update(sceneBoundingRect()); | |
310 | } | |
311 | else { | |
312 | setFocus(); //WHY: Force focus | |
313 | QEvent::Type type = event->type(); | |
314 | qDebug("GraphicsItemRenderer event type = %d", type); | |
315 | if (type == QEvent::KeyPress) { | |
316 | qDebug("KeyPress Event. key=%d", static_cast<QKeyEvent*>(event)->key()); | |
317 | } | |
318 | } | |
298 | 319 | return true; |
299 | 320 | } |
300 | 321 | #else |
322 | bool GraphicsItemRenderer::event(QEvent *event) | |
323 | { | |
324 | if (event->type() != QEvent::User) | |
325 | return GraphicsWidget::event(event); | |
326 | scene()->update(sceneBoundingRect()); | |
327 | return true; | |
328 | } | |
301 | 329 | /*simply passes event to QGraphicsWidget::event(). you should not have to |
302 | 330 | *reimplement sceneEvent() in a subclass of QGraphicsWidget. |
303 | 331 | */ |
103 | 103 | #if CONFIG_GRAPHICSWIDGET |
104 | 104 | bool event(QEvent *event) Q_DECL_OVERRIDE; |
105 | 105 | #else |
106 | bool event(QEvent *event) Q_DECL_OVERRIDE; | |
106 | 107 | //bool sceneEvent(QEvent *event) Q_DECL_OVERRIDE; |
107 | 108 | #endif //CONFIG_GRAPHICSWIDGET |
108 | 109 |
44 | 44 | void setFile(const QString& file); |
45 | 45 | QString file() const; |
46 | 46 | // default is false |
47 | void setKeepAspectRatio(bool value = true); | |
48 | bool isKeepAspectRatio() const; | |
47 | void setKeepAspectRatio(bool value = true) { m_keep_ar = value; } | |
48 | bool isKeepAspectRatio() const { return m_keep_ar; } | |
49 | /// AutoDisplayFrame -- default is true. if true, new frames from underlying extractor will update display widget automatically. | |
50 | bool isAutoDisplayFrame() const { return m_auto_display; } | |
51 | /// If false, new frames (or frame errors) won't automatically update widget | |
52 | /// (caller must ensure to call displayFrame()/displayFrame(frame) for this if false). | |
53 | /// set to false only if you want to do your own frame caching magic with preview frames. | |
54 | void setAutoDisplayFrame(bool b=true); | |
55 | public Q_SLOTS: // these were previously private but made public to allow calling code to cache some preview frames and directly display frames to this class | |
56 | void displayFrame(const QtAV::VideoFrame& frame); //parameter VideoFrame | |
57 | void displayNoFrame(); | |
49 | 58 | Q_SIGNALS: |
50 | 59 | void timestampChanged(); |
51 | 60 | void fileChanged(); |
61 | /// emitted on real decode error -- in that case displayNoFrame() will be automatically called | |
62 | void gotError(const QString &); | |
63 | /// usually emitted when a new request for a frame came in and current request was aborted. displayNoFrame() will be automatically called | |
64 | void gotAbort(const QString &); | |
65 | /// useful if calling code is interested in keeping stats on good versus bad frame counts, | |
66 | /// or if it wants to cache preview frames. Keeping counts helps caller decide if | |
67 | /// preview is working reliably or not for the designated file. | |
68 | /// parameter frame will always have: frame.isValid() == true, and will be | |
69 | /// already-scaled and in the right format to fit in the preview widget | |
70 | void gotFrame(const QtAV::VideoFrame & frame); | |
52 | 71 | protected: |
53 | 72 | virtual void resizeEvent(QResizeEvent *); |
54 | private Q_SLOTS: | |
55 | void displayFrame(const QtAV::VideoFrame& frame); //parameter VideoFrame | |
56 | void displayNoFrame(); | |
57 | 73 | |
58 | 74 | private: |
59 | bool m_keep_ar; | |
75 | bool m_keep_ar, m_auto_display; | |
60 | 76 | QString m_file; |
61 | 77 | VideoFrameExtractor *m_extractor; |
62 | 78 | VideoOutput *m_out; |
28 | 28 | VideoPreviewWidget::VideoPreviewWidget(QWidget *parent) |
29 | 29 | : QWidget(parent) |
30 | 30 | , m_keep_ar(false) |
31 | , m_auto_display(false) // set to false initially to trigger connections in setAutoDisplayFrame() below -- will default to true | |
31 | 32 | , m_extractor(new VideoFrameExtractor(this)) |
32 | 33 | , m_out(new VideoOutput(VideoRendererId_Widget, this)) |
33 | 34 | // FIXME: opengl may crash, so use software renderer here |
36 | 37 | Q_ASSERT_X(m_out->widget(), "VideoPreviewWidget()", "widget based renderer is not found"); |
37 | 38 | m_out->widget()->setParent(this); |
38 | 39 | connect(m_extractor, SIGNAL(positionChanged()), this, SIGNAL(timestampChanged())); |
39 | connect(m_extractor, SIGNAL(frameExtracted(QtAV::VideoFrame)), SLOT(displayFrame(QtAV::VideoFrame))); | |
40 | connect(m_extractor, SIGNAL(error()), SLOT(displayNoFrame())); | |
41 | connect(this, SIGNAL(fileChanged()), SLOT(displayNoFrame())); | |
40 | connect(m_extractor, SIGNAL(error(const QString &)), this, SIGNAL(gotError(const QString &))); | |
41 | connect(m_extractor, SIGNAL(aborted(const QString &)), this, SIGNAL(gotAbort(const QString &))); | |
42 | 42 | m_extractor->setAutoExtract(false); |
43 | m_auto_display = false; | |
44 | setAutoDisplayFrame(true); // set up frame-related connections, defaulting autoDisplayFrame to true | |
45 | } | |
46 | ||
47 | void VideoPreviewWidget::setAutoDisplayFrame(bool b) | |
48 | { | |
49 | if (!!b == !!m_auto_display) return; // avoid reduntant connect/disconnect calls | |
50 | if (b) { | |
51 | connect(m_extractor, SIGNAL(frameExtracted(QtAV::VideoFrame)), SLOT(displayFrame(QtAV::VideoFrame))); | |
52 | connect(m_extractor, SIGNAL(error(const QString &)), SLOT(displayNoFrame())); | |
53 | connect(m_extractor, SIGNAL(aborted(const QString &)), SLOT(displayNoFrame())); | |
54 | connect(this, SIGNAL(fileChanged()), SLOT(displayNoFrame())); | |
55 | } else { | |
56 | disconnect(m_extractor, SIGNAL(frameExtracted(QtAV::VideoFrame)), this, SLOT(displayFrame(QtAV::VideoFrame))); | |
57 | disconnect(m_extractor, SIGNAL(error(const QString &)), this, SLOT(displayNoFrame())); | |
58 | disconnect(m_extractor, SIGNAL(aborted(const QString &)), this, SLOT(displayNoFrame())); | |
59 | disconnect(this, SIGNAL(fileChanged()), this, SLOT(displayNoFrame())); | |
60 | } | |
43 | 61 | } |
44 | 62 | |
45 | 63 | void VideoPreviewWidget::resizeEvent(QResizeEvent *e) |
82 | 100 | if (diff > m_extractor->precision()) { |
83 | 101 | //qWarning("timestamp difference (%d/%lld) is too large! ignore", diff); |
84 | 102 | } |
85 | if (m_out->isSupported(frame.format().pixelFormat())) { | |
86 | m_out->receive(frame); | |
103 | if (!frame.isValid()) { | |
104 | displayNoFrame(); | |
87 | 105 | return; |
88 | 106 | } |
89 | 107 | QSize s = m_out->widget()->rect().size(); |
92 | 110 | fs.scale(s, Qt::KeepAspectRatio); |
93 | 111 | s = fs; |
94 | 112 | } |
95 | VideoFrame f(frame.to(VideoFormat::Format_RGB32, s)); | |
96 | if (!f.isValid()) | |
113 | // make sure frame is the same size as the output widget size, and of the desired format | |
114 | // if not, convert & scale | |
115 | VideoFrame f(frame.pixelFormat() == m_out->preferredPixelFormat() && frame.size() == s | |
116 | ? frame | |
117 | : frame.to(m_out->preferredPixelFormat(), s) | |
118 | ); | |
119 | if (!f.isValid()) { | |
97 | 120 | return; |
121 | } | |
98 | 122 | m_out->receive(f); |
123 | Q_EMIT gotFrame(f); | |
99 | 124 | } |
100 | 125 | |
101 | 126 | void VideoPreviewWidget::displayNoFrame() |
23 | 23 | #include <QBoxLayout> |
24 | 24 | #include <QMessageBox> |
25 | 25 | #include <QPushButton> |
26 | #include <QTableWidget> | |
26 | #include <QTabWidget> | |
27 | 27 | #include <QTextBrowser> |
28 | 28 | |
29 | 29 | #include "QtAVWidgets/WidgetRenderer.h" |
23 | 23 | |
24 | 24 | !rc_file { |
25 | 25 | RC_ICONS = $$PROJECTROOT/src/QtAV.ico |
26 | QMAKE_TARGET_COMPANY = "Shanghai University->S3 Graphics->Deepin | wbsecg1@gmail.com" | |
26 | QMAKE_TARGET_COMPANY = "wbsecg1@gmail.com" | |
27 | 27 | QMAKE_TARGET_DESCRIPTION = "QtAVWidgets module. QtAV Multimedia framework. http://qtav.org" |
28 | QMAKE_TARGET_COPYRIGHT = "Copyright (C) 2012-2017 WangBin, wbsecg1@gmail.com" | |
28 | QMAKE_TARGET_COPYRIGHT = "Copyright (C) 2012-2019 WangBin, wbsecg1@gmail.com" | |
29 | 29 | QMAKE_TARGET_PRODUCT = "QtAV Widgets" |
30 | 30 | } else:win32 { |
31 | 31 | RC_FILE = QtAVWidgets.rc |