diff options
author | André de la Rocha <andre.rocha@qt.io> | 2021-07-18 04:02:38 +0200 |
---|---|---|
committer | André de la Rocha <andre.rocha@qt.io> | 2021-07-27 14:06:06 +0200 |
commit | cb488cf23f8967260e2bba36d43d1b5e8ebb49cc (patch) | |
tree | af5acabdb7ce424665d5d9aaad1cfdafaaf8dc58 | |
parent | 7054093fdb8ae9c56fd6488be71af2c5cc4e0c4e (diff) |
Add support for multiple audio tracks to Windows player backend
Change-Id: I342536e89469a6127d8136b8d6cea563c66518bf
Reviewed-by: Lars Knoll <lars.knoll@qt.io>
6 files changed, 303 insertions, 67 deletions
diff --git a/examples/multimediawidgets/player/player.cpp b/examples/multimediawidgets/player/player.cpp index 3005f2e4f..d616fd813 100644 --- a/examples/multimediawidgets/player/player.cpp +++ b/examples/multimediawidgets/player/player.cpp @@ -337,6 +337,26 @@ void Player::metaDataChanged() } } +QString Player::trackName(const QMediaMetaData &metaData, int index) +{ + QString name; + QString title = metaData.stringValue(QMediaMetaData::Title); + QLocale::Language lang = metaData.value(QMediaMetaData::Language).value<QLocale::Language>(); + + if (title.isEmpty()) { + if (lang == QLocale::Language::AnyLanguage) + name = tr("Track %1").arg(index+1); + else + name = QLocale::languageToString(lang); + } else { + if (lang == QLocale::Language::AnyLanguage) + name = title; + else + name = QString("%1 - [%2]").arg(title).arg(QLocale::languageToString(lang)); + } + return name; +} + void Player::tracksChanged() { m_audioTracks->clear(); @@ -345,19 +365,19 @@ void Player::tracksChanged() const auto audioTracks = m_player->audioTracks(); for (int i = 0; i < audioTracks.size(); ++i) - m_audioTracks->addItem(audioTracks.at(i).stringValue(QMediaMetaData::Language), i); + m_audioTracks->addItem(trackName(audioTracks.at(i), i), i); m_audioTracks->setCurrentIndex(m_player->activeAudioTrack()); const auto videoTracks = m_player->videoTracks(); for (int i = 0; i < videoTracks.size(); ++i) - m_videoTracks->addItem(videoTracks.at(i).stringValue(QMediaMetaData::Language), i); + m_videoTracks->addItem(trackName(videoTracks.at(i), i), i); m_videoTracks->setCurrentIndex(m_player->activeVideoTrack()); m_subtitleTracks->addItem(QString::fromUtf8("No subtitles"), -1); const auto subtitleTracks = m_player->subtitleTracks(); for (int i = 0; i < subtitleTracks.size(); ++i) - m_subtitleTracks->addItem(subtitleTracks.at(i).stringValue(QMediaMetaData::Language), i); - m_subtitleTracks->setCurrentIndex(m_player->activeSubtitleTrack()); + m_subtitleTracks->addItem(trackName(subtitleTracks.at(i), i), i); + m_subtitleTracks->setCurrentIndex(m_player->activeSubtitleTrack() + 1); } void Player::previousClicked() @@ -469,7 +489,7 @@ void Player::selectAudioStream() void Player::selectVideoStream() { - int stream = m_audioTracks->currentData().toInt(); + int stream = m_videoTracks->currentData().toInt(); m_player->setActiveVideoTrack(stream); } diff --git a/examples/multimediawidgets/player/player.h b/examples/multimediawidgets/player/player.h index cba34d973..0be2282f8 100644 --- a/examples/multimediawidgets/player/player.h +++ b/examples/multimediawidgets/player/player.h @@ -118,6 +118,7 @@ private: void setStatusInfo(const QString &info); void handleCursor(QMediaPlayer::MediaStatus status); void updateDurationInfo(qint64 currentInfo); + QString trackName(const QMediaMetaData &metaData, int index); QMediaPlayer *m_player = nullptr; QAudioOutput *m_audioOutput = nullptr; diff --git a/src/multimedia/platform/windows/player/mfplayercontrol.cpp b/src/multimedia/platform/windows/player/mfplayercontrol.cpp index 90b6de66f..b114e3264 100644 --- a/src/multimedia/platform/windows/player/mfplayercontrol.cpp +++ b/src/multimedia/platform/windows/player/mfplayercontrol.cpp @@ -175,6 +175,11 @@ void MFPlayerControl::handleStatusChanged() refreshState(); } +void MFPlayerControl::handleTracksChanged() +{ + tracksChanged(); +} + void MFPlayerControl::handleVideoAvailable() { if (m_videoAvailable) @@ -300,3 +305,24 @@ void MFPlayerControl::handleError(QMediaPlayer::Error errorCode, const QString& stop(); emit error(int(errorCode), errorString); } + +void MFPlayerControl::setActiveTrack(TrackType type, int index) +{ + m_session->setActiveTrack(type, index); +} + +int MFPlayerControl::activeTrack(TrackType type) +{ + return m_session->activeTrack(type); +} + +int MFPlayerControl::trackCount(TrackType type) +{ + return m_session->trackCount(type); +} + +QMediaMetaData MFPlayerControl::trackMetaData(TrackType type, int trackNumber) +{ + return m_session->trackMetaData(type, trackNumber); +} + diff --git a/src/multimedia/platform/windows/player/mfplayercontrol_p.h b/src/multimedia/platform/windows/player/mfplayercontrol_p.h index 995181626..e8467ab8d 100644 --- a/src/multimedia/platform/windows/player/mfplayercontrol_p.h +++ b/src/multimedia/platform/windows/player/mfplayercontrol_p.h @@ -103,7 +103,13 @@ public: void setVideoSink(QVideoSink *sink) override; + void setActiveTrack(TrackType type, int index) override; + int activeTrack(TrackType type) override; + int trackCount(TrackType type) override; + QMediaMetaData trackMetaData(TrackType type, int trackNumber) override; + void handleStatusChanged(); + void handleTracksChanged(); void handleVideoAvailable(); void handleAudioAvailable(); void handleDurationUpdate(qint64 duration); diff --git a/src/multimedia/platform/windows/player/mfplayersession.cpp b/src/multimedia/platform/windows/player/mfplayersession.cpp index 619f7b17a..3d01050da 100644 --- a/src/multimedia/platform/windows/player/mfplayersession.cpp +++ b/src/multimedia/platform/windows/player/mfplayersession.cpp @@ -239,6 +239,7 @@ void MFPlayerSession::handleMediaSourceReady() //convert from 100 nanosecond to milisecond emit durationUpdate(qint64(m_duration / 10000)); setupPlaybackTopology(mediaSource, sourcePD); + tracksChanged(); sourcePD->Release(); } else { changeStatus(QMediaPlayer::InvalidMedia); @@ -246,26 +247,49 @@ void MFPlayerSession::handleMediaSourceReady() } } -MFPlayerSession::MediaType MFPlayerSession::getStreamType(IMFStreamDescriptor *stream) const +bool MFPlayerSession::getStreamInfo(IMFStreamDescriptor *stream, + MFPlayerSession::MediaType *type, + QString *name, + QString *language) const { - if (!stream) - return Unknown; - - struct SafeRelease { - IMFMediaTypeHandler *ptr = nullptr; - ~SafeRelease() { if (ptr) ptr->Release(); } - } typeHandler; - if (SUCCEEDED(stream->GetMediaTypeHandler(&typeHandler.ptr))) { + if (!stream || !type || !name || !language) + return false; + + *type = Unknown; + *name = QString(); + *language = QString(); + + IMFMediaTypeHandler *typeHandler = nullptr; + + if (SUCCEEDED(stream->GetMediaTypeHandler(&typeHandler))) { + + UINT32 len = 0; + if (SUCCEEDED(stream->GetStringLength(MF_SD_STREAM_NAME, &len)) && len > 0) { + WCHAR *wstr = new WCHAR[len+1]; + if (SUCCEEDED(stream->GetString(MF_SD_STREAM_NAME, wstr, len+1, &len))) { + *name = QString::fromUtf16(reinterpret_cast<const char16_t *>(wstr)); + } + delete []wstr; + } + if (SUCCEEDED(stream->GetStringLength(MF_SD_LANGUAGE, &len)) && len > 0) { + WCHAR *wstr = new WCHAR[len+1]; + if (SUCCEEDED(stream->GetString(MF_SD_LANGUAGE, wstr, len+1, &len))) { + *language = QString::fromUtf16(reinterpret_cast<const char16_t *>(wstr)); + } + delete []wstr; + } + GUID guidMajorType; - if (SUCCEEDED(typeHandler.ptr->GetMajorType(&guidMajorType))) { + if (SUCCEEDED(typeHandler->GetMajorType(&guidMajorType))) { if (guidMajorType == MFMediaType_Audio) - return Audio; + *type = Audio; else if (guidMajorType == MFMediaType_Video) - return Video; + *type = Video; } + typeHandler->Release(); } - return Unknown; + return *type != Unknown; } void MFPlayerSession::setupPlaybackTopology(IMFMediaSource *source, IMFPresentationDescriptor *sourcePD) @@ -288,64 +312,76 @@ void MFPlayerSession::setupPlaybackTopology(IMFMediaSource *source, IMFPresentat return; } - // Remember output node id for a first video stream - TOPOID outputNodeId = -1; - // For each stream, create the topology nodes and add them to the topology. DWORD succeededCount = 0; - for (DWORD i = 0; i < cSourceStreams; i++) - { - BOOL fSelected = FALSE; + for (DWORD i = 0; i < cSourceStreams; i++) { + BOOL selected = FALSE; bool streamAdded = false; IMFStreamDescriptor *streamDesc = NULL; - HRESULT hr = sourcePD->GetStreamDescriptorByIndex(i, &fSelected, &streamDesc); + HRESULT hr = sourcePD->GetStreamDescriptorByIndex(i, &selected, &streamDesc); if (SUCCEEDED(hr)) { // The media might have multiple audio and video streams, // only use one of each kind, and only if it is selected by default. - MediaType mediaType = getStreamType(streamDesc); - if (mediaType != Unknown - && ((m_mediaTypes & mediaType) == 0) // Check if this type isn't already added - && fSelected) { - - IMFTopologyNode *sourceNode = addSourceNode(topology, source, sourcePD, streamDesc); - if (sourceNode) { - IMFTopologyNode *outputNode = addOutputNode(mediaType, topology, 0); - if (outputNode) { - bool connected = false; - if (mediaType == Audio) { - if (!m_audioSampleGrabberNode) - connected = setupAudioSampleGrabber(topology, sourceNode, outputNode); - } else if (mediaType == Video && outputNodeId == -1) { - // Remember video output node ID. - outputNode->GetTopoNodeID(&outputNodeId); - } - - if (!connected) - hr = sourceNode->ConnectOutput(0, outputNode, 0); - - if (FAILED(hr)) { - emit error(QMediaPlayer::FormatError, tr("Unable to play any stream."), false); - } else { - streamAdded = true; - succeededCount++; - m_mediaTypes |= mediaType; - switch (mediaType) { - case Audio: - emit audioAvailable(); - break; - case Video: - emit videoAvailable(); - break; + MediaType mediaType = Unknown; + QString streamName; + QString streamLanguage; + + if (getStreamInfo(streamDesc, &mediaType, &streamName, &streamLanguage)) { + + QPlatformMediaPlayer::TrackType trackType = (mediaType == Audio) ? + QPlatformMediaPlayer::AudioStream : QPlatformMediaPlayer::VideoStream; + + QLocale::Language lang = streamLanguage.isEmpty() ? + QLocale::Language::AnyLanguage : QLocale(streamLanguage).language(); + + QMediaMetaData metaData; + metaData.insert(QMediaMetaData::Title, streamName); + metaData.insert(QMediaMetaData::Language, lang); + + m_trackInfo[trackType].metaData.append(metaData); + m_trackInfo[trackType].nativeIndexes.append(i); + + if (((m_mediaTypes & mediaType) == 0) && selected) { // Check if this type isn't already added + IMFTopologyNode *sourceNode = addSourceNode(topology, source, sourcePD, streamDesc); + if (sourceNode) { + IMFTopologyNode *outputNode = addOutputNode(mediaType, topology, 0); + if (outputNode) { + bool connected = false; + if (mediaType == Audio) { + if (!m_audioSampleGrabberNode) + connected = setupAudioSampleGrabber(topology, sourceNode, outputNode); } + sourceNode->GetTopoNodeID(&m_trackInfo[trackType].sourceNodeId); + outputNode->GetTopoNodeID(&m_trackInfo[trackType].outputNodeId); + + if (!connected) + hr = sourceNode->ConnectOutput(0, outputNode, 0); + + if (FAILED(hr)) { + emit error(QMediaPlayer::FormatError, tr("Unable to play any stream."), false); + } else { + m_trackInfo[trackType].currentIndex = m_trackInfo[trackType].nativeIndexes.count() - 1; + streamAdded = true; + succeededCount++; + m_mediaTypes |= mediaType; + switch (mediaType) { + case Audio: + emit audioAvailable(); + break; + case Video: + emit videoAvailable(); + break; + } + } + outputNode->Release(); } - outputNode->Release(); + sourceNode->Release(); } - sourceNode->Release(); } } - if (fSelected && !streamAdded) + if (selected && !streamAdded) sourcePD->DeselectStream(i); streamDesc->Release(); @@ -356,12 +392,13 @@ void MFPlayerSession::setupPlaybackTopology(IMFMediaSource *source, IMFPresentat changeStatus(QMediaPlayer::InvalidMedia); emit error(QMediaPlayer::ResourceError, tr("Unable to play."), true); } else { - if (outputNodeId != -1) { - topology = insertMFT(topology, outputNodeId); - } + if (m_trackInfo[QPlatformMediaPlayer::VideoStream].outputNodeId != -1) + topology = insertMFT(topology, m_trackInfo[QPlatformMediaPlayer::VideoStream].outputNodeId); hr = m_session->SetTopology(MFSESSION_SETTOPOLOGY_IMMEDIATE, topology); - if (FAILED(hr)) { + if (SUCCEEDED(hr)) { + m_updatingTopology = true; + } else { changeStatus(QMediaPlayer::InvalidMedia); emit error(QMediaPlayer::ResourceError, tr("Failed to set topology."), true); } @@ -982,6 +1019,7 @@ void MFPlayerSession::stop(bool immediate) scrub(false); if (SUCCEEDED(m_session->Stop())) { + m_state.setCommand(CmdStop); m_pendingState = CmdPending; if (m_status != QMediaPlayer::EndOfMedia) { @@ -1011,6 +1049,12 @@ void MFPlayerSession::start() if (m_scrubbing) scrub(false); + if (m_restorePosition >= 0) { + m_position = m_restorePosition; + if (!m_updatingTopology) + m_restorePosition = -1; + } + PROPVARIANT varStart; InitPropVariantFromInt64(m_position, &varStart); @@ -1586,6 +1630,7 @@ void MFPlayerSession::handleSessionEvent(IMFMediaEvent *sessionEvent) updatePendingCommands(CmdStop); break; case MESessionPaused: + m_position = position() * 10000; updatePendingCommands(CmdPause); break; case MEReconnectStart: @@ -1690,6 +1735,9 @@ void MFPlayerSession::handleSessionEvent(IMFMediaEvent *sessionEvent) if (SUCCEEDED(MFGetService(m_session, MR_STREAM_VOLUME_SERVICE, IID_PPV_ARGS(&m_volumeControl)))) setVolumeInternal(m_muted ? 0 : m_volume); + + m_updatingTopology = false; + stop(); } } } @@ -1772,6 +1820,14 @@ void MFPlayerSession::clear() m_request.command = CmdNone; m_request.prevCmd = CmdNone; + for (int i = 0; i < QPlatformMediaPlayer::NTrackTypes; ++i) { + m_trackInfo[i].metaData.clear(); + m_trackInfo[i].nativeIndexes.clear(); + m_trackInfo[i].currentIndex = -1; + m_trackInfo[i].sourceNodeId = -1; + m_trackInfo[i].outputNodeId = -1; + } + if (!m_metaData.isEmpty()) { m_metaData.clear(); emit metaDataChanged(); @@ -1826,3 +1882,112 @@ void MFPlayerSession::setVideoSink(QVideoSink *sink) { m_videoRendererControl->setSink(sink); } + +void MFPlayerSession::setActiveTrack(QPlatformMediaPlayer::TrackType type, int index) +{ + if (!m_session) + return; + + // Only audio track selection is currently supported. + if (type != QPlatformMediaPlayer::AudioStream) + return; + + const auto &nativeIndexes = m_trackInfo[type].nativeIndexes; + + if (index < -1 || index >= nativeIndexes.count() || index == m_trackInfo[type].currentIndex) + return; + + IMFTopology *topology = nullptr; + + if (SUCCEEDED(m_session->GetFullTopology(MFSESSION_GETFULLTOPOLOGY_CURRENT, 0, &topology))) { + + m_restorePosition = position() * 10000; + + if (m_state.command == CmdStart) + stop(); + + if (m_trackInfo[type].outputNodeId != -1) { + IMFTopologyNode *node = nullptr; + if (SUCCEEDED(topology->GetNodeByID(m_trackInfo[type].outputNodeId, &node))) { + topology->RemoveNode(node); + node->Release(); + m_trackInfo[type].outputNodeId = -1; + } + } + if (m_trackInfo[type].sourceNodeId != -1) { + IMFTopologyNode *node = nullptr; + if (SUCCEEDED(topology->GetNodeByID(m_trackInfo[type].sourceNodeId, &node))) { + topology->RemoveNode(node); + node->Release(); + m_trackInfo[type].sourceNodeId = -1; + } + } + + IMFMediaSource *mediaSource = m_sourceResolver->mediaSource(); + + IMFPresentationDescriptor *sourcePD = nullptr; + if (SUCCEEDED(mediaSource->CreatePresentationDescriptor(&sourcePD))) { + + if (m_trackInfo[type].currentIndex >= 0 && m_trackInfo[type].currentIndex < nativeIndexes.count()) + sourcePD->DeselectStream(nativeIndexes.at(m_trackInfo[type].currentIndex)); + + m_trackInfo[type].currentIndex = index; + + if (index == -1) { + m_session->SetTopology(MFSESSION_SETTOPOLOGY_IMMEDIATE, topology); + } else { + int nativeIndex = nativeIndexes.at(index); + sourcePD->SelectStream(nativeIndex); + + IMFStreamDescriptor *streamDesc = nullptr; + BOOL selected = FALSE; + + if (SUCCEEDED(sourcePD->GetStreamDescriptorByIndex(nativeIndex, &selected, &streamDesc))) { + IMFTopologyNode *sourceNode = addSourceNode(topology, mediaSource, sourcePD, streamDesc); + if (sourceNode) { + IMFTopologyNode *outputNode = addOutputNode(MFPlayerSession::Audio, topology, 0); + if (outputNode) { + if (SUCCEEDED(sourceNode->ConnectOutput(0, outputNode, 0))) { + sourceNode->GetTopoNodeID(&m_trackInfo[type].sourceNodeId); + outputNode->GetTopoNodeID(&m_trackInfo[type].outputNodeId); + m_session->SetTopology(MFSESSION_SETTOPOLOGY_IMMEDIATE, topology); + } + outputNode->Release(); + } + sourceNode->Release(); + } + streamDesc->Release(); + } + } + m_updatingTopology = true; + sourcePD->Release(); + } + topology->Release(); + } +} + +int MFPlayerSession::activeTrack(QPlatformMediaPlayer::TrackType type) +{ + if (type < 0 || type >= QPlatformMediaPlayer::NTrackTypes) + return -1; + return m_trackInfo[type].currentIndex; +} + +int MFPlayerSession::trackCount(QPlatformMediaPlayer::TrackType type) +{ + if (type < 0 || type >= QPlatformMediaPlayer::NTrackTypes) + return -1; + return m_trackInfo[type].metaData.count(); +} + +QMediaMetaData MFPlayerSession::trackMetaData(QPlatformMediaPlayer::TrackType type, int trackNumber) +{ + if (type < 0 || type >= QPlatformMediaPlayer::NTrackTypes) + return {}; + + if (trackNumber < 0 || trackNumber >= m_trackInfo[type].metaData.count()) + return {}; + + return m_trackInfo[type].metaData.at(trackNumber); +} + diff --git a/src/multimedia/platform/windows/player/mfplayersession_p.h b/src/multimedia/platform/windows/player/mfplayersession_p.h index 18a4b02fa..621fe4f26 100644 --- a/src/multimedia/platform/windows/player/mfplayersession_p.h +++ b/src/multimedia/platform/windows/player/mfplayersession_p.h @@ -126,7 +126,13 @@ public: void setVideoSink(QVideoSink *sink); + void setActiveTrack(QPlatformMediaPlayer::TrackType type, int index); + int activeTrack(QPlatformMediaPlayer::TrackType type); + int trackCount(QPlatformMediaPlayer::TrackType); + QMediaMetaData trackMetaData(QPlatformMediaPlayer::TrackType type, int trackNumber); + void statusChanged() { if (m_playerControl) m_playerControl->handleStatusChanged(); } + void tracksChanged() { if (m_playerControl) m_playerControl->handleTracksChanged(); } void audioAvailable() { if (m_playerControl) m_playerControl->handleAudioAvailable(); } void videoAvailable() { if (m_playerControl) m_playerControl->handleVideoAvailable(); } void durationUpdate(qint64 duration) { if (m_playerControl) m_playerControl->handleDurationUpdate(duration); } @@ -160,7 +166,9 @@ private: IMFAudioStreamVolume *m_volumeControl; IPropertyStore *m_netsourceStatistics; qint64 m_position = 0; + qint64 m_restorePosition = -1; UINT64 m_duration = 0; + bool m_updatingTopology = false; enum Command { @@ -219,6 +227,16 @@ private: float m_pendingRate; void updatePendingCommands(Command command); + struct TrackInfo + { + QList<QMediaMetaData> metaData; + QList<int> nativeIndexes; + int currentIndex = -1; + TOPOID sourceNodeId = -1; + TOPOID outputNodeId = -1; + }; + TrackInfo m_trackInfo[QPlatformMediaPlayer::NTrackTypes]; + QMediaPlayer::MediaStatus m_status; bool m_canScrub; float m_volume = 1.; @@ -231,7 +249,7 @@ private: void createSession(); void setupPlaybackTopology(IMFMediaSource *source, IMFPresentationDescriptor *sourcePD); - MediaType getStreamType(IMFStreamDescriptor *stream) const; + bool getStreamInfo(IMFStreamDescriptor *stream, MFPlayerSession::MediaType *type, QString *name, QString *language) const; IMFTopologyNode* addSourceNode(IMFTopology* topology, IMFMediaSource* source, IMFPresentationDescriptor* presentationDesc, IMFStreamDescriptor *streamDesc); IMFTopologyNode* addOutputNode(MediaType mediaType, IMFTopology* topology, DWORD sinkID); |