/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package androidx.media3.exoplayer.audio;

import static androidx.media3.common.util.Util.constrainValue;
import static androidx.media3.exoplayer.audio.DefaultAudioSink.OUTPUT_MODE_OFFLOAD;
import static androidx.media3.exoplayer.audio.DefaultAudioSink.OUTPUT_MODE_PASSTHROUGH;
import static androidx.media3.exoplayer.audio.DefaultAudioSink.OUTPUT_MODE_PCM;
import static com.google.common.primitives.Ints.checkedCast;
import static java.lang.Math.max;

import android.media.AudioTrack;
import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.exoplayer.audio.DefaultAudioSink.OutputMode;
import androidx.media3.extractor.AacUtil;
import androidx.media3.extractor.Ac3Util;
import androidx.media3.extractor.Ac4Util;
import androidx.media3.extractor.DtsUtil;
import androidx.media3.extractor.MpegAudioUtil;

/** Provide the buffer size to use when creating an {@link AudioTrack}. */
@UnstableApi
public class DefaultAudioTrackBufferSizeProvider
    implements DefaultAudioSink.AudioTrackBufferSizeProvider {

  /** Default minimum length for the {@link AudioTrack} buffer, in microseconds. */
  private static final int MIN_PCM_BUFFER_DURATION_US = 250_000;
  /** Default maximum length for the {@link AudioTrack} buffer, in microseconds. */
  private static final int MAX_PCM_BUFFER_DURATION_US = 750_000;
  /** Default multiplication factor to apply to the minimum buffer size requested. */
  private static final int PCM_BUFFER_MULTIPLICATION_FACTOR = 4;
  /** Default length for passthrough {@link AudioTrack} buffers, in microseconds. */
  private static final int PASSTHROUGH_BUFFER_DURATION_US = 250_000;
  /** Default length for offload {@link AudioTrack} buffers, in microseconds. */
  private static final int OFFLOAD_BUFFER_DURATION_US = 50_000_000;
  /**
   * Default multiplication factor to apply to AC3 passthrough buffer to avoid underruns on some
   * devices (e.g., Broadcom 7271).
   */
  private static final int AC3_BUFFER_MULTIPLICATION_FACTOR = 2;

  /** A builder to create {@link DefaultAudioTrackBufferSizeProvider} instances. */
  public static class Builder {

    private int minPcmBufferDurationUs;
    private int maxPcmBufferDurationUs;
    private int pcmBufferMultiplicationFactor;
    private int passthroughBufferDurationUs;
    private int offloadBufferDurationUs;
    private int ac3BufferMultiplicationFactor;

    /** Creates a new builder. */
    public Builder() {
      minPcmBufferDurationUs = MIN_PCM_BUFFER_DURATION_US;
      maxPcmBufferDurationUs = MAX_PCM_BUFFER_DURATION_US;
      pcmBufferMultiplicationFactor = PCM_BUFFER_MULTIPLICATION_FACTOR;
      passthroughBufferDurationUs = PASSTHROUGH_BUFFER_DURATION_US;
      offloadBufferDurationUs = OFFLOAD_BUFFER_DURATION_US;
      ac3BufferMultiplicationFactor = AC3_BUFFER_MULTIPLICATION_FACTOR;
    }

    /**
     * Sets the minimum length for PCM {@link AudioTrack} buffers, in microseconds. Default is
     * {@value #MIN_PCM_BUFFER_DURATION_US}.
     */
    public Builder setMinPcmBufferDurationUs(int minPcmBufferDurationUs) {
      this.minPcmBufferDurationUs = minPcmBufferDurationUs;
      return this;
    }

    /**
     * Sets the maximum length for PCM {@link AudioTrack} buffers, in microseconds. Default is
     * {@value #MAX_PCM_BUFFER_DURATION_US}.
     */
    public Builder setMaxPcmBufferDurationUs(int maxPcmBufferDurationUs) {
      this.maxPcmBufferDurationUs = maxPcmBufferDurationUs;
      return this;
    }

    /**
     * Sets the multiplication factor to apply to the minimum buffer size requested. Default is
     * {@value #PCM_BUFFER_MULTIPLICATION_FACTOR}.
     */
    public Builder setPcmBufferMultiplicationFactor(int pcmBufferMultiplicationFactor) {
      this.pcmBufferMultiplicationFactor = pcmBufferMultiplicationFactor;
      return this;
    }

    /**
     * Sets the length for passthrough {@link AudioTrack} buffers, in microseconds. Default is
     * {@value #PASSTHROUGH_BUFFER_DURATION_US}.
     */
    public Builder setPassthroughBufferDurationUs(int passthroughBufferDurationUs) {
      this.passthroughBufferDurationUs = passthroughBufferDurationUs;
      return this;
    }

    /**
     * The length for offload {@link AudioTrack} buffers, in microseconds. Default is {@value
     * #OFFLOAD_BUFFER_DURATION_US}.
     */
    public Builder setOffloadBufferDurationUs(int offloadBufferDurationUs) {
      this.offloadBufferDurationUs = offloadBufferDurationUs;
      return this;
    }

    /**
     * Sets the multiplication factor to apply to the passthrough buffer for AC3 to avoid underruns
     * on some devices (e.g., Broadcom 7271). Default is {@value #AC3_BUFFER_MULTIPLICATION_FACTOR}.
     */
    public Builder setAc3BufferMultiplicationFactor(int ac3BufferMultiplicationFactor) {
      this.ac3BufferMultiplicationFactor = ac3BufferMultiplicationFactor;
      return this;
    }

    /** Build the {@link DefaultAudioTrackBufferSizeProvider}. */
    public DefaultAudioTrackBufferSizeProvider build() {
      return new DefaultAudioTrackBufferSizeProvider(this);
    }
  }

  /** The minimum length for PCM {@link AudioTrack} buffers, in microseconds. */
  protected final int minPcmBufferDurationUs;
  /** The maximum length for PCM {@link AudioTrack} buffers, in microseconds. */
  protected final int maxPcmBufferDurationUs;
  /** The multiplication factor to apply to the minimum buffer size requested. */
  protected final int pcmBufferMultiplicationFactor;
  /** The length for passthrough {@link AudioTrack} buffers, in microseconds. */
  protected final int passthroughBufferDurationUs;
  /** The length for offload {@link AudioTrack} buffers, in microseconds. */
  protected final int offloadBufferDurationUs;
  /**
   * The multiplication factor to apply to AC3 passthrough buffer to avoid underruns on some devices
   * (e.g., Broadcom 7271).
   */
  public final int ac3BufferMultiplicationFactor;

  protected DefaultAudioTrackBufferSizeProvider(Builder builder) {
    minPcmBufferDurationUs = builder.minPcmBufferDurationUs;
    maxPcmBufferDurationUs = builder.maxPcmBufferDurationUs;
    pcmBufferMultiplicationFactor = builder.pcmBufferMultiplicationFactor;
    passthroughBufferDurationUs = builder.passthroughBufferDurationUs;
    offloadBufferDurationUs = builder.offloadBufferDurationUs;
    ac3BufferMultiplicationFactor = builder.ac3BufferMultiplicationFactor;
  }

  @Override
  public int getBufferSizeInBytes(
      int minBufferSizeInBytes,
      @C.Encoding int encoding,
      @OutputMode int outputMode,
      int pcmFrameSize,
      int sampleRate,
      double maxAudioTrackPlaybackSpeed) {
    int bufferSize =
        get1xBufferSizeInBytes(
            minBufferSizeInBytes, encoding, outputMode, pcmFrameSize, sampleRate);
    // Maintain the buffer duration by scaling the size accordingly.
    bufferSize = (int) (bufferSize * maxAudioTrackPlaybackSpeed);
    // Buffer size must not be lower than the AudioTrack min buffer size for this format.
    bufferSize = max(minBufferSizeInBytes, bufferSize);
    // Increase if needed to make sure the buffers contains an integer number of frames.
    return (bufferSize + pcmFrameSize - 1) / pcmFrameSize * pcmFrameSize;
  }

  /** Returns the buffer size for playback at 1x speed. */
  protected int get1xBufferSizeInBytes(
      int minBufferSizeInBytes, int encoding, int outputMode, int pcmFrameSize, int sampleRate) {
    switch (outputMode) {
      case OUTPUT_MODE_PCM:
        return getPcmBufferSizeInBytes(minBufferSizeInBytes, sampleRate, pcmFrameSize);
      case OUTPUT_MODE_PASSTHROUGH:
        return getPassthroughBufferSizeInBytes(encoding);
      case OUTPUT_MODE_OFFLOAD:
        return getOffloadBufferSizeInBytes(encoding);
      default:
        throw new IllegalArgumentException();
    }
  }

  /** Returns the buffer size for PCM playback. */
  protected int getPcmBufferSizeInBytes(int minBufferSizeInBytes, int samplingRate, int frameSize) {
    int targetBufferSize = minBufferSizeInBytes * pcmBufferMultiplicationFactor;
    int minAppBufferSize = durationUsToBytes(minPcmBufferDurationUs, samplingRate, frameSize);
    int maxAppBufferSize = durationUsToBytes(maxPcmBufferDurationUs, samplingRate, frameSize);
    return constrainValue(targetBufferSize, minAppBufferSize, maxAppBufferSize);
  }

  /** Returns the buffer size for passthrough playback. */
  protected int getPassthroughBufferSizeInBytes(@C.Encoding int encoding) {
    int bufferSizeUs = passthroughBufferDurationUs;
    if (encoding == C.ENCODING_AC3) {
      bufferSizeUs *= ac3BufferMultiplicationFactor;
    }
    int maxByteRate = getMaximumEncodedRateBytesPerSecond(encoding);
    return checkedCast((long) bufferSizeUs * maxByteRate / C.MICROS_PER_SECOND);
  }

  /** Returns the buffer size for offload playback. */
  protected int getOffloadBufferSizeInBytes(@C.Encoding int encoding) {
    int maxByteRate = getMaximumEncodedRateBytesPerSecond(encoding);
    return checkedCast((long) offloadBufferDurationUs * maxByteRate / C.MICROS_PER_SECOND);
  }

  protected static int durationUsToBytes(int durationUs, int samplingRate, int frameSize) {
    return checkedCast((long) durationUs * samplingRate * frameSize / C.MICROS_PER_SECOND);
  }

  protected static int getMaximumEncodedRateBytesPerSecond(@C.Encoding int encoding) {
    switch (encoding) {
      case C.ENCODING_MP3:
        return MpegAudioUtil.MAX_RATE_BYTES_PER_SECOND;
      case C.ENCODING_AAC_LC:
        return AacUtil.AAC_LC_MAX_RATE_BYTES_PER_SECOND;
      case C.ENCODING_AAC_HE_V1:
        return AacUtil.AAC_HE_V1_MAX_RATE_BYTES_PER_SECOND;
      case C.ENCODING_AAC_HE_V2:
        return AacUtil.AAC_HE_V2_MAX_RATE_BYTES_PER_SECOND;
      case C.ENCODING_AAC_XHE:
        return AacUtil.AAC_XHE_MAX_RATE_BYTES_PER_SECOND;
      case C.ENCODING_AAC_ELD:
        return AacUtil.AAC_ELD_MAX_RATE_BYTES_PER_SECOND;
      case C.ENCODING_AC3:
        return Ac3Util.AC3_MAX_RATE_BYTES_PER_SECOND;
      case C.ENCODING_E_AC3:
      case C.ENCODING_E_AC3_JOC:
        return Ac3Util.E_AC3_MAX_RATE_BYTES_PER_SECOND;
      case C.ENCODING_AC4:
        return Ac4Util.MAX_RATE_BYTES_PER_SECOND;
      case C.ENCODING_DTS:
        return DtsUtil.DTS_MAX_RATE_BYTES_PER_SECOND;
      case C.ENCODING_DTS_HD:
        return DtsUtil.DTS_HD_MAX_RATE_BYTES_PER_SECOND;
      case C.ENCODING_DOLBY_TRUEHD:
        return Ac3Util.TRUEHD_MAX_RATE_BYTES_PER_SECOND;
      case C.ENCODING_PCM_16BIT:
      case C.ENCODING_PCM_16BIT_BIG_ENDIAN:
      case C.ENCODING_PCM_24BIT:
      case C.ENCODING_PCM_32BIT:
      case C.ENCODING_PCM_8BIT:
      case C.ENCODING_PCM_FLOAT:
      case C.ENCODING_AAC_ER_BSAC:
      case C.ENCODING_INVALID:
      case Format.NO_VALUE:
      default:
        throw new IllegalArgumentException();
    }
  }
}
