Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1538,18 +1538,17 @@ private static void parseSampleGroups(
sgpd.setPosition(Mp4Box.HEADER_SIZE);
int sgpdVersion = BoxParser.parseFullBoxVersion(sgpd.readInt());
sgpd.skipBytes(4); // grouping_type == seig.
if (sgpdVersion == 1) {
if (sgpd.readUnsignedInt() == 0) {
throw ParserException.createForUnsupportedContainerFeature(
"Variable length description in sgpd found (unsupported)");
}
} else if (sgpdVersion >= 2) {
long default_length = sgpdVersion >= 1 ? sgpd.readUnsignedInt() : 0;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i don't think the >= 1 check here is correct - I think it should be == 1.

Looking at section 8.9.3.2 of ISO 14496-12:2015 it seems default_length is present at v1 only, and the same 4 bytes are default_sample_description_index on v2+:

if (version==1) { unsigned int(32) default_length; }
if (version>=2) {
  unsigned int(32) default_sample_description_index;
}

This also matches the previous code.

Same below on the condition gating the description_length skip.

Copy link
Copy Markdown
Author

@dimitry-unified-streaming dimitry-unified-streaming Jun 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have the 2022 and 2024 DIS versions of ISO 14496-12 here, and those both have the following:

aligned(8) class SampleGroupDescriptionBox ()
  extends FullBox('sgpd', version, flags){
  unsigned int(32) grouping_type;
  if (version>=1) { unsigned int(32) default_length; }
  if (version>=2) {
    unsigned int(32) default_group_description_index;
  }
  unsigned int(32) entry_count;
  int i;
  for (i = 1 ; i <= entry_count ; i++){
    if (version>=1) {
     if (default_length==0) {
       unsigned int(32) description_length;
      }
    }
    SampleGroupDescriptionEntry (grouping_type);
    // an instance of a class derived from SampleGroupDescriptionEntry
    // that is appropriate and permitted for the media type
  }
}

I.e. default_length exists for version>=1, and default_group_description_index exists for version>=2. This is also how we write sgpd boxes in our own software.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, thanks - aren't these two versions of the spec binary-incompatible with each other? My understanding was that this was generally avoided in these sort of specs. Was this an (unacknowledged?) bug in the 2012 to 2020 versions of the spec?

Given this is quite a fiddly change, and there's a risk that it's quite hard to find this PR comment thread in future when someone is going through the blame layer, I'm going to send a change that pretty much only changes == 1 to >= 1 with an explanation - and then we can rebase this PR on top - hope that's OK.

if (sgpdVersion >= 2) {
sgpd.skipBytes(4); // default_sample_description_index.
}
if (sgpd.readUnsignedInt() != 1) { // entry_count.
throw ParserException.createForUnsupportedContainerFeature(
"Entry count in sgpd != 1 (unsupported).");
}
if (sgpdVersion >= 1 && default_length == 0) {
sgpd.skipBytes(4); // description_length.
}

// CencSampleEncryptionInformationGroupEntry
sgpd.skipBytes(1); // reserved = 0.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,22 @@ public void extract_h265WithoutGopParsingFlags() throws Exception {
"extractordumps/mp4/fragmented_captions_h265.mp4_without_gop_parsing_flags");
}

@Test
public void extract_h264WithVariableLengthSgpdBox() throws Exception {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this test fail for you without the fix? It seems to pass for me

To show this, I checked out this PR, then ran:

$ git checkout HEAD^ -- libraries/extractor/src/main/java/androidx/media3/extractor/mp4/FragmentedMp4Extractor.java

And then ran the tests in this file, and they all passed.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I see what is happening; the parseSampleGroups() function scans only for seig grouping types, not roll ones. When it doesn't find any seig grouping type, it does an early return, and doesn't hit the sgpd box parsing code at all.

I had troubles getting any test to work with seig boxes, which is why the sample_fragmented_variable_length_sgpd.mp4 file has another grouping type roll, e.g. MP4Box output:

<SampleGroupBox Size="28" Type="sbgp" Version="0" Flags="0" Specification="p12" Container="stbl traf" grouping_type="roll">
  <SampleGroupBoxEntry sample_count="94" group_description_index="1" group_description_in_traf="1"/>
</SampleGroupBox>
<SampleGroupDescriptionBox Size="30" Type="sgpd" Version="1" Flags="0" Specification="p12" Container="stbl traf" grouping_type="roll" default_length="0">
  <RollRecoveryEntry roll_distance="-1"/>
</SampleGroupDescriptionBox>

I'll try this again with a file with a seig box, but previously I got a deduplicateConsecutiveFormats=false so TrackOutput must receive at least one sampleMetadata() call between format() calls. error on that. I had no idea what to do to work around that other error. Maybe I have to supply some DRM metadata somewhere to make such a test work?

FragmentedMp4Extractor extractor =
new FragmentedMp4Extractor(SubtitleParser.Factory.UNSUPPORTED);
FakeExtractorOutput output =
TestUtil.extractAllSamplesFromFile(
extractor,
ApplicationProvider.getApplicationContext(),
"media/mp4/sample_fragmented_variable_length_sgpd.mp4");

DumpFileAsserts.assertOutput(
ApplicationProvider.getApplicationContext(),
output,
"extractordumps/mp4/fragmented_variable_length_sgpd.mp4");
}

private static FakeExtractorInput createInputForSample(String sample) throws IOException {
return new FakeExtractorInput.Builder()
.setData(
Expand Down
Loading