Bug 1808804 - part2 : handle the MF CDM process crash in the external state machine. r=jolin

This patch makes the external state machine be able to recover playback
when the MF CDM process crash.

In addition, one of the important thing is that, the external state
machine would need to recreate the MFMediaEngineParent/Child actor
first, before the media format reader recreate the remote decoder.

Otherwise, the new created remote decoder won't be able to find the
correct media engine stream, which would be created after finishing
the initializtion of the engine parent actor and the media source.

Differential Revision: https://phabricator.services.mozilla.com/D166411
This commit is contained in:
alwu 2023-01-17 20:00:47 +00:00
Родитель 470b9ff545
Коммит d7d8b580d4
6 изменённых файлов: 105 добавлений и 13 удалений

Просмотреть файл

@ -66,6 +66,7 @@ const char* ExternalEngineStateMachine::StateToStr(State aNextState) {
STATE_TO_STR(RunningEngine);
STATE_TO_STR(SeekingData);
STATE_TO_STR(ShutdownEngine);
STATE_TO_STR(RecoverEngine);
default:
MOZ_ASSERT_UNREACHABLE("Undefined state!");
return "Undefined";
@ -86,13 +87,18 @@ void ExternalEngineStateMachine::ChangeStateTo(State aNextState) {
MOZ_ASSERT_IF(mState.IsReadingMetadata(),
aNextState == State::RunningEngine ||
aNextState == State::ShutdownEngine);
MOZ_ASSERT_IF(
mState.IsRunningEngine(),
aNextState == State::SeekingData || aNextState == State::ShutdownEngine);
MOZ_ASSERT_IF(mState.IsRunningEngine(),
aNextState == State::SeekingData ||
aNextState == State::ShutdownEngine ||
aNextState == State::RecoverEngine);
MOZ_ASSERT_IF(mState.IsSeekingData(),
aNextState == State::RunningEngine ||
aNextState == State::ShutdownEngine);
aNextState == State::ShutdownEngine ||
aNextState == State::RecoverEngine);
MOZ_ASSERT_IF(mState.IsShutdownEngine(), aNextState == State::ShutdownEngine);
MOZ_ASSERT_IF(mState.IsRecoverEngine(),
aNextState == State::RunningEngine ||
aNextState == State::ShutdownEngine);
if (aNextState == State::SeekingData) {
mState = StateObject({StateObject::SeekingData()});
} else if (aNextState == State::ReadingMetadata) {
@ -101,6 +107,8 @@ void ExternalEngineStateMachine::ChangeStateTo(State aNextState) {
mState = StateObject({StateObject::RunningEngine()});
} else if (aNextState == State::ShutdownEngine) {
mState = StateObject({StateObject::ShutdownEngine()});
} else if (aNextState == State::RecoverEngine) {
mState = StateObject({StateObject::RecoverEngine()});
} else {
MOZ_ASSERT_UNREACHABLE("Wrong state!");
}
@ -111,6 +119,11 @@ ExternalEngineStateMachine::ExternalEngineStateMachine(
: MediaDecoderStateMachineBase(aDecoder, aReader) {
LOG("Created ExternalEngineStateMachine");
MOZ_ASSERT(mState.IsInitEngine());
InitEngine();
}
void ExternalEngineStateMachine::InitEngine() {
MOZ_ASSERT(mState.IsInitEngine() || mState.IsRecoverEngine());
#ifdef MOZ_WMF_MEDIA_ENGINE
mEngine.reset(new MFMediaEngineWrapper(this, mFrameStats));
#endif
@ -129,21 +142,27 @@ void ExternalEngineStateMachine::OnEngineInitSuccess() {
AssertOnTaskQueue();
AUTO_PROFILER_LABEL("ExternalEngineStateMachine::OnEngineInitSuccess",
MEDIA_PLAYBACK);
MOZ_ASSERT(mState.IsInitEngine());
LOG("Initialized the external playback engine %" PRIu64
", start reading metadata",
mEngine->Id());
MOZ_ASSERT(mState.IsInitEngine() || mState.IsRecoverEngine());
LOG("Initialized the external playback engine %" PRIu64, mEngine->Id());
auto* state = mState.AsInitEngine();
state->mEngineInitRequest.Complete();
mReader->UpdateMediaEngineId(mEngine->Id());
state->mInitPromise = nullptr;
ChangeStateTo(State::ReadingMetadata);
ReadMetadata();
if (mState.IsInitEngine()) {
ChangeStateTo(State::ReadingMetadata);
ReadMetadata();
return;
}
// We just recovered from CDM process crash, so we need to update the media
// info to the new CDM process.
MOZ_ASSERT(mInfo);
mEngine->SetMediaInfo(*mInfo);
StartRunningEngine();
}
void ExternalEngineStateMachine::OnEngineInitFailure() {
AssertOnTaskQueue();
MOZ_ASSERT(mState.IsInitEngine());
MOZ_ASSERT(mState.IsInitEngine() || mState.IsRecoverEngine());
LOGE("Failed to initialize the external playback engine");
auto* state = mState.AsInitEngine();
state->mEngineInitRequest.Complete();
@ -691,6 +710,10 @@ void ExternalEngineStateMachine::OnRequestAudio() {
LOG("Reach to the end, no more audio data");
EndOfStream(MediaData::Type::AUDIO_DATA);
break;
case NS_ERROR_DOM_MEDIA_REMOTE_DECODER_CRASHED_MF_CDM_ERR:
// We will handle the process crash in `NotifyErrorInternal()`
// so here just silently ignore this.
break;
default:
DecodeError(aError);
}
@ -763,6 +786,10 @@ void ExternalEngineStateMachine::OnRequestVideo() {
LOG("Reach to the end, no more video data");
EndOfStream(MediaData::Type::VIDEO_DATA);
break;
case NS_ERROR_DOM_MEDIA_REMOTE_DECODER_CRASHED_MF_CDM_ERR:
// We will handle the process crash in `NotifyErrorInternal()`
// so here just silently ignore this.
break;
default:
DecodeError(aError);
}
@ -952,11 +979,34 @@ void ExternalEngineStateMachine::NotifyErrorInternal(
// to use our own state machine again.
DecodeError(
MediaResult(NS_ERROR_DOM_MEDIA_EXTERNAL_ENGINE_NOT_SUPPORTED_ERR));
} else if (aError == NS_ERROR_DOM_MEDIA_REMOTE_DECODER_CRASHED_MF_CDM_ERR) {
RecoverFromCDMProcessCrashIfNeeded();
} else {
DecodeError(aError);
}
}
void ExternalEngineStateMachine::RecoverFromCDMProcessCrashIfNeeded() {
AssertOnTaskQueue();
LOG("CDM process has crashed, recover the engine again");
if (mState.IsRecoverEngine()) {
return;
}
ChangeStateTo(State::RecoverEngine);
if (HasVideo()) {
mVideoDataRequest.DisconnectIfExists();
mVideoWaitRequest.DisconnectIfExists();
}
if (HasAudio()) {
mAudioDataRequest.DisconnectIfExists();
mAudioWaitRequest.DisconnectIfExists();
}
// Ask the reader to shutdown current decoders which are no longer available
// due to the remote process crash.
mReader->ReleaseResources();
InitEngine();
}
media::TimeUnit ExternalEngineStateMachine::GetVideoThreshold() {
AssertOnTaskQueue();
if (auto* state = mState.AsSeekingData()) {

Просмотреть файл

@ -105,6 +105,7 @@ class ExternalEngineStateMachine final
RunningEngine,
SeekingData,
ShutdownEngine,
RecoverEngine,
};
struct InitEngine {
InitEngine() = default;
@ -159,6 +160,9 @@ class ExternalEngineStateMachine final
struct ShutdownEngine {
RefPtr<ShutdownPromise> mShutdown;
};
// This state is used to recover the media engine after the MF CDM process
// crashes.
struct RecoverEngine : public InitEngine {};
StateObject() : mData(InitEngine()), mName(State::InitEngine){};
explicit StateObject(ReadingMetadata&& aArg)
@ -169,15 +173,24 @@ class ExternalEngineStateMachine final
: mData(std::move(aArg)), mName(State::SeekingData){};
explicit StateObject(ShutdownEngine&& aArg)
: mData(std::move(aArg)), mName(State::ShutdownEngine){};
explicit StateObject(RecoverEngine&& aArg)
: mData(std::move(aArg)), mName(State::RecoverEngine){};
bool IsInitEngine() const { return mData.is<InitEngine>(); }
bool IsReadingMetadata() const { return mData.is<ReadingMetadata>(); }
bool IsRunningEngine() const { return mData.is<RunningEngine>(); }
bool IsSeekingData() const { return mData.is<SeekingData>(); }
bool IsShutdownEngine() const { return mData.is<ShutdownEngine>(); }
bool IsRecoverEngine() const { return mData.is<RecoverEngine>(); }
InitEngine* AsInitEngine() {
return IsInitEngine() ? &mData.as<InitEngine>() : nullptr;
if (IsInitEngine()) {
return &mData.as<InitEngine>();
}
if (IsRecoverEngine()) {
return &mData.as<RecoverEngine>();
}
return nullptr;
}
ReadingMetadata* AsReadingMetadata() {
return IsReadingMetadata() ? &mData.as<ReadingMetadata>() : nullptr;
@ -190,7 +203,7 @@ class ExternalEngineStateMachine final
}
Variant<InitEngine, ReadingMetadata, RunningEngine, SeekingData,
ShutdownEngine>
ShutdownEngine, RecoverEngine>
mData;
State mName;
} mState;
@ -213,6 +226,7 @@ class ExternalEngineStateMachine final
void SetCanPlayThrough(bool aCanPlayThrough) override {}
void SetFragmentEndTime(const media::TimeUnit& aFragmentEndTime) override {}
void InitEngine();
void OnEngineInitSuccess();
void OnEngineInitFailure();
@ -262,6 +276,8 @@ class ExternalEngineStateMachine final
void UpdateSecondaryVideoContainer() override;
void RecoverFromCDMProcessCrashIfNeeded();
UniquePtr<ExternalPlaybackEngine> mEngine;
bool mHasEnoughAudio = false;

Просмотреть файл

@ -2439,6 +2439,18 @@ void MediaFormatReader::Update(TrackType aTrack) {
NS_ERROR_DOM_MEDIA_REMOTE_DECODER_CRASHED_UTILITY_ERR)) {
needsNewDecoder = true;
}
// For MF CDM crash, it needs to be handled differently. We need to shutdown
// current decoder and report that error to the state machine in order to
// let it to determine if playback can keep going or not.
if (decoder.mError.ref() ==
NS_ERROR_DOM_MEDIA_REMOTE_DECODER_CRASHED_MF_CDM_ERR) {
LOG("Error: notify MF CDM crash and shutdown %s decoder",
TrackTypeToStr(aTrack));
ShutdownDecoder(aTrack);
decoder.RejectPromise(decoder.mError.ref(), __func__);
decoder.mError.reset();
return;
}
#ifdef XP_LINUX
// We failed to decode on Linux with HW decoder,
// give it another try without HW decoder.
@ -2472,6 +2484,7 @@ void MediaFormatReader::Update(TrackType aTrack) {
NotifyError(aTrack, decoder.mError.ref());
return;
}
if (firstFrameDecodingFailedWithHardware) {
decoder.mHardwareDecodingDisabled = true;
}

Просмотреть файл

@ -513,6 +513,9 @@ class MediaFormatReader final
// TODO: Telemetry?
return tooManyConsecutiveCrashes ||
StaticPrefs::media_playback_warnings_as_errors();
} else if (mError.ref() ==
NS_ERROR_DOM_MEDIA_REMOTE_DECODER_CRASHED_MF_CDM_ERR) {
return false;
} else {
// All other error types are fatal
return true;

Просмотреть файл

@ -202,14 +202,22 @@ void MFMediaEngineChild::OwnerDestroyed() {
void MFMediaEngineChild::IPDLActorDestroyed() {
AssertOnManagerThread();
if (!mShutdown) {
CLOG("Destroyed actor without shutdown, remote process has crashed!");
mOwner->NotifyError(NS_ERROR_DOM_MEDIA_REMOTE_DECODER_CRASHED_MF_CDM_ERR);
}
mIPDLSelfRef = nullptr;
}
void MFMediaEngineChild::Shutdown() {
AssertOnManagerThread();
if (mShutdown) {
return;
}
SendShutdown();
mInitPromiseHolder.RejectIfExists(NS_ERROR_FAILURE, __func__);
mInitEngineRequest.DisconnectIfExists();
mShutdown = true;
}
MFMediaEngineWrapper::MFMediaEngineWrapper(ExternalEngineStateMachine* aOwner,

Просмотреть файл

@ -71,6 +71,8 @@ class MFMediaEngineChild final : public PMFMediaEngineChild {
// This is guaranteed always being alive in our lifetime.
NotNull<FrameStatistics*> const MOZ_NON_OWNING_REF mFrameStats;
bool mShutdown = false;
};
/**