google / ExoPlayer

This project is deprecated and stale. The latest ExoPlayer code is available in https://github.com/androidx/media
https://developer.android.com/media/media3/exoplayer
Apache License 2.0
21.75k stars 6.03k forks source link

Use actual chunk bitrate instead of average in ABR decisions to improve performance for VBR streams. #8495

Open tanhan opened 3 years ago

tanhan commented 3 years ago

Hi,

In our experience ExoPlayer does not use lower bitrate even there is latency in downloading big chunks and this causes freezing and buffering. However the bandwith is 6 Mbit, because of VBR sometimes the size of one 2 second chunk differs from 1.5 MegeByte to 15 MegaByte. In our experience when there is increase in bitrate the player still tries to download next chunk using same bitrate instead of lowering bitrate. What is causing this problem. Is there a way or configuration to fix this?

Trace Screenshot

image

Our MPD

`<?xml version="1.0" encoding="UTF-8"?>

`
tanhan commented 3 years ago

We are using default values of buffer sizes and track selection durations.

`private const val ADAPTIVE_MIN_DURATION_FOR_QUALITY_INCREASE_MS = 10000 private const val ADAPTIVE_MAX_DURATION_FOR_QUALITY_DECREASE_MS = 25000 private const val ADAPTIVE_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS = 25000 private const val ADAPTIVE_BANDWIDTH_FRACTION = 0.70f private const val ADAPTIVE_BUFFERED_FRACTION_TO_LIVE_EDGE_FOR_QUALITY_INCREASE = 0.75f

public static final int DEFAULT_MIN_BUFFER_MS = 50_000; public static final int DEFAULT_MAX_BUFFER_MS = 50_000; public static final int DEFAULT_BUFFER_FOR_PLAYBACK_MS = 2500; public static final int DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS = 5000;`

tanhan commented 3 years ago

ExoPlayer Version: 2.12.2

linhai326 commented 3 years ago

It seems your issue is more about network latency, not about bandwidth: You can check how much time you spent on on making http connection.

The bandwidth meter calculate the estimated bandwidth based on the downloading/read speed. It doesn't include the time on connection.

tanhan commented 3 years ago

I am not sure latency is the problem. Because making the connections is fast however downloading the big chunks takes some time. How is the download speed is calculated in this case?

I am trying to understand why is the player insisting on downloading big chunks on same high profile instead of lowering bitrate on this experience which causes an empty buffer as a result. Can high VBR cause this problem?

linhai326 commented 3 years ago

video variant selection is based on the calculated downloading bitrate. You may want to print out the values from: This listener will give you the current bitrate: void onBandwidthSample(int elapsedMs, long bytes, long bitrate);

the bitrate here is the value used for video track selection.

kpandroid commented 3 years ago

It seems that the current ABR algorithm can't support VBR with big difference of bitrate for the same resolution and can't determine the moment to switch lower. Look at the ABR algorithm of the hls.js - it's more complicated, may be some ideas would be userfull for exoplayer. https://github.com/video-dev/hls.js/blob/master/src/controller/abr-controller.ts

linhai326 commented 3 years ago

"It seems that the current ABR algorithm can't support VBR with big difference of bitrate for the same resolution and can't determine the moment to switch lower."

EXOPlayer's bandwidth meter uses sliding window algorithm, which works good for us. In some cases, depending on your media segment size, you may need to adjust the weight (setSlidingWindowMaxWeight()) to get more accurate downloading bitrate.

tanhan commented 3 years ago

I suppose there is a need to update trackBitrate dynamically. Because at the time VBR creating big chunks the trackBitrate is much higher than the mpd is stating.

`protected boolean canSelectFormat(Format format, int trackBitrate, float playbackSpeed, long effectiveBitrate) { return Math.round(trackBitrate * playbackSpeed) <= effectiveBitrate;

}`

tanhan commented 3 years ago

I made a workaround for this. It works mostly.

protected boolean canSelectFormat(Format format, int trackBitrate, float playbackSpeed, long effectiveBitrate) {
        if (lastBufferEvaluationMediaChunk != null && trackBitrate == lastBufferEvaluationMediaChunk.trackFormat.bitrate) {
            long chunkDurationInSeconds = (lastBufferEvaluationMediaChunk.endTimeUs - lastBufferEvaluationMediaChunk.startTimeUs) / 1_000_000;
            double chunkBitrate = lastBufferEvaluationMediaChunk.bytesLoaded() / chunkDurationInSeconds * 8.0;

            double bitrateCoefficient = chunkBitrate / trackBitrate;
            if (bitrateCoefficient > 1.0f) {
                return Math.round(trackBitrate * playbackSpeed) * bitrateCoefficient <= effectiveBitrate;
            }
        }
        return Math.round(trackBitrate * playbackSpeed) <= effectiveBitrate;
    }
tonihei commented 2 years ago

I think we should turn this into a low-priority enhancement. If I understand the issue correctly, our ABR algorithm works with the average bitrate when making decisions and may run into problems if the actual chunk bitrate is much higher.

Note that all the information about the actual bitrate of potential future and past chunks is already available in the queue and mediaChunkIterators parameters of updateSelectedTrack, so it's possible to adjust the logic by overriding AdaptiveTrackSelection or providing a completely custom track selection class.