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:
committed by
dmeehan@mozilla.com
parent
f3267d9d24
commit
ee67e078b7
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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));
|
||||
|
||||
Reference in New Issue
Block a user