Skip to content

Draft: Fix H265 I-frame-only segment flush (Ateme / known-length PES) + regression tests#3092

Draft
stevemayhew wants to merge 1 commit intoandroidx:releasefrom
TiVo:p-add-iframe-test-cases-1.5.1
Draft

Draft: Fix H265 I-frame-only segment flush (Ateme / known-length PES) + regression tests#3092
stevemayhew wants to merge 1 commit intoandroidx:releasefrom
TiVo:p-add-iframe-test-cases-1.5.1

Conversation

@stevemayhew
Copy link
Contributor

Context / Why this PR exists

This PR adds regression tests around HLS TS “I-frame-only / keyframe-only” segment parsing, and highlights a remaining failure mode in the current “synthesized empty PUSI” end-of-input approach (the approach introduced/iterated in a series of commits by @daniele.sparano).

The failures are specific to H.265 (HEVC) streams produced by an Ateme transcoder where the PES header contains a known/explicit packet length. In these streams, a segment may contain a single access unit that ends exactly at the end of the segment without a following AUD / next access unit delimiter. In that case, the final frame may not be committed until a subsequent segment is loaded.

This issue reproduces on our 1.5.1-based branch and also on top-of-tree release.

We are agnostic as to which approach is "correct/best", as long as all our test cases pass. We are working on a solution for the two failing tests, using the current “synthesized empty PUSI” logic, as long as all parties feel this is best.

Current upstream approach (synthesized empty PUSI)

At end of TS input, TsExtractor.read() attempts to flush the last unit by sending a synthesized empty payload with FLAG_PAYLOAD_UNIT_START_INDICATOR to PesReader, but only when PesReader.canConsumeSynthesizedEmptyPusi(isModeHls) returns true.

PesReader.canConsumeSynthesizedEmptyPusi(...) is currently gated on payloadSize == C.LENGTH_UNSET (unknown PES length). That means known-length PES streams do not receive the synthesized flush event at EOF.

As a result, the H265 reader may never see a delimiter that triggers committing the final access unit (it typically commits when a subsequent access unit start is detected).

Our approach (works for all tests)

We have an alternative approach that works for all of our test cases (including the Ateme HEVC known-length PES cases): explicitly detect end-of-input in TsExtractor and call endOfStream() on the last active payload reader chain.

Reference implementation:

However, given the existing upstream investment in the synthesized-empty-PUSI approach, we are not insisting on replacing it. The primary goal of this PR is to:

  1. add regression tests that clearly capture the HEVC known-length PES failure mode, and
  2. provide evidence that an explicit end-of-stream notification solves it cleanly,
  3. our approach was restricted to IDR only, simply to avoid breaking all the existing TS test cases (which @icbaker fixed once the final sample is committed)

Test coverage and current status

This PR includes a set of HLS TS extraction tests. With only the current synthesized-empty-PUSI changes (“dsparano’s changes”), most tests pass, but two H.265 cases fail. With our end-of-stream approach they pass.

Tests that pass with both our change an the empty PUSI approach

  • consumeInitThenSingleIframe
    Notes: sample_iframe.ts includes an extra TS packet with an AUD, so it passes no matter what (the AUD triggers access unit boundary/commit).
  • consumeIframeWithInit — passes with dsparano’s changes
  • consumeThreeH264IFrames — passes with dsparano’s changes
  • consumeTwoH264Segments — passes with dsparano’s changes
  • consumeIFrameWithFillerData — passes with dsparano’s changes
  • consumeIFrameWithMultipleNalUnits — passes with dsparano’s changes

Tests that currently fail with dsparano changes, but pass with our end-of-stream approach

  • consumeThreeH265IFrames
    Fails with dsparano’s changes, passes with ours.
  • consumeTwoH265IFramesWithInit
    Fails with dsparano’s changes, passes with ours.

These failing tests are the key reproductions for HEVC streams where the PES length is explicitly signaled (Ateme), and the last access unit does not have a subsequent delimiter to trigger commit.

For H.264/H.265 elementary streams in Transport Stream the current
ExoPlayer `ElementaryStreamReader` fail to commit the sample until the next
I-Frame segment is read.  This is because the readers do not commit until
the next AUD NALU is encountered.

This also leaves the last sample in any HLS segment uncommited, this set of
test cases verifies any fix for this works correctly with a variety (ATEME, Harmonic, ...)
of common transcoders as well as a couple of different segmenters.
@stevemayhew
Copy link
Contributor Author

@icbaker we originally opened this code change in ExoPlayer 2.15.1, so we have been running with it for years with many different segmenters and transcoders across our millions of subscribers.

Both @dsparano 's work and our approach cover the same area (@dsparano handles even non-IDR samples as well as non-HLS mode. I personally don't care which path we take, as long as it works for all the cases.

The MPEG parsing code is fragile, and the decoders that consume its output are even more fragile (streamers as well as countless versions of handsets). So, IMO, we need to make sure unit test coverage is included in the changes we submit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant