summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndré de la Rocha <andre.rocha@qt.io>2021-06-11 20:58:19 +0200
committerAndré de la Rocha <andre.rocha@qt.io>2021-06-16 16:51:45 +0200
commitf6dc1df986dacbc018a482f7e21b93bda6fc69fc (patch)
tree0fc780f65086eae3cbc7195bd05c7d87e4b3b61e
parente1f0c82576325a220a9dd1775c7b28dff60905c9 (diff)
Add metadata to captured media on Windows
Change-Id: I41957e8f8ba2d8ab59e206e14db349d6fbf13a6c Reviewed-by: Lars Knoll <lars.knoll@qt.io>
-rw-r--r--src/multimedia/CMakeLists.txt2
-rw-r--r--src/multimedia/platform/windows/common/mfmetadata.cpp (renamed from src/multimedia/platform/windows/player/mfmetadata.cpp)209
-rw-r--r--src/multimedia/platform/windows/common/mfmetadata_p.h (renamed from src/multimedia/platform/windows/player/mfmetadata_p.h)1
-rw-r--r--src/multimedia/platform/windows/mediacapture/qwindowsmediaencoder.cpp47
-rw-r--r--src/multimedia/platform/windows/mediacapture/qwindowsmediaencoder_p.h7
-rw-r--r--src/multimedia/platform/windows/player/mfplayersession.cpp2
6 files changed, 245 insertions, 23 deletions
diff --git a/src/multimedia/CMakeLists.txt b/src/multimedia/CMakeLists.txt
index f60e13be7..47851c296 100644
--- a/src/multimedia/CMakeLists.txt
+++ b/src/multimedia/CMakeLists.txt
@@ -135,6 +135,7 @@ qt_internal_extend_target(Multimedia CONDITION QT_FEATURE_wmf
platform/windows/audio/qwindowsaudiosource.cpp platform/windows/audio/qwindowsaudiosource_p.h
platform/windows/audio/qwindowsaudiosink.cpp platform/windows/audio/qwindowsaudiosink_p.h
platform/windows/audio/qwindowsaudioutils.cpp platform/windows/audio/qwindowsaudioutils_p.h
+ platform/windows/common/mfmetadata.cpp platform/windows/common/mfmetadata_p.h
platform/windows/common/qwindowsmultimediautils.cpp platform/windows/common/qwindowsmultimediautils_p.h
platform/windows/common/qwindowsiupointer_p.h
platform/windows/decoder/mfaudiodecodercontrol.cpp platform/windows/decoder/mfaudiodecodercontrol_p.h
@@ -147,7 +148,6 @@ qt_internal_extend_target(Multimedia CONDITION QT_FEATURE_wmf
platform/windows/mfstream.cpp platform/windows/mfstream_p.h
platform/windows/player/mfactivate.cpp platform/windows/player/mfactivate_p.h
platform/windows/player/mfevrvideowindowcontrol.cpp platform/windows/player/mfevrvideowindowcontrol_p.h
- platform/windows/player/mfmetadata.cpp platform/windows/player/mfmetadata_p.h
platform/windows/player/mfplayercontrol.cpp platform/windows/player/mfplayercontrol_p.h
platform/windows/player/mfplayersession.cpp platform/windows/player/mfplayersession_p.h
platform/windows/player/mftvideo.cpp platform/windows/player/mftvideo_p.h
diff --git a/src/multimedia/platform/windows/player/mfmetadata.cpp b/src/multimedia/platform/windows/common/mfmetadata.cpp
index d64b3fa2f..587d757d6 100644
--- a/src/multimedia/platform/windows/player/mfmetadata.cpp
+++ b/src/multimedia/platform/windows/common/mfmetadata.cpp
@@ -43,9 +43,10 @@
#include <mfapi.h>
#include <mfidl.h>
+#include <propvarutil.h>
+#include <propkey.h>
#include "mfmetadata_p.h"
-#include "Propkey.h"
//#define DEBUG_MEDIAFOUNDATION
@@ -176,21 +177,21 @@ static QVariant convertValue(const PROPVARIANT& var)
return value;
}
-static QVariant metaDataValue(IPropertyStore *m_content, const PROPERTYKEY &key)
+static QVariant metaDataValue(IPropertyStore *content, const PROPERTYKEY &key)
{
QVariant value;
PROPVARIANT var;
PropVariantInit(&var);
HRESULT hr = S_FALSE;
- if (m_content)
- hr = m_content->GetValue(key, &var);
+ if (content)
+ hr = content->GetValue(key, &var);
if (SUCCEEDED(hr)) {
value = convertValue(var);
// some metadata needs to be reformatted
- if (value.isValid() && m_content) {
+ if (value.isValid() && content) {
if (key == PKEY_Media_ClassPrimaryID /*QMediaMetaData::MediaType*/) {
QString v = value.toString();
if (v == QLatin1String("{D1607DBC-E323-4BE2-86A1-48A42A28441E}"))
@@ -211,12 +212,12 @@ static QVariant metaDataValue(IPropertyStore *m_content, const PROPERTYKEY &key)
} else if (key == PKEY_Video_FrameHeight /*Resolution*/) {
QSize res;
res.setHeight(value.toUInt());
- if (m_content && SUCCEEDED(m_content->GetValue(PKEY_Video_FrameWidth, &var)))
+ if (content && SUCCEEDED(content->GetValue(PKEY_Video_FrameWidth, &var)))
res.setWidth(convertValue(var).toUInt());
value = res;
} else if (key == PKEY_Video_Orientation) {
uint orientation = 0;
- if (m_content && SUCCEEDED(m_content->GetValue(PKEY_Video_Orientation, &var)))
+ if (content && SUCCEEDED(content->GetValue(PKEY_Video_Orientation, &var)))
orientation = convertValue(var).toUInt();
value = orientation;
} else if (key == PKEY_Video_FrameRate) {
@@ -233,17 +234,17 @@ QMediaMetaData MFMetaData::fromNative(IMFMediaSource* mediaSource)
{
QMediaMetaData metaData;
- IPropertyStore *m_content = nullptr;
- if (!SUCCEEDED(MFGetService(mediaSource, MF_PROPERTY_HANDLER_SERVICE, IID_PPV_ARGS(&m_content))))
+ IPropertyStore *content = nullptr;
+ if (!SUCCEEDED(MFGetService(mediaSource, MF_PROPERTY_HANDLER_SERVICE, IID_PPV_ARGS(&content))))
return metaData;
- Q_ASSERT(m_content);
+ Q_ASSERT(content);
DWORD cProps;
- if (SUCCEEDED(m_content->GetCount(&cProps))) {
+ if (SUCCEEDED(content->GetCount(&cProps))) {
for (DWORD i = 0; i < cProps; i++)
{
PROPERTYKEY key;
- if (FAILED(m_content->GetAt(i, &key)))
+ if (FAILED(content->GetAt(i, &key)))
continue;
QMediaMetaData::Key mediaKey;
if (key == PKEY_Author) {
@@ -325,11 +326,191 @@ QMediaMetaData MFMetaData::fromNative(IMFMediaSource* mediaSource)
} else {
continue;
}
- metaData.insert(mediaKey, metaDataValue(m_content, key));
+ metaData.insert(mediaKey, metaDataValue(content, key));
}
}
- m_content->Release();
+ content->Release();
return metaData;
}
+
+static REFPROPERTYKEY propertyKeyForMetaDataKey(QMediaMetaData::Key key)
+{
+ switch (key) {
+ case QMediaMetaData::Key::Title:
+ return PKEY_Title;
+ case QMediaMetaData::Key::Author:
+ return PKEY_Author;
+ case QMediaMetaData::Key::Comment:
+ return PKEY_Comment;
+ case QMediaMetaData::Key::Genre:
+ return PKEY_Music_Genre;
+ case QMediaMetaData::Key::Copyright:
+ return PKEY_Copyright;
+ case QMediaMetaData::Key::Publisher:
+ return PKEY_Media_Publisher;
+ case QMediaMetaData::Key::Url:
+ return PKEY_Media_AuthorUrl;
+ case QMediaMetaData::Key::AlbumTitle:
+ return PKEY_Music_AlbumTitle;
+ case QMediaMetaData::Key::AlbumArtist:
+ return PKEY_Music_AlbumArtist;
+ case QMediaMetaData::Key::TrackNumber:
+ return PKEY_Music_TrackNumber;
+ case QMediaMetaData::Key::Date:
+ return PKEY_Media_DateEncoded;
+ case QMediaMetaData::Key::Composer:
+ return PKEY_Music_Composer;
+ case QMediaMetaData::Key::Duration:
+ return PKEY_Media_Duration;
+ case QMediaMetaData::Key::Language:
+ return PKEY_Language;
+ case QMediaMetaData::Key::Description:
+ return PKEY_Media_EncodingSettings;
+ case QMediaMetaData::Key::AudioBitRate:
+ return PKEY_Audio_EncodingBitrate;
+ case QMediaMetaData::Key::ContributingArtist:
+ return PKEY_Music_Artist;
+ case QMediaMetaData::Key::ThumbnailImage:
+ return PKEY_ThumbnailStream;
+ case QMediaMetaData::Key::Orientation:
+ return PKEY_Video_Orientation;
+ case QMediaMetaData::Key::VideoFrameRate:
+ return PKEY_Video_FrameRate;
+ case QMediaMetaData::Key::VideoBitRate:
+ return PKEY_Video_EncodingBitrate;
+ case QMediaMetaData::MediaType:
+ return PKEY_Media_ClassPrimaryID;
+ default:
+ return PKEY_Null;
+ }
+}
+
+static void setStringProperty(IPropertyStore *content, REFPROPERTYKEY key, const QString &value)
+{
+ PROPVARIANT propValue = {};
+ if (SUCCEEDED(InitPropVariantFromString(reinterpret_cast<LPCWSTR>(value.utf16()), &propValue))) {
+ if (SUCCEEDED(PSCoerceToCanonicalValue(key, &propValue)))
+ content->SetValue(key, propValue);
+ PropVariantClear(&propValue);
+ }
+}
+
+static void setUInt32Property(IPropertyStore *content, REFPROPERTYKEY key, quint32 value)
+{
+ PROPVARIANT propValue = {};
+ if (SUCCEEDED(InitPropVariantFromUInt32(ULONG(value), &propValue))) {
+ if (SUCCEEDED(PSCoerceToCanonicalValue(key, &propValue)))
+ content->SetValue(key, propValue);
+ PropVariantClear(&propValue);
+ }
+}
+
+static void setUInt64Property(IPropertyStore *content, REFPROPERTYKEY key, quint64 value)
+{
+ PROPVARIANT propValue = {};
+ if (SUCCEEDED(InitPropVariantFromUInt64(ULONGLONG(value), &propValue))) {
+ if (SUCCEEDED(PSCoerceToCanonicalValue(key, &propValue)))
+ content->SetValue(key, propValue);
+ PropVariantClear(&propValue);
+ }
+}
+
+static void setFileTimeProperty(IPropertyStore *content, REFPROPERTYKEY key, const FILETIME *ft)
+{
+ PROPVARIANT propValue = {};
+ if (SUCCEEDED(InitPropVariantFromFileTime(ft, &propValue))) {
+ if (SUCCEEDED(PSCoerceToCanonicalValue(key, &propValue)))
+ content->SetValue(key, propValue);
+ PropVariantClear(&propValue);
+ }
+}
+
+void MFMetaData::toNative(const QMediaMetaData &metaData, IPropertyStore *content)
+{
+ if (content) {
+
+ for (const auto &key : metaData.keys()) {
+
+ QVariant value = metaData.value(key);
+
+ if (key == QMediaMetaData::Key::MediaType) {
+
+ QString strValue = metaData.stringValue(key);
+ QString v;
+
+ // Sets property to one of the MediaClassPrimaryID values defined by Microsoft:
+ // https://docs.microsoft.com/en-us/windows/win32/wmformat/wm-mediaprimaryid
+ if (strValue == QLatin1String("Music"))
+ v = QLatin1String("{D1607DBC-E323-4BE2-86A1-48A42A28441E}");
+ else if (strValue == QLatin1String("Video"))
+ v = QLatin1String("{DB9830BD-3AB3-4FAB-8A37-1A995F7FF74B}");
+ else if (strValue == QLatin1String("Audio"))
+ v = QLatin1String("{01CD0F29-DA4E-4157-897B-6275D50C4F11}");
+ else
+ v = QLatin1String("{FCF24A76-9A57-4036-990D-E35DD8B244E1}");
+
+ setStringProperty(content, PKEY_Media_ClassPrimaryID, v);
+
+ } else if (key == QMediaMetaData::Key::Duration) {
+
+ setUInt64Property(content, PKEY_Media_Duration, value.toULongLong() * 10000);
+
+ } else if (key == QMediaMetaData::Key::Resolution) {
+
+ QSize res = value.toSize();
+ setUInt32Property(content, PKEY_Video_FrameWidth, quint32(res.width()));
+ setUInt32Property(content, PKEY_Video_FrameHeight, quint32(res.height()));
+
+ } else if (key == QMediaMetaData::Key::Orientation) {
+
+ setUInt32Property(content, PKEY_Video_Orientation, value.toUInt());
+
+ } else if (key == QMediaMetaData::Key::VideoFrameRate) {
+
+ qreal fps = value.toReal();
+ setUInt32Property(content, PKEY_Video_FrameRate, quint32(fps * 1000));
+
+ } else if (key == QMediaMetaData::Key::TrackNumber) {
+
+ setUInt32Property(content, PKEY_Music_TrackNumber, value.toUInt());
+
+ } else if (key == QMediaMetaData::Key::AudioBitRate) {
+
+ setUInt32Property(content, PKEY_Audio_EncodingBitrate, value.toUInt());
+
+ } else if (key == QMediaMetaData::Key::VideoBitRate) {
+
+ setUInt32Property(content, PKEY_Video_EncodingBitrate, value.toUInt());
+
+ } else if (key == QMediaMetaData::Key::Date) {
+
+ // Convert QDateTime to FILETIME by converting to 100-nsecs since
+ // 01/01/1970 UTC and adding the difference from 1601 to 1970.
+ ULARGE_INTEGER t = {};
+ t.QuadPart = ULONGLONG(value.toDateTime().toUTC().toMSecsSinceEpoch() * 10000
+ + 116444736000000000LL);
+
+ FILETIME ft = {};
+ ft.dwHighDateTime = t.HighPart;
+ ft.dwLowDateTime = t.LowPart;
+
+ setFileTimeProperty(content, PKEY_Media_DateEncoded, &ft);
+
+ } else {
+
+ // By default use as string and let PSCoerceToCanonicalValue()
+ // do validation and type conversion.
+ REFPROPERTYKEY propKey = propertyKeyForMetaDataKey(key);
+
+ if (propKey != PKEY_Null) {
+ QString strValue = metaData.stringValue(key);
+ if (!strValue.isEmpty())
+ setStringProperty(content, propKey, strValue);
+ }
+ }
+ }
+ }
+}
+
diff --git a/src/multimedia/platform/windows/player/mfmetadata_p.h b/src/multimedia/platform/windows/common/mfmetadata_p.h
index 813b42cf3..d1846e9c5 100644
--- a/src/multimedia/platform/windows/player/mfmetadata_p.h
+++ b/src/multimedia/platform/windows/common/mfmetadata_p.h
@@ -60,6 +60,7 @@ class MFMetaData
{
public:
static QMediaMetaData fromNative(IMFMediaSource* mediaSource);
+ static void toNative(const QMediaMetaData &metaData, IPropertyStore *content);
};
#endif
diff --git a/src/multimedia/platform/windows/mediacapture/qwindowsmediaencoder.cpp b/src/multimedia/platform/windows/mediacapture/qwindowsmediaencoder.cpp
index 1ecd8af0b..6018c8974 100644
--- a/src/multimedia/platform/windows/mediacapture/qwindowsmediaencoder.cpp
+++ b/src/multimedia/platform/windows/mediacapture/qwindowsmediaencoder.cpp
@@ -41,9 +41,11 @@
#include "qwindowsmediadevicesession_p.h"
#include "qwindowsmediacapture_p.h"
+#include "mfmetadata_p.h"
#include <QtCore/QUrl>
#include <QtCore/QMimeType>
#include <Mferror.h>
+#include <shobjidl.h>
QT_BEGIN_NAMESPACE
@@ -131,18 +133,18 @@ void QWindowsMediaEncoder::setState(QMediaRecorder::RecorderState state)
const QString path = (m_outputLocation.scheme() == QLatin1String("file") ?
m_outputLocation.path() : m_outputLocation.toString());
- QString fileName = m_storageLocation.generateFileName(path, audioOnly
- ? QWindowsStorageLocation::Audio
- : QWindowsStorageLocation::Video,
- QLatin1String("clip_"),
- m_settings.mimeType().preferredSuffix());
+ m_fileName = m_storageLocation.generateFileName(path, audioOnly
+ ? QWindowsStorageLocation::Audio
+ : QWindowsStorageLocation::Video,
+ QLatin1String("clip_"),
+ m_settings.mimeType().preferredSuffix());
- if (m_mediaDeviceSession->startRecording(fileName, audioOnly)) {
+ if (m_mediaDeviceSession->startRecording(m_fileName, audioOnly)) {
m_state = QMediaRecorder::RecordingState;
m_lastStatus = QMediaRecorder::StartingStatus;
- actualLocationChanged(QUrl::fromLocalFile(fileName));
+ actualLocationChanged(QUrl::fromLocalFile(m_fileName));
stateChanged(m_state);
statusChanged(m_lastStatus);
@@ -203,6 +205,35 @@ void QWindowsMediaEncoder::setCaptureSession(QPlatformMediaCaptureSession *sessi
onCameraChanged();
}
+void QWindowsMediaEncoder::setMetaData(const QMediaMetaData &metaData)
+{
+ m_metaData = metaData;
+}
+
+QMediaMetaData QWindowsMediaEncoder::metaData() const
+{
+ return m_metaData;
+}
+
+void QWindowsMediaEncoder::saveMetadata()
+{
+ if (!m_metaData.isEmpty()) {
+
+ const QString nativeFileName = QDir::toNativeSeparators(m_fileName);
+
+ IPropertyStore *store = nullptr;
+
+ if (SUCCEEDED(SHGetPropertyStoreFromParsingName(reinterpret_cast<LPCWSTR>(nativeFileName.utf16()),
+ nullptr, GPS_READWRITE, IID_PPV_ARGS(&store)))) {
+
+ MFMetaData::toNative(m_metaData, store);
+
+ store->Commit();
+ store->Release();
+ }
+ }
+}
+
void QWindowsMediaEncoder::onDurationChanged(qint64 duration)
{
m_duration = duration;
@@ -235,6 +266,8 @@ void QWindowsMediaEncoder::onRecordingStarted()
void QWindowsMediaEncoder::onRecordingStopped()
{
+ saveMetadata();
+
auto lastState = m_state;
auto lastStatus = m_lastStatus;
m_state = QMediaRecorder::StoppedState;
diff --git a/src/multimedia/platform/windows/mediacapture/qwindowsmediaencoder_p.h b/src/multimedia/platform/windows/mediacapture/qwindowsmediaencoder_p.h
index ffa73b428..315e26e7e 100644
--- a/src/multimedia/platform/windows/mediacapture/qwindowsmediaencoder_p.h
+++ b/src/multimedia/platform/windows/mediacapture/qwindowsmediaencoder_p.h
@@ -79,6 +79,9 @@ public:
void setEncoderSettings(const QMediaEncoderSettings &settings) override;
+ void setMetaData(const QMediaMetaData &metaData) override;
+ QMediaMetaData metaData() const override;
+
void setCaptureSession(QPlatformMediaCaptureSession *session);
public Q_SLOTS:
@@ -92,6 +95,8 @@ private Q_SLOTS:
void onStreamingError(int errorCode);
private:
+ void saveMetadata();
+
QWindowsMediaCaptureService *m_captureService = nullptr;
QWindowsMediaDeviceSession *m_mediaDeviceSession = nullptr;
QUrl m_outputLocation;
@@ -99,6 +104,8 @@ private:
QMediaRecorder::Status m_lastStatus = QMediaRecorder::StoppedStatus;
QMediaEncoderSettings m_settings;
QWindowsStorageLocation m_storageLocation;
+ QString m_fileName;
+ QMediaMetaData m_metaData;
qint64 m_duration = 0;
};
diff --git a/src/multimedia/platform/windows/player/mfplayersession.cpp b/src/multimedia/platform/windows/player/mfplayersession.cpp
index 1fa05c33a..50a022fc3 100644
--- a/src/multimedia/platform/windows/player/mfplayersession.cpp
+++ b/src/multimedia/platform/windows/player/mfplayersession.cpp
@@ -53,7 +53,7 @@
#include "mfplayercontrol_p.h"
#include "mfevrvideowindowcontrol_p.h"
#include "mfvideorenderercontrol_p.h"
-#include "mfmetadata_p.h"
+#include <private/mfmetadata_p.h>
#include "mfplayersession_p.h"
#include <mferror.h>