content: /*
   * Copyright 2002-2019 Drew Noakes and contributors
   *
   *    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
   *
   *        https://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.
   *
   * More information about this project is available at:
   *
   *    https://drewnoakes.com/code/exif/
   *    https://github.com/drewnoakes/metadata-extractor
   */
  package com.drew.metadata.mp3;
  
  import com.drew.imaging.ImageProcessingException;
  import com.drew.lang.SequentialReader;
  import com.drew.lang.StreamReader;
  import com.drew.lang.annotations.NotNull;
  import com.drew.metadata.Metadata;
  
  import java.io.IOException;
  import java.io.InputStream;
  
  /**
   * Sources: https://id3.org/mp3Frame
   *          https://www.loc.gov/preservation/digital/formats/fdd/fdd000105.shtml
   *
   * @author Payton Garland
   */
  public class Mp3Reader
  {
      public void extract(@NotNull final InputStream inputStream, @NotNull final Metadata metadata)
      {
          Mp3Directory directory = new Mp3Directory();
          metadata.addDirectory(directory);
  
          try {
              SequentialReader reader = new StreamReader(inputStream);
  
              int header = reader.getInt32();
  
              // ID: MPEG-2.5, MPEG-2, or MPEG-1
              int id = 0;
              switch ((header & 0x000180000) >> 19) {
                  case 0:
                      throw new ImageProcessingException("MPEG-2.5 not supported.");
                  case 2:
                      directory.setString(Mp3Directory.TAG_ID, "MPEG-2");
                      id = 2;
                      break;
                  case 3:
                      directory.setString(Mp3Directory.TAG_ID, "MPEG-1");
                      id = 1;
                      break;
              }
  
              // Layer Type: 1, 2, 3, or not defined
              int layer = ((header & 0x00060000) >> 17);
              switch (layer) {
                  case 0:
                      directory.setString(Mp3Directory.TAG_LAYER, "Not defined");
                      break;
                  case 1:
                      directory.setString(Mp3Directory.TAG_LAYER, "Layer III");
                      break;
                  case 2:
                      directory.setString(Mp3Directory.TAG_LAYER, "Layer II");
                      break;
                  case 3:
                      directory.setString(Mp3Directory.TAG_LAYER, "Layer I");
                      break;
              }
  
  
              int protectionBit = ((header & 0x00010000) >> 16);
  
              // Bitrate: depends on ID and Layer
              int bitrate = ((header & 0x0000F000) >> 12);
              if (bitrate != 0 && bitrate != 15)
                  directory.setInt(Mp3Directory.TAG_BITRATE, setBitrate(bitrate, layer, id));
  
              // Frequency: depends on ID
              int frequency = ((header & 0x00000C00) >> 10);
              int[][] frequencyMapping = new int[2][3];
              frequencyMapping[0] = new int[]{44100, 48000, 32000};
              frequencyMapping[1] = new int[]{22050, 24000, 16000};
              if (frequency <= 2) {
                  if (id == 2) {
                      directory.setInt(Mp3Directory.TAG_FREQUENCY, frequencyMapping[1][frequency]);
                      frequency = frequencyMapping[1][frequency];
                  } else if (id == 1) {
                      directory.setInt(Mp3Directory.TAG_FREQUENCY, frequencyMapping[0][frequency]);
                      frequency = frequencyMapping[0][frequency];
                  }
              } else {
                  directory.addError("Invalid frequency index.");
                  frequency = -1;
              }
  
              int paddingBit = ((header & 0x00000200) >> 9);
  
              // Encoding type: Stereo, Joint Stereo, Dual Channel, or Mono
              int mode = ((header & 0x000000C0) >> 6);
              switch (mode){
                  case 0:
                      directory.setString(Mp3Directory.TAG_MODE, "Stereo");
                      break;
                  case 1:
                      directory.setString(Mp3Directory.TAG_MODE, "Joint stereo");
                      break;
                  case 2:
                      directory.setString(Mp3Directory.TAG_MODE, "Dual channel");
                      break;
                  case 3:
                      directory.setString(Mp3Directory.TAG_MODE, "Mono");
                      break;
              }
  
              // Copyright boolean
              int copyright = ((header & 0x00000008) >> 3);
              switch (copyright) {
                  case 0:
                      directory.setString(Mp3Directory.TAG_COPYRIGHT, "False");
                      break;
                  case 1:
                      directory.setString(Mp3Directory.TAG_COPYRIGHT, "True");
                      break;
              }
  
              int emphasis = (header & 0x00000003);
              switch (emphasis) {
                  case 0:
                      directory.setString(Mp3Directory.TAG_EMPHASIS, "none");
                      break;
                  case 1:
                      directory.setString(Mp3Directory.TAG_EMPHASIS, "50/15ms");
                      break;
                  case 3:
                      directory.setString(Mp3Directory.TAG_EMPHASIS, "CCITT j.17");
                      break;
              }
  
              if (frequency != -1 && bitrate != 0 && bitrate != 15) {
                  int frameSize = ((setBitrate(bitrate, layer, id) * 1000) * 144) / frequency;
                  directory.setString(Mp3Directory.TAG_FRAME_SIZE, frameSize + " bytes");
              }
          } catch (IOException e) {
              directory.addError(e.getMessage());
          } catch (ImageProcessingException e) {
              directory.addError(e.getMessage());
          }
      }
  
      private static int setBitrate(int bitrate, int layer, int id)
      {
          int[][] bitrateMapping = new int[14][6];
          bitrateMapping[0] = new int[]{32, 32, 32, 32, 32, 8};
          bitrateMapping[1] = new int[]{64, 48, 40, 64, 48, 16};
          bitrateMapping[2] = new int[]{96, 56, 48, 96, 56, 24};
          bitrateMapping[3] = new int[]{128, 64, 56, 128, 64, 32};
          bitrateMapping[4] = new int[]{160, 80, 64, 160, 80, 64};
          bitrateMapping[5] = new int[]{192, 96, 80, 192, 96, 80};
          bitrateMapping[6] = new int[]{224, 112, 96, 224, 112, 56};
          bitrateMapping[7] = new int[]{256, 128, 112, 256, 128, 64};
          bitrateMapping[8] = new int[]{288, 160, 128, 28, 160, 128};
          bitrateMapping[9] = new int[]{320, 192, 160, 320, 192, 160};
          bitrateMapping[10] = new int[]{352, 224, 192, 352, 224, 112};
          bitrateMapping[11] = new int[]{384, 256, 224, 384, 256, 128};
          bitrateMapping[12] = new int[]{416, 320, 256, 416, 320, 256};
          bitrateMapping[13] = new int[]{448, 384, 320, 448, 384, 320};
  
          int xPos = 0;
          int yPos = bitrate - 1;
  
          if (id == 2) {
              // MPEG-2
              switch (layer) {
                  case 1:
                      xPos = 5;
                      break;
                  case 2:
                      xPos = 4;
                      break;
                  case 3:
                      xPos = 3;
                      break;
              }
          } else if (id == 1) {
              // MPEG-1
              switch (layer) {
                  case 1:
                      xPos = 2;
                      break;
                  case 2:
                      xPos = 1;
                      break;
                  case 3:
                      xPos = 0;
                      break;
              }
          }
  
          return bitrateMapping[yPos][xPos];
      }
  
  /*
      // https://phoxis.org/2010/05/08/synch-safe/
      private static int getSyncSafeSize(int decode)
      {
          int a = decode & 0xFF;
          int b = (decode >> 8) & 0xFF;
          int c  = (decode >> 16) & 0xFF;
          int d = (decode >> 24) & 0xFF;
  
          int decoded = 0x0;
          decoded = decoded | a;
          decoded = decoded | (b << 7);
          decoded = decoded | (c << 14);
          decoded = decoded | (d << 21);
          return decoded;
      }
  */
  }
  