Exoplayer: Buffer configuration for live streams

Created on 10 Feb 2017  路  3Comments  路  Source: google/ExoPlayer

Hello,

I've encountered an buffering issue with ExoPlayer 2 :

  • On my radio application, i would like to have a bigger buffer so that when the network is loss, i could continue listening for example 30s. Actually there some constants in the DefaultLoadControl class :
    /**
     * The default minimum duration of media that the player will attempt to ensure is buffered at all
     * times, in milliseconds.
     */
    public static final int DEFAULT_MIN_BUFFER_MS = 15000;

    /**
     * The default maximum duration of media that the player will attempt to buffer, in milliseconds.
     */
    public static final int DEFAULT_MAX_BUFFER_MS = 30000;

    /**
     * The default duration of media that must be buffered for playback to start or resume following a
     * user action such as a seek, in milliseconds.
     */
    public static final int DEFAULT_BUFFER_FOR_PLAYBACK_MS = 2500;

    /**
     * The default duration of media that must be buffered for playback to resume after a rebuffer,
     * in milliseconds. A rebuffer is defined to be caused by buffer depletion rather than a user
     * action.
     */
    public static final int DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS = 5000;

Actually, i have 5s of buffers, I did increase these parameters by 3x, 4x, 5x, .... together or separately but i found no way to achieve the initial goal 30s. Could you explain in detail how we can control Buffering time, please ?

Thanks in advance

question

Most helpful comment

Dear @petitTrung:

I think the problem is similar to #2083; please refer to the logics below.

  private int getBufferTimeState(long bufferedDurationUs) {
    return bufferedDurationUs > maxBufferUs ? ABOVE_HIGH_WATERMARK
        : (bufferedDurationUs < minBufferUs ? BELOW_LOW_WATERMARK : BETWEEN_WATERMARKS);
  }
  @Override
  public boolean shouldContinueLoading(long bufferedDurationUs) {
    int bufferTimeState = getBufferTimeState(bufferedDurationUs);
    boolean targetBufferSizeReached = allocator.getTotalBytesAllocated() >= targetBufferSize;
    isBuffering = bufferTimeState == BELOW_LOW_WATERMARK
        || (bufferTimeState == BETWEEN_WATERMARKS && isBuffering && !targetBufferSizeReached);
    return isBuffering;
  }
  /**
   * A default size in bytes for an audio buffer.
   */
  public static final int DEFAULT_AUDIO_BUFFER_SIZE = 54 * DEFAULT_BUFFER_SEGMENT_SIZE;

There are four parameters within DefaultLoadControl; two of them control the load timing; the other twos control when the playback starts by having sufficient buffering. Please refer to the figure below.
The maxBufferUs & minBufferUs are about the load timing but bufferForPlaybackAfterRebufferUs & bufferForPlaybackUs are about when the playback starts by having sufficient buffering.

1

Refer to shouldContinueLoading(), a typical loading pattern includes 3 stages.
At the first stage we continuing loading until maxBufferUs is reached as below.
2

Since then isBuffering = false and bufferTimeState == BETWEEN_WATERMARKS; so shouldContinueLoading() returns false as below.
3

Finally, when bufferTimeState == BELOW_LOW_WATERMARK again we recover the download, as the figure below.
4

Per the flow described, you could see within the period of [t1, t3] actually we DO NOT load anymore. Therefore you may meet the condition, for example to have merely ~ 15 seconds buffering.

Beside, there is a limit upon buffering size.
From your description, your application is about radio. Hence it is limited by DEFAULT_AUDIO_BUFFER_SIZE = 54 * DEFAULT_BUFFER_SEGMENT_SIZE; (assume there is NO video).
So if the radio's bit-rate exceeds (54 * 64 / 30) = 115.2 KBps, you still will not be able to have 30 seconds maximum buffering.
In short, typically the buffering data will be from 15 to 30 second if your connection speed is good enough.

You have two choices to enlarge it.

  1. Enlarge both maxBufferUs & minBufferUs (instead of maxBufferUs only).
  2. Remove the limit by applying "drip-feeding" method.
    isBuffering = bufferTimeState == BELOW_LOW_WATERMARK
    || (bufferTimeState == BETWEEN_WATERMARKS /&& isBuffering/ && !targetBufferSizeReached);
    It is, once you are below maxBufferUs, do fetch immediately.

Finally, if it is a LIVE streaming. you are still limited by current available data at source.
For example, the source may own at most 20 seconds data so you could not buffer more than it.

All 3 comments

Dear @petitTrung:

I think the problem is similar to #2083; please refer to the logics below.

  private int getBufferTimeState(long bufferedDurationUs) {
    return bufferedDurationUs > maxBufferUs ? ABOVE_HIGH_WATERMARK
        : (bufferedDurationUs < minBufferUs ? BELOW_LOW_WATERMARK : BETWEEN_WATERMARKS);
  }
  @Override
  public boolean shouldContinueLoading(long bufferedDurationUs) {
    int bufferTimeState = getBufferTimeState(bufferedDurationUs);
    boolean targetBufferSizeReached = allocator.getTotalBytesAllocated() >= targetBufferSize;
    isBuffering = bufferTimeState == BELOW_LOW_WATERMARK
        || (bufferTimeState == BETWEEN_WATERMARKS && isBuffering && !targetBufferSizeReached);
    return isBuffering;
  }
  /**
   * A default size in bytes for an audio buffer.
   */
  public static final int DEFAULT_AUDIO_BUFFER_SIZE = 54 * DEFAULT_BUFFER_SEGMENT_SIZE;

There are four parameters within DefaultLoadControl; two of them control the load timing; the other twos control when the playback starts by having sufficient buffering. Please refer to the figure below.
The maxBufferUs & minBufferUs are about the load timing but bufferForPlaybackAfterRebufferUs & bufferForPlaybackUs are about when the playback starts by having sufficient buffering.

1

Refer to shouldContinueLoading(), a typical loading pattern includes 3 stages.
At the first stage we continuing loading until maxBufferUs is reached as below.
2

Since then isBuffering = false and bufferTimeState == BETWEEN_WATERMARKS; so shouldContinueLoading() returns false as below.
3

Finally, when bufferTimeState == BELOW_LOW_WATERMARK again we recover the download, as the figure below.
4

Per the flow described, you could see within the period of [t1, t3] actually we DO NOT load anymore. Therefore you may meet the condition, for example to have merely ~ 15 seconds buffering.

Beside, there is a limit upon buffering size.
From your description, your application is about radio. Hence it is limited by DEFAULT_AUDIO_BUFFER_SIZE = 54 * DEFAULT_BUFFER_SEGMENT_SIZE; (assume there is NO video).
So if the radio's bit-rate exceeds (54 * 64 / 30) = 115.2 KBps, you still will not be able to have 30 seconds maximum buffering.
In short, typically the buffering data will be from 15 to 30 second if your connection speed is good enough.

You have two choices to enlarge it.

  1. Enlarge both maxBufferUs & minBufferUs (instead of maxBufferUs only).
  2. Remove the limit by applying "drip-feeding" method.
    isBuffering = bufferTimeState == BELOW_LOW_WATERMARK
    || (bufferTimeState == BETWEEN_WATERMARKS /&& isBuffering/ && !targetBufferSizeReached);
    It is, once you are below maxBufferUs, do fetch immediately.

Finally, if it is a LIVE streaming. you are still limited by current available data at source.
For example, the source may own at most 20 seconds data so you could not buffer more than it.

For radio or any other live stream, depending on the exact type of the stream, it might not be possible to build up a large buffer in a sensible way. For example if the stream/server only allows you to request the past 5s of the stream, then the only way a player could build up a 30s buffer would be for it to sit around buffering for 25s after opening the connection, prior to starting playback. That wouldn't be a good user experience. Note also that the larger the buffer, the further behind "live" the user will be.

I suspect in your case the stream/server is effectively controlling the buffer size to something around 5s, by only making that duration of past media available. In which case changes you make in the player probably wont have any effect.

Thank both of you @WeiChungChang & @ojw28 for your help !

Was this page helpful?
0 / 5 - 0 ratings