Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Significant frame drop while playing 1920x1080 HLS video tracks on AFTSSS and AFTSS #9565

Closed
Koster35 opened this issue Oct 14, 2021 · 14 comments
Assignees
Labels

Comments

@Koster35
Copy link

Koster35 commented Oct 14, 2021

[REQUIRED] Issue description

Fire TV Stick - 3rd Gen (2020) (AFTSSS) and Fire TV Stick Lite - 1st Gen (2020) (AFTSS) consistently drop frames while playing 1920x1080 video tracks in HLS streams.

This bug report is a copy of a bug report I created on the Amazon ExoPlayer port repo: amzn/exoplayer-amazon-port#115
I haven't received any response on that repo, so I am re-posting this here.

Our production app uses the ExoPlayer Amazon port, but the issue occurs in both the ExoPlayer and the ExoPlayer Amazon port. This issue started when we upgraded our ExoPlayer Amazon port version from v2.10.6 to v2.11.3. So, I tested a few different ExoPlayer versions and confirmed the issue occurs on the following versions:

  • v2.11.3 (ExoPlayer and ExoPlayer Amazon Port)
  • v2.12.1 (ExoPlayer and ExoPlayer Amazon Port)
  • v2.15.1 (ExoPlayer; there is no Amazon port for this version yet)

After some debugging, I have some insight as to why this started in v2.11:
In both v2.10 and v2.11, ExoPlayer's MediaCodecVideoRenderer.getDecoderInfos() method call returns a list of decoders available on the device. On v2.10, the order of the codecs was always returned in the following order, on both of these Fire Stick devices:
[OMX.MTK.VIDEO.DECODER.AVC, OMX.google.h264.decoder]

So, to decode our HLS stream, the ExoPlayer would choose the first decoder in the list, OMX.MTK.VIDEO.DECODER.AVC (the hardware decoder). When the ExoPlayer checks the Format of our 1920x1080 video track in the HLS stream against this hardware codec, it returns that the hardware codec does not support the 1920x1080 video track. So, it would never attempt to play the 1920x1080 video track.

Starting in ExoPlayer v2.11, the ExoPlayer's MediaCodecVideoRenderer.getDecoderInfos() implementation changed. This method started sorting the list of decoders by 'Format' support.

The sorting recognizes that the hardware decoder (OMX.MTK.VIDEO.DECODER.AVC) does not support the 1920x1080 video track in our HLS stream, but the software decoder (OMX.google.h264.decoder) does, so the sorting method call sorts the list of available decoders into the following order: [OMX.google.h264.decoder, OMX.MTK.VIDEO.DECODER.AVC]. The ExoPlayer then chooses the first decoder in the list, the software decoder, to decode the 1920x1080 video track. However, during playback, there is nearly constant frame drop causing a bad experience for the user.

I don't know what's causing the software codec to perform so poorly, but there are logs indicating frames being dropped:

2021-05-05 16:25:59.404 319-319/? E/FrameEvents: updateAcquireFence: Did not find frame.
2021-05-05 16:25:59.436 7423-7618/? I/CodecNameUnknown-MediaCodecVideoRenderer: dropOutputBuffer: bufferIndex = 2, PTS = 800000
2021-05-05 16:25:59.478 7423-7618/? I/CodecNameUnknown-MediaCodecVideoRenderer: dropOutputBuffer: bufferIndex = 3, PTS = 840000
2021-05-05 16:25:59.486 7423-7439/? I/exoplayer2.dem: Background concurrent copying GC freed 12043(757KB) AllocSpace objects, 4(272KB) LOS objects, 43% free, 7MB/13MB, paused 897us total 147.805ms
2021-05-05 16:25:59.521 319-319/? E/FrameEvents: updateAcquireFence: Did not find frame.
2021-05-05 16:25:59.571 319-319/? E/FrameEvents: updateAcquireFence: Did not find frame.
2021-05-05 16:25:59.584 513-601/? W/BestClock: java.time.DateTimeException: Missing NTP fix
2021-05-05 16:25:59.592 7423-7618/? I/CodecNameUnknown-MediaCodecVideoRenderer: dropOutputBuffer: bufferIndex = 6, PTS = 960000
2021-05-05 16:25:59.602 315-496/? D/AudioFlinger: mixer(0xa5183780) throttle end: throttle time(3)
2021-05-05 16:25:59.638 319-319/? E/FrameEvents: updateAcquireFence: Did not find frame.
2021-05-05 16:25:59.644 513-601/? W/BestClock: java.time.DateTimeException: Missing NTP fix
2021-05-05 16:25:59.647 513-602/? W/BestClock: java.time.DateTimeException: Missing NTP fix
2021-05-05 16:25:59.655 7423-7618/? I/CodecNameUnknown-MediaCodecVideoRenderer: dropOutputBuffer: bufferIndex = 7, PTS = 1000000
2021-05-05 16:25:59.704 319-319/? E/FrameEvents: updateAcquireFence: Did not find frame.
2021-05-05 16:25:59.708 315-496/? D/AudioFlinger: mixer(0xa5183780) throttle end: throttle time(2)
2021-05-05 16:25:59.745 7423-7618/? I/CodecNameUnknown-MediaCodecVideoRenderer: dropOutputBuffer: bufferIndex = 1, PTS = 1080000
2021-05-05 16:25:59.785 7423-7618/? I/CodecNameUnknown-MediaCodecVideoRenderer: dropOutputBuffer: bufferIndex = 2, PTS = 1120000
2021-05-05 16:25:59.836 315-496/? D/AudioFlinger: mixer(0xa5183780) throttle end: throttle time(3)
2021-05-05 16:25:59.838 319-319/? E/FrameEvents: updateAcquireFence: Did not find frame.
2021-05-05 16:25:59.879 315-496/? D/AudioFlinger: mixer(0xa5183780) throttle end: throttle time(3)
2021-05-05 16:25:59.888 7423-7618/? I/CodecNameUnknown-MediaCodecVideoRenderer: dropOutputBuffer: bufferIndex = 4, PTS = 1200000
2021-05-05 16:25:59.919 7423-7618/? I/CodecNameUnknown-MediaCodecVideoRenderer: dropOutputBuffer: bufferIndex = 5, PTS = 1240000
2021-05-05 16:25:59.971 319-319/? E/FrameEvents: updateAcquireFence: Did not find frame.
2021-05-05 16:26:00.032 7423-7618/? I/CodecNameUnknown-MediaCodecVideoRenderer: dropOutputBuffer: bufferIndex = 7, PTS = 1320000
2021-05-05 16:26:00.072 319-319/? E/FrameEvents: updateAcquireFence: Did not find frame.
2021-05-05 16:26:00.082 513-601/? W/BestClock: java.time.DateTimeException: Missing NTP fix
2021-05-05 16:26:00.092 315-496/? D/AudioFlinger: mixer(0xa5183780) throttle end: throttle time(3)
2021-05-05 16:26:00.108 7423-7618/? I/CodecNameUnknown-MediaCodecVideoRenderer: dropOutputBuffer: bufferIndex = 1, PTS = 1400000

I also don't know why these two Fire Sticks are reporting that their hardware codec does not support the 1920x1080 video track. Seemingly, the same OMX.MTK.VIDEO.DECODER.AVC hardware codec can play 1920x1080 video tracks on the Fire TV Stick - 2nd Gen (2016-2019) (AFTT), but not on the newer models.

Finally, this may be related to amzn/exoplayer-amazon-port#42 but I'm not sure.

[REQUIRED] Reproduction steps

This does not reproduce with the Apple HLS samples in the demo app. However, you can reproduce it in the demo app using this sample I found on Bitmovin's website: https://bitdash-a.akamaihd.net/content/MI201109210084_1/m3u8s/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.m3u8

Notice the choppiness of the video and the logs being printed. If the issue does not reproduce the first time, try:

  • Backing out of the video, then starting it again.
  • Manually selecting the 1920x1080 video track.

[REQUIRED] Link to test content

https://bitdash-a.akamaihd.net/content/MI201109210084_1/m3u8s/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.m3u8

I can provide our production HLS streams if more than this sample is needed.

[REQUIRED] A full bug report captured from the device

Fire TV Stick Lite - 1st Gen (2020) (AFTSS):
bugreport-sheldon-PS7234-2021-05-05-16-39-26.zip

[REQUIRED] Version of ExoPlayer being used

  • v2.11.3 (ExoPlayer and ExoPlayer Amazon Port)
  • v2.12.1 (ExoPlayer and ExoPlayer Amazon Port)
  • v2.15.1 (ExoPlayer)

It seems to reproduce in all ExoPlayer versions (Amazon port or not) starting with v2.11.

[REQUIRED] Device(s) and version(s) of Android being used

@claincly
Copy link
Contributor

The bug report you linked to doesn't seem to contain enough information about the codec setup.

I'm surprised 1080p AVC is reported to be not supported by the decoder, but ExoPlayer is not able to change that. Furthermore, I wouldn't be surprised that a software decoder performs poorly on a fire stick.

@claincly claincly self-assigned this Oct 15, 2021
@ojw28
Copy link
Contributor

ojw28 commented Oct 18, 2021

If you look at the logcat output at the start of playback, you should see some logging that uses tag MediaCodecInfo and explains why ExoPlayer thinks that the hardware codec does not support playback. As @claincly notes, the bug report doesn't seem to include this, so it was likely not captured soon enough after the start of playback.

Please could you try reproducing again using v2.15.1, and capturing the logcat that's output at the start of playback, which should include the MediaCodecInfo log lines (and other useful information).

@Koster35
Copy link
Author

Sorry about that, here is another bug report using the v2.15.1 demo app:
bugreport-sheldon-PS7242-2021-10-18-15-40-47.zip

Within it, I found one log with the MediaCodecInfo tag:

10-18 15:41:11.206 10223 9618 16010 D MediaCodecInfo: NoSupport [codec.profileLevel, avc1.4D4032, video/avc] [OMX.MTK.VIDEO.DECODER.AVC, video/avc] [sheldon, AFTSS, Amazon, 28]

And here is one more run just capturing logcat output:
AFTSS_logcat.txt

@claincly
Copy link
Contributor

Thanks for providing the detail.

ExoPlayer queries android.media.MediaCodecInfo for whether a format is supported by a specific decoder. If the platform reports that the format is not supported, ExoPlayer will not try to play with that decoder.

As such, we cannot provide further help if the platform reports it does not support hardware decoding AVC.

@ojw28
Copy link
Contributor

ojw28 commented Oct 25, 2021

As such, we cannot provide further help if the platform reports it does not support hardware decoding AVC.

If the device claims to not support a profile/level that it does actually support, then we should probably consider adding a device specific workaround for that. Particularly given it's effectively a regression, presumably introduced when we started to check the profile/level.

@gregni2
Copy link

gregni2 commented Nov 10, 2021

As such, we cannot provide further help if the platform reports it does not support hardware decoding AVC.

If the device claims to not support a profile/level that it does actually support, then we should probably consider adding a device specific workaround for that. Particularly given it's effectively a regression, presumably introduced when we started to check the profile/level.

Is there a plan to address this in the future? We work on a similar app to Maxwell's and have been discussing upgrading the exoplayer but have not for fear of this issue. Should we attempt a work around or should we just wait for a fix? Thanks.

I'd also like to push back on the designation of this as a 'device specific' bug. This is a regression from a previous build and from the description seems to be related into how it handles a codec that is failing. And just these two devices examples given have some of the largest share of the streaming TV in the country.

@ojw28
Copy link
Contributor

ojw28 commented Nov 22, 2021

I think my comment above about the profile/level check may have been misplaced. In any case, I've ordered one of these devices so that I can more easily see exactly what's going on.

@ojw28 ojw28 assigned ojw28 and unassigned claincly Nov 22, 2021
@ojw28
Copy link
Contributor

ojw28 commented Nov 24, 2021

There are a few interesting things going on here that we should take a look at:

  1. For the Bitmovin sample stream, the AVC codec string that we parse from the actual media for the 1080p variant is avc1.4D4032 (which the HW decoder claims to not support), whereas the master playlist declares that this variant uses avc1.42c00d (which the HW decoder does claim to support). This may be an issue with the content, but we should double check that we're parsing the codec from the media correctly.
  2. Whether it's correct that the HW decoder doesn't advertise support for AVC Main Profile, Level 5 on these devices.
  3. There's a lack of alignment between initial decoder selection and what happens during an adaptive switch. In particular, we don't check profile and level support when adapting between formats in the middle of playback, meaning the player may try to reuse a decoder when switching to a format that the decoder doesn't support (for this to happen there must exist some other decoder that does support the format, else that format would never have been eligible for selection). This is something we need to address in ExoPlayer.
  4. We need to revisit how ExoPlayer handles formats that are not supported by the primary decoder, but are supported by a secondary decoder, in the case where multiple streams are available for selection.

@ojw28
Copy link
Contributor

ojw28 commented Nov 24, 2021

I think (1) is just that the codec string in the master playlist is incorrect (i.e., it's a problem with the media itself).

(3) and (4) are the main things that we need to look at on the ExoPlayer side. In the meantime, I think you can get the old behavior with new releases of ExoPlayer with the workaround below, which should unblock you if you're stuck not being able to upgrade to a newer release:

RenderersFactory renderersFactory =
    new DefaultRenderersFactory(context)
        ....
        .setMediaCodecSelector(
            (mimeType, requiresSecureDecoder, requiresTunnelingDecoder) -> {
              List<MediaCodecInfo> codecs = MediaCodecSelector.DEFAULT.getDecoderInfos(
                  mimeType,
                  requiresSecureDecoder,
                  requiresTunnelingDecoder);
              if (codecs.isEmpty()) {
                return Collections.emptyList();
              }
              // Only consider the first decoder.
              return Collections.singletonList(codecs.get(0));
            }
        );

player =
    new ExoPlayer.Builder(context)
        ...
        .setRenderersFactory(renderersFactory)
        .build();

@gregni2
Copy link

gregni2 commented Dec 1, 2021

This is a huge help/relief. Great explanation. Now with mediastreamvalidator and your comments I see what you mean by the miss matched levels.
After putting in some debugging it does look like the Exoplayer is parsing all the levels correctly (at least to my eyes). And that the example does not actually need at level 5 profile as its later marked as. Since the Amazon Fire Lite's initial codec handles it fine on the first pass when it steps it up with that adaptive algorithm.

We will use this fix to get the same behavior. I do have one question though. The non 4K fire devices report they have two codecs ...

OMX.MTK.VIDEO.DECODER.AVC and OMX.google.h264.decoder. The first is the better choice (hardware acceleration?). Does the ExoPlayer ask the second vocoder if it supports level 5? Or does it just use it because the first says it can't support the higher profile level. Because if doesn't ask the second time, it might be helpful to have it ask. And if they both don't support the higher level, it seems like the first codec would be the better choice. At least from what I see of the Fire devices.

As an interesting side note. I've noticed that the Amazon port of the exoplayer has very few differences. But the main one is at that profile check (isCodecSupported) they have the method AmazonQuirks.skipProfileLevelCheck. If this is set to true it skips that test entirely and the player will select the first codec every time. I believe we would prefer your fix. But I thought I would mention this.

Thanks again for looking into this. I feel like not only did we get a work around but also a an education!

@ojw28
Copy link
Contributor

ojw28 commented Dec 1, 2021

OMX.MTK.VIDEO.DECODER.AVC and OMX.google.h264.decoder. The first is the better choice (hardware acceleration?).

Correct.

Does the ExoPlayer ask the second vocoder if it supports level 5? Or does it just use it because the first says it can't support the higher profile level. Because if doesn't ask the second time, it might be helpful to have it ask. And if they both don't support the higher level, it seems like the first codec would be the better choice. At least from what I see of the Fire devices.

In more detail, what happens is:

  1. The TrackSelector asks the Renderer whether it supports each of the available track individually. Because the renderer will now consider both decoders, it replies "yes" for all tracks. The TrackSelector doesn't know (because the renderer doesn't tell it) that the "yes" for the 1080p track was on the basis that the software decoder (OMX.google.h264.decoder) supports it, where-as the other tracks are supported by the hardware decoder.
  2. Because the TrackSelector sees that all tracks are supported, it includes all of them in the adaptive track selection.
  3. At the start of playback, the Renderer will instantiate OMX.MTK.VIDEO.DECODER.AVC if the initially selected track is not the 1080p track, and OMX.google.h264.decoder if it is the 1080p track. This is correct based on what each decoder states that it can support.
  4. During playback, if adaptation occurs from the 1080p track to one of the other tracks, or vice-versa, the Renderer does not switch from one decoder to the other. This is a bug, because it means we can end up trying to play a track with a decoder that doesn't claim to support it.

My current thinking for fixing this is:

  1. Pass some additional data from the Renderer to the TrackSelector during track support evaluation, so that the TrackSelector is aware that the 1080p track is only supported by a software decoder whereas the others are supported by a hardware decoder.
  2. Make DefaultTrackSelector exclude tracks that are only supported by a software decoder, if there are other lower quality tracks that are supported by a hardware decoder. In the example here, this will cause the selection to exclude the 1080p track. This will be overridable.
  3. During playback, if adaptation occurs between tracks whose support was evaluated based on different decoders, actually switch from one decoder to the other. Note that this will only ever be relevant if overriding DefaultTrackSelector behavior.

@gregni2
Copy link

gregni2 commented Dec 2, 2021

Wow. Great details. Your fix sounds perfect for us at least. Those steps would fix our current issue very well.

One more question though. I was impressed to see the adaptive algorithm (TrackSelection I think?) measure the bitrate and basically say 'we can handle more' and then to go to the higher resolution/bitrate/profile level. The player seems great about going up. But when the stuttering occurred I saw logging from the exoplayer that said it was dropping packets. But I never saw the adaptive algorithm go back down to a lower resolutions/bitrates.

Was there a reason that it didn't step down to a lower resolution once it realized it was dropping packets? It looked to me like the code was setup to step down as well.

I promise that's my last question. :) You folks have been awesome and playing with the exoplayer has given me a real appreciation for the great work you folks are doing.

@ojw28
Copy link
Contributor

ojw28 commented Dec 2, 2021

Adaptive bitrate is based on available network bandwidth. In this case there is sufficient network bandwidth, and it's CPU that's the limiting factor, so the logic to step down wont be triggered.

We've discussed looking at additional metrics such as how frequently frames are being dropped (many frame drops is indicative of being CPU bound), and even things like the thermal state of the device, and factoring those into the adaptive bitrate logic as well. It's something we should look at at some point, and is particularly useful for devices that only ship with software decoders. In the case discussed here though, it's better just to avoid switching up to the 1080p stream in the first place :).

icbaker pushed a commit to androidx/media that referenced this issue Dec 10, 2021
This is a no-op change that updates supportsFormat to use the
decoder list before it's reordered by format support. Instead,
supportsFormat iterates through the decoders listed in their
original priority order as specified by the MediaCodecSelector.
The end result is identical.

This is necessary groundwork for a subsequent change that will
indicate in Capabilities whether the decoder that suppports the
format is the primary one as specified by the MediaCodecSelector
(i.e., the one at index=0 in the lists that are now used).

Issue: google/ExoPlayer#9565
PiperOrigin-RevId: 414971986
icbaker pushed a commit to androidx/media that referenced this issue Dec 10, 2021
ojw28 added a commit that referenced this issue Dec 10, 2021
This is a no-op change that updates supportsFormat to use the
decoder list before it's reordered by format support. Instead,
supportsFormat iterates through the decoders listed in their
original priority order as specified by the MediaCodecSelector.
The end result is identical.

This is necessary groundwork for a subsequent change that will
indicate in Capabilities whether the decoder that suppports the
format is the primary one as specified by the MediaCodecSelector
(i.e., the one at index=0 in the lists that are now used).

Issue: #9565
PiperOrigin-RevId: 414971986
ojw28 added a commit that referenced this issue Dec 10, 2021
icbaker pushed a commit that referenced this issue Dec 14, 2021
*** Original commit ***

Add capability flags for hardware and decoder support

Issue: #9565

***

PiperOrigin-RevId: 416170329
icbaker pushed a commit that referenced this issue Dec 14, 2021
*** Original commit ***

Rollback of 0aa23b0

*** Original commit ***

Add capability flags for hardware and decoder support

Issue: #9565

***

***

PiperOrigin-RevId: 416285603
@tidoemanuele
Copy link
Contributor

tidoemanuele commented Dec 20, 2021

Same problem here with LENOVO TB-X306X running the latest exoplayer version (2.16.1).
Here how I'm temporary solving it:

  • For all devices running Android 10 or above I use the VideoCapabilities to understand if the device is able to play the highest quality track. If not I limit the track list by framerate or bitrate for all the play sessions.

  • For all devices running Android 9 and below I'm looking to the Video Frame Processing Offset values during the play session and decide to limit the track list by framerate or bitrate for the next play sessions.

I'm not satisfied with my approach for devices running android 9 or below, because I need a training period to decide if the device can play smoothly or not. Waiting for a better solution or some advice.

tonihei pushed a commit to androidx/media that referenced this issue Jan 5, 2022
*** Original commit ***

Add capability flags for hardware and decoder support

Issue: google/ExoPlayer#9565

***

PiperOrigin-RevId: 416170329
tonihei added a commit to androidx/media that referenced this issue Jan 5, 2022
*** Original commit ***

Rollback of 3c4c1f4

*** Original commit ***

Add capability flags for hardware and decoder support

Issue: google/ExoPlayer#9565

***

***

PiperOrigin-RevId: 416285603
tonihei added a commit that referenced this issue Jan 17, 2022
Adaptive video and audio selections will be limited to formats with
the same level of DecoderSupport and HardwareAccelatationSupport, unless
specifically allowed by new flags.

If different levels of decoder support are available, prefer primary
over fallback decoders and hardware-accelerated over software decoders
(in this order). For video, also prefer more efficient codecs, if both
are supported by hardware-accelerated primary decoders.

Issue: #4835
Issue: #9565
PiperOrigin-RevId: 422345048
@tonihei tonihei closed this as completed Jan 17, 2022
icbaker pushed a commit to androidx/media that referenced this issue Jan 26, 2022
Adaptive video and audio selections will be limited to formats with
the same level of DecoderSupport and HardwareAccelatationSupport, unless
specifically allowed by new flags.

If different levels of decoder support are available, prefer primary
over fallback decoders and hardware-accelerated over software decoders
(in this order). For video, also prefer more efficient codecs, if both
are supported by hardware-accelerated primary decoders.

Issue: google/ExoPlayer#4835
Issue: google/ExoPlayer#9565
PiperOrigin-RevId: 422345048
@google google locked and limited conversation to collaborators Mar 19, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

7 participants