content: /*
   * Copyright (C) 2019 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 com.google.android.exoplayer2.audio;
  
  import androidx.annotation.Nullable;
  import com.google.android.exoplayer2.C;
  import com.google.android.exoplayer2.Format;
  import com.google.android.exoplayer2.drm.DrmInitData;
  import com.google.android.exoplayer2.util.MimeTypes;
  import com.google.android.exoplayer2.util.ParsableBitArray;
  import com.google.android.exoplayer2.util.ParsableByteArray;
  import java.nio.ByteBuffer;
  
  /** Utility methods for parsing AC-4 frames, which are access units in AC-4 bitstreams. */
  public final class Ac4Util {
  
    /** Holds sample format information as presented by a syncframe header. */
    public static final class SyncFrameInfo {
  
      /** The bitstream version. */
      public final int bitstreamVersion;
      /** The audio sampling rate in Hz. */
      public final int sampleRate;
      /** The number of audio channels */
      public final int channelCount;
      /** The size of the frame. */
      public final int frameSize;
      /** Number of audio samples in the frame. */
      public final int sampleCount;
  
      private SyncFrameInfo(
          int bitstreamVersion, int channelCount, int sampleRate, int frameSize, int sampleCount) {
        this.bitstreamVersion = bitstreamVersion;
        this.channelCount = channelCount;
        this.sampleRate = sampleRate;
        this.frameSize = frameSize;
        this.sampleCount = sampleCount;
      }
    }
  
    public static final int AC40_SYNCWORD = 0xAC40;
    public static final int AC41_SYNCWORD = 0xAC41;
  
    /** Maximum rate for an AC-4 audio stream, in bytes per second. */
    public static final int MAX_RATE_BYTES_PER_SECOND = 2688 * 1000 / 8;
  
    /** The channel count of AC-4 stream. */
    // TODO: Parse AC-4 stream channel count.
    private static final int CHANNEL_COUNT_2 = 2;
    /**
     * The AC-4 sync frame header size for extractor. The seven bytes are 0xAC, 0x40, 0xFF, 0xFF,
     * sizeByte1, sizeByte2, sizeByte3. See ETSI TS 103 190-1 V1.3.1, Annex G
     */
    public static final int SAMPLE_HEADER_SIZE = 7;
    /**
     * The header size for AC-4 parser. Only needs to be as big as we need to read, not the full
     * header size.
     */
    public static final int HEADER_SIZE_FOR_PARSER = 16;
    /**
     * Number of audio samples in the frame. Defined in IEC61937-14:2017 table 5 and 6. This table
     * provides the number of samples per frame at the playback sampling frequency of 48 kHz. For 44.1
     * kHz, only frame_rate_index(13) is valid and corresponding sample count is 2048.
     */
    private static final int[] SAMPLE_COUNT =
        new int[] {
          /* [ 0]  23.976 fps */ 2002,
          /* [ 1]  24     fps */ 2000,
          /* [ 2]  25     fps */ 1920,
          /* [ 3]  29.97  fps */ 1601, // 1601 | 1602 | 1601 | 1602 | 1602
          /* [ 4]  30     fps */ 1600,
          /* [ 5]  47.95  fps */ 1001,
          /* [ 6]  48     fps */ 1000,
          /* [ 7]  50     fps */ 960,
          /* [ 8]  59.94  fps */ 800, //  800 |  801 |  801 |  801 |  801
          /* [ 9]  60     fps */ 800,
          /* [10] 100     fps */ 480,
          /* [11] 119.88  fps */ 400, //  400 |  400 |  401 |  400 |  401
          /* [12] 120     fps */ 400,
          /* [13]  23.438 fps */ 2048
        };
  
    /**
     * Returns the AC-4 format given {@code data} containing the AC4SpecificBox according to ETSI TS
     * 103 190-1 Annex E. The reading position of {@code data} will be modified.
     *
     * @param data The AC4SpecificBox to parse.
     * @param trackId The track identifier to set on the format.
     * @param language The language to set on the format.
     * @param drmInitData {@link DrmInitData} to be included in the format.
     * @return The AC-4 format parsed from data in the header.
     */
    public static Format parseAc4AnnexEFormat(
        ParsableByteArray data, String trackId, String language, @Nullable DrmInitData drmInitData) {
      data.skipBytes(1); // ac4_dsi_version, bitstream_version[0:5]
      int sampleRate = ((data.readUnsignedByte() & 0x20) >> 5 == 1) ? 48000 : 44100;
      return new Format.Builder()
          .setId(trackId)
          .setSampleMimeType(MimeTypes.AUDIO_AC4)
          .setChannelCount(CHANNEL_COUNT_2)
          .setSampleRate(sampleRate)
          .setDrmInitData(drmInitData)
          .setLanguage(language)
          .build();
    }
  
    /**
     * Returns AC-4 format information given {@code data} containing a syncframe. The reading position
     * of {@code data} will be modified.
     *
     * @param data The data to parse, positioned at the start of the syncframe.
     * @return The AC-4 format data parsed from the header.
     */
    public static SyncFrameInfo parseAc4SyncframeInfo(ParsableBitArray data) {
      int headerSize = 0;
      int syncWord = data.readBits(16);
      headerSize += 2;
      int frameSize = data.readBits(16);
      headerSize += 2;
      if (frameSize == 0xFFFF) {
        frameSize = data.readBits(24);
        headerSize += 3; // Extended frame_size
      }
      frameSize += headerSize;
      if (syncWord == AC41_SYNCWORD) {
        frameSize += 2; // crc_word
      }
      int bitstreamVersion = data.readBits(2);
      if (bitstreamVersion == 3) {
        bitstreamVersion += readVariableBits(data, /* bitsPerRead= */ 2);
      }
      int sequenceCounter = data.readBits(10);
      if (data.readBit()) { // b_wait_frames
        if (data.readBits(3) > 0) { // wait_frames
          data.skipBits(2); // reserved
        }
      }
      int sampleRate = data.readBit() ? 48000 : 44100;
      int frameRateIndex = data.readBits(4);
      int sampleCount = 0;
      if (sampleRate == 44100 && frameRateIndex == 13) {
        sampleCount = SAMPLE_COUNT[frameRateIndex];
      } else if (sampleRate == 48000 && frameRateIndex < SAMPLE_COUNT.length) {
        sampleCount = SAMPLE_COUNT[frameRateIndex];
        switch (sequenceCounter % 5) {
          case 1: // fall through
          case 3:
            if (frameRateIndex == 3 || frameRateIndex == 8) {
              sampleCount++;
            }
            break;
          case 2:
            if (frameRateIndex == 8 || frameRateIndex == 11) {
              sampleCount++;
            }
            break;
          case 4:
            if (frameRateIndex == 3 || frameRateIndex == 8 || frameRateIndex == 11) {
              sampleCount++;
            }
            break;
          default:
            break;
        }
      }
      return new SyncFrameInfo(bitstreamVersion, CHANNEL_COUNT_2, sampleRate, frameSize, sampleCount);
    }
  
    /**
     * Returns the size in bytes of the given AC-4 syncframe.
     *
     * @param data The syncframe to parse.
     * @param syncword The syncword value for the syncframe.
     * @return The syncframe size in bytes, or {@link C#LENGTH_UNSET} if the input is invalid.
     */
    public static int parseAc4SyncframeSize(byte[] data, int syncword) {
      if (data.length < 7) {
        return C.LENGTH_UNSET;
      }
      int headerSize = 2; // syncword
      int frameSize = ((data[2] & 0xFF) << 8) | (data[3] & 0xFF);
      headerSize += 2;
      if (frameSize == 0xFFFF) {
        frameSize = ((data[4] & 0xFF) << 16) | ((data[5] & 0xFF) << 8) | (data[6] & 0xFF);
        headerSize += 3;
      }
      if (syncword == AC41_SYNCWORD) {
        headerSize += 2;
      }
      frameSize += headerSize;
      return frameSize;
    }
  
    /**
     * Reads the number of audio samples represented by the given AC-4 syncframe. The buffer's
     * position is not modified.
     *
     * @param buffer The {@link ByteBuffer} from which to read the syncframe.
     * @return The number of audio samples represented by the syncframe.
     */
    public static int parseAc4SyncframeAudioSampleCount(ByteBuffer buffer) {
      byte[] bufferBytes = new byte[HEADER_SIZE_FOR_PARSER];
      int position = buffer.position();
      buffer.get(bufferBytes);
      buffer.position(position);
      return parseAc4SyncframeInfo(new ParsableBitArray(bufferBytes)).sampleCount;
    }
  
    /** Populates {@code buffer} with an AC-4 sample header for a sample of the specified size. */
    public static void getAc4SampleHeader(int size, ParsableByteArray buffer) {
      // See ETSI TS 103 190-1 V1.3.1, Annex G.
      buffer.reset(SAMPLE_HEADER_SIZE);
      byte[] data = buffer.getData();
      data[0] = (byte) 0xAC;
      data[1] = 0x40;
      data[2] = (byte) 0xFF;
      data[3] = (byte) 0xFF;
      data[4] = (byte) ((size >> 16) & 0xFF);
      data[5] = (byte) ((size >> 8) & 0xFF);
      data[6] = (byte) (size & 0xFF);
    }
  
    private static int readVariableBits(ParsableBitArray data, int bitsPerRead) {
      int value = 0;
      while (true) {
        value += data.readBits(bitsPerRead);
        if (!data.readBit()) {
          break;
        }
        value++;
        value <<= bitsPerRead;
      }
      return value;
    }
  
    private Ac4Util() {}
  }
  