Bug 1970948 - ensure SPS is always presented in the H264 bytestream for IDR frames. r=media-playback-reviewers,karlt a=dmeehan

The error SPR_E_INVALID_H264_SLICE_HEADERS is caused by the media engine being
unable to handle an IDR frame without a valid SPS. Therefore, we ensure that SPS
should always be presented in the bytestream for all IDR frames.

Differential Revision: https://phabricator.services.mozilla.com/D252925
This commit is contained in:
alwu
2025-06-10 03:55:00 +00:00
committed by dmeehan@mozilla.com
parent f3267d9d24
commit ee67e078b7
4 changed files with 44 additions and 27 deletions

View File

@@ -414,10 +414,11 @@ already_AddRefed<MediaRawData> MP4TrackDemuxer::GetNextSample() {
if (mType == kH264 && !sample->mCrypto.IsEncrypted()) {
H264::FrameType type = H264::GetFrameType(sample);
switch (type) {
case H264::FrameType::I_FRAME:
[[fallthrough]];
case H264::FrameType::I_FRAME_IDR:
case H264::FrameType::I_FRAME_OTHER:
case H264::FrameType::OTHER: {
bool keyframe = type == H264::FrameType::I_FRAME;
bool keyframe = type == H264::FrameType::I_FRAME_OTHER ||
type == H264::FrameType::I_FRAME_IDR;
if (sample->mKeyframe != keyframe) {
NS_WARNING(nsPrintfCString("Frame incorrectly marked as %skeyframe "
"@ pts:%" PRId64 " dur:%" PRId64

View File

@@ -958,18 +958,18 @@ uint32_t H264::ComputeMaxRefFrames(const mozilla::MediaByteBuffer* aExtraData) {
int8_t nalType = AssertedCast<int8_t>(*p & 0x1f);
if (nalType == H264_NAL_IDR_SLICE) {
// IDR NAL.
return FrameType::I_FRAME;
return FrameType::I_FRAME_IDR;
}
if (nalType == H264_NAL_SEI) {
RefPtr<mozilla::MediaByteBuffer> decodedNAL = DecodeNALUnit(p, nalLen);
SEIRecoveryData data;
if (DecodeRecoverySEI(decodedNAL, data)) {
return FrameType::I_FRAME;
return FrameType::I_FRAME_OTHER;
}
} else if (nalType == H264_NAL_SLICE) {
RefPtr<mozilla::MediaByteBuffer> decodedNAL = DecodeNALUnit(p, nalLen);
if (DecodeISlice(decodedNAL)) {
return FrameType::I_FRAME;
return FrameType::I_FRAME_OTHER;
}
}
}

View File

@@ -516,7 +516,14 @@ class H264 {
const mozilla::MediaByteBuffer* aExtraData);
enum class FrameType {
I_FRAME,
// IDR is a special iframe, according to the spec T-REC-H.264-202408, 3.69 :
// "An IDR picture causes the decoding process to mark all reference
// pictures as "unused for reference" immediately after the decoding of the
// IDR picture. All coded pictures that follow an IDR picture in decoding
// order can be decoded without inter prediction from any picture that
// precedes the IDR picture in decoding order."
I_FRAME_IDR,
I_FRAME_OTHER,
OTHER,
INVALID,
};

View File

@@ -74,10 +74,17 @@ static bool IsBeingProfiledOrLogEnabled() {
class H264ChangeMonitor : public MediaChangeMonitor::CodecChangeMonitor {
public:
explicit H264ChangeMonitor(const VideoInfo& aInfo, bool aFullParsing)
: mCurrentConfig(aInfo), mFullParsing(aFullParsing) {
explicit H264ChangeMonitor(const CreateDecoderParams& aParams)
: mCurrentConfig(aParams.VideoConfig()),
mFullParsing(aParams.mOptions.contains(
CreateDecoderParams::Option::FullH264Parsing))
#ifdef MOZ_WMF_MEDIA_ENGINE
,
mIsMediaEnginePlayback(aParams.mMediaEngineId.isSome())
#endif
{
if (CanBeInstantiated()) {
UpdateConfigFromExtraData(aInfo.mExtraData);
UpdateConfigFromExtraData(mCurrentConfig.mExtraData);
auto avcc = AVCCConfig::Parse(mCurrentConfig.mExtraData);
if (avcc.isOk() && avcc.unwrap().NALUSize() != 4) {
// `CheckForChange()` will use `AnnexB::ConvertSampleToAVCC()` to change
@@ -169,14 +176,21 @@ class H264ChangeMonitor : public MediaChangeMonitor::CodecChangeMonitor {
aSample->mTrackInfo = mTrackInfo;
bool appendExtradata = aNeedKeyFrame;
if (aSample->mCrypto.IsEncrypted() && !mReceivedFirstEncryptedSample) {
LOG("Detected first encrypted sample [%" PRId64 ",%" PRId64
"], keyframe=%d",
aSample->mTime.ToMicroseconds(),
aSample->GetEndTime().ToMicroseconds(), aSample->mKeyframe);
mReceivedFirstEncryptedSample = true;
appendExtradata = true;
#ifdef MOZ_WMF_MEDIA_ENGINE
// The error SPR_E_INVALID_H264_SLICE_HEADERS is caused by the media engine
// being unable to handle an IDR frame without a valid SPS. Therefore, we
// ensure that SPS should always be presented in the bytestream for all IDR
// frames.
if (mIsMediaEnginePlayback &&
H264::GetFrameType(aSample) == H264::FrameType::I_FRAME_IDR) {
RefPtr<MediaByteBuffer> extradata = H264::ExtractExtraData(aSample);
appendExtradata = aNeedKeyFrame || !H264::HasSPS(extradata);
LOG("%s need to append extradata for IDR sample [%" PRId64 ",%" PRId64
"]",
appendExtradata ? "Do" : "No", aSample->mTime.ToMicroseconds(),
aSample->GetEndTime().ToMicroseconds());
}
#endif
if (aConversion == MediaDataDecoder::ConversionRequired::kNeedAnnexB) {
auto res = AnnexB::ConvertAVCCSampleToAnnexB(aSample, appendExtradata);
@@ -189,8 +203,6 @@ class H264ChangeMonitor : public MediaChangeMonitor::CodecChangeMonitor {
return NS_OK;
}
void Flush() override { mReceivedFirstEncryptedSample = false; }
private:
void UpdateConfigFromExtraData(MediaByteBuffer* aExtraData) {
SPSData spsdata;
@@ -224,14 +236,13 @@ class H264ChangeMonitor : public MediaChangeMonitor::CodecChangeMonitor {
VideoInfo mCurrentConfig;
uint32_t mStreamID = 0;
const bool mFullParsing;
#ifdef MOZ_WMF_MEDIA_ENGINE
// True if the playback is performed by Windows Media Foundation Engine.
const bool mIsMediaEnginePlayback;
#endif
bool mGotSPS = false;
RefPtr<TrackInfoSharedPtr> mTrackInfo;
RefPtr<MediaByteBuffer> mPreviousExtraData;
// This ensures the first encrypted sample always includes all necessary
// information for decoding, as some decoders, such as MediaEngine, require
// SPS/PPS to be appended during the clearlead-to-encrypted transition.
bool mReceivedFirstEncryptedSample = false;
};
class HEVCChangeMonitor : public MediaChangeMonitor::CodecChangeMonitor {
@@ -820,9 +831,7 @@ RefPtr<PlatformDecoderModule::CreateDecoderPromise> MediaChangeMonitor::Create(
changeMonitor = MakeUnique<HEVCChangeMonitor>(config);
} else {
MOZ_ASSERT(MP4Decoder::IsH264(config.mMimeType));
changeMonitor = MakeUnique<H264ChangeMonitor>(
config, aParams.mOptions.contains(
CreateDecoderParams::Option::FullH264Parsing));
changeMonitor = MakeUnique<H264ChangeMonitor>(aParams);
}
} else {
MOZ_ASSERT(MP4Decoder::IsAAC(aParams.AudioConfig().mMimeType));