/*
 * Copyright (C) 2016 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.extractor;

import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.MimeTypes;

/**
 * An MPEG audio frame header.
 */
public final class MpegAudioHeader {

  /**
   * Theoretical maximum frame size for an MPEG audio stream, which occurs when playing a Layer 2
   * MPEG 2.5 audio stream at 16 kb/s (with padding). The size is 1152 sample/frame *
   * 160000 bit/s / (8000 sample/s * 8 bit/byte) + 1 padding byte/frame = 2881 byte/frame.
   * The next power of two size is 4 KiB.
   */
  public static final int MAX_FRAME_SIZE_BYTES = 4096;

  private static final String[] MIME_TYPE_BY_LAYER =
      new String[] {MimeTypes.AUDIO_MPEG_L1, MimeTypes.AUDIO_MPEG_L2, MimeTypes.AUDIO_MPEG};
  private static final int[] SAMPLING_RATE_V1 = {44100, 48000, 32000};
  private static final int[] BITRATE_V1_L1 =
      {32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448};
  private static final int[] BITRATE_V2_L1 =
      {32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256};
  private static final int[] BITRATE_V1_L2 =
      {32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384};
  private static final int[] BITRATE_V1_L3 =
      {32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320};
  private static final int[] BITRATE_V2 =
      {8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160};

  /**
   * Returns the size of the frame associated with {@code header}, or {@link C#LENGTH_UNSET} if it
   * is invalid.
   */
  public static int getFrameSize(int header) {
    if ((header & 0xFFE00000) != 0xFFE00000) {
      return C.LENGTH_UNSET;
    }

    int version = (header >>> 19) & 3;
    if (version == 1) {
      return C.LENGTH_UNSET;
    }

    int layer = (header >>> 17) & 3;
    if (layer == 0) {
      return C.LENGTH_UNSET;
    }

    int bitrateIndex = (header >>> 12) & 15;
    if (bitrateIndex == 0 || bitrateIndex == 0xF) {
      // Disallow "free" bitrate.
      return C.LENGTH_UNSET;
    }

    int samplingRateIndex = (header >>> 10) & 3;
    if (samplingRateIndex == 3) {
      return C.LENGTH_UNSET;
    }

    int samplingRate = SAMPLING_RATE_V1[samplingRateIndex];
    if (version == 2) {
      // Version 2
      samplingRate /= 2;
    } else if (version == 0) {
      // Version 2.5
      samplingRate /= 4;
    }

    int bitrate;
    int padding = (header >>> 9) & 1;
    if (layer == 3) {
      // Layer I (layer == 3)
      bitrate = version == 3 ? BITRATE_V1_L1[bitrateIndex - 1] : BITRATE_V2_L1[bitrateIndex - 1];
      return (12000 * bitrate / samplingRate + padding) * 4;
    } else {
      // Layer II (layer == 2) or III (layer == 1)
      if (version == 3) {
        bitrate = layer == 2 ? BITRATE_V1_L2[bitrateIndex - 1] : BITRATE_V1_L3[bitrateIndex - 1];
      } else {
        // Version 2 or 2.5.
        bitrate = BITRATE_V2[bitrateIndex - 1];
      }
    }

    if (version == 3) {
      // Version 1
      return 144000 * bitrate / samplingRate + padding;
    } else {
      // Version 2 or 2.5
      return (layer == 1 ? 72000 : 144000) * bitrate / samplingRate + padding;
    }
  }

  /**
   * Parses {@code headerData}, populating {@code header} with the parsed data.
   *
   * @param headerData Header data to parse.
   * @param header Header to populate with data from {@code headerData}.
   * @return True if the header was populated. False otherwise, indicating that {@code headerData}
   *     is not a valid MPEG audio header.
   */
  public static boolean populateHeader(int headerData, MpegAudioHeader header) {
    if ((headerData & 0xFFE00000) != 0xFFE00000) {
      return false;
    }

    int version = (headerData >>> 19) & 3;
    if (version == 1) {
      return false;
    }

    int layer = (headerData >>> 17) & 3;
    if (layer == 0) {
      return false;
    }

    int bitrateIndex = (headerData >>> 12) & 15;
    if (bitrateIndex == 0 || bitrateIndex == 0xF) {
      // Disallow "free" bitrate.
      return false;
    }

    int samplingRateIndex = (headerData >>> 10) & 3;
    if (samplingRateIndex == 3) {
      return false;
    }

    int sampleRate = SAMPLING_RATE_V1[samplingRateIndex];
    if (version == 2) {
      // Version 2
      sampleRate /= 2;
    } else if (version == 0) {
      // Version 2.5
      sampleRate /= 4;
    }

    int padding = (headerData >>> 9) & 1;
    int bitrate, frameSize, samplesPerFrame;
    if (layer == 3) {
      // Layer I (layer == 3)
      bitrate = version == 3 ? BITRATE_V1_L1[bitrateIndex - 1] : BITRATE_V2_L1[bitrateIndex - 1];
      frameSize = (12000 * bitrate / sampleRate + padding) * 4;
      samplesPerFrame = 384;
    } else {
      // Layer II (layer == 2) or III (layer == 1)
      if (version == 3) {
        // Version 1
        bitrate = layer == 2 ? BITRATE_V1_L2[bitrateIndex - 1] : BITRATE_V1_L3[bitrateIndex - 1];
        samplesPerFrame = 1152;
        frameSize = 144000 * bitrate / sampleRate + padding;
      } else {
        // Version 2 or 2.5.
        bitrate = BITRATE_V2[bitrateIndex - 1];
        samplesPerFrame = layer == 1 ? 576 : 1152;
        frameSize = (layer == 1 ? 72000 : 144000) * bitrate / sampleRate + padding;
      }
    }

    String mimeType = MIME_TYPE_BY_LAYER[3 - layer];
    int channels = ((headerData >> 6) & 3) == 3 ? 1 : 2;
    header.setValues(version, mimeType, frameSize, sampleRate, channels, bitrate * 1000,
        samplesPerFrame);
    return true;
  }

  /** MPEG audio header version. */
  public int version;
  /** The mime type. */
  public String mimeType;
  /** Size of the frame associated with this header, in bytes. */
  public int frameSize;
  /** Sample rate in samples per second. */
  public int sampleRate;
  /** Number of audio channels in the frame. */
  public int channels;
  /** Bitrate of the frame in bit/s. */
  public int bitrate;
  /** Number of samples stored in the frame. */
  public int samplesPerFrame;

  private void setValues(int version, String mimeType, int frameSize, int sampleRate, int channels,
      int bitrate, int samplesPerFrame) {
    this.version = version;
    this.mimeType = mimeType;
    this.frameSize = frameSize;
    this.sampleRate = sampleRate;
    this.channels = channels;
    this.bitrate = bitrate;
    this.samplesPerFrame = samplesPerFrame;
  }

}
