Draft: Fix H265 I-frame-only segment flush (Ateme / known-length PES) + regression tests#3092
Draft
stevemayhew wants to merge 1 commit intoandroidx:releasefrom
Draft
Draft: Fix H265 I-frame-only segment flush (Ateme / known-length PES) + regression tests#3092stevemayhew wants to merge 1 commit intoandroidx:releasefrom
stevemayhew wants to merge 1 commit intoandroidx:releasefrom
Conversation
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.
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. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 withFLAG_PAYLOAD_UNIT_START_INDICATORtoPesReader, but only whenPesReader.canConsumeSynthesizedEmptyPusi(isModeHls)returns true.PesReader.canConsumeSynthesizedEmptyPusi(...)is currently gated onpayloadSize == 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
TsExtractorand callendOfStream()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:
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
consumeInitThenSingleIframeNotes:
sample_iframe.tsincludes 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 changesconsumeThreeH264IFrames— passes with dsparano’s changesconsumeTwoH264Segments— passes with dsparano’s changesconsumeIFrameWithFillerData— passes with dsparano’s changesconsumeIFrameWithMultipleNalUnits— passes with dsparano’s changesTests that currently fail with dsparano changes, but pass with our end-of-stream approach
consumeThreeH265IFramesFails with dsparano’s changes, passes with ours.
consumeTwoH265IFramesWithInitFails 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.