/**
   * Sets the priority of the calling thread to a specific value.
   *
   * @param threadPriority the priority to be set on the calling thread
   */
  public static void setThreadPriority(int threadPriority) {
    Throwable exception = null;

    try {
      Process.setThreadPriority(threadPriority);
    } catch (IllegalArgumentException iae) {
      exception = iae;
    } catch (SecurityException se) {
      exception = se;
    }
    if (exception != null) logger.warn("Failed to set thread priority.", exception);
  }
    /** Configures echo cancellation and noise suppression effects. */
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    private void configureEffects() {
      if (!AndroidUtils.hasAPI(16)) return;

      AudioSystem audioSystem =
          AudioSystem.getAudioSystem(AudioSystem.LOCATOR_PROTOCOL_AUDIORECORD);

      // Creates echo canceler if available
      if (AcousticEchoCanceler.isAvailable()) {
        AcousticEchoCanceler echoCanceller =
            AcousticEchoCanceler.create(audioRecord.getAudioSessionId());
        if (echoCanceller != null) {
          echoCanceller.setEnableStatusListener(this);
          echoCanceller.setEnabled(audioSystem.isEchoCancel());
          logger.info("Echo cancellation: " + echoCanceller.getEnabled());
        }
      }

      // Automatic gain control
      if (AutomaticGainControl.isAvailable()) {
        AutomaticGainControl agc = AutomaticGainControl.create(audioRecord.getAudioSessionId());
        if (agc != null) {
          agc.setEnableStatusListener(this);
          agc.setEnabled(audioSystem.isAutomaticGainControl());
          logger.info("Auto gain control: " + agc.getEnabled());
        }
      }

      // Creates noise suppressor if available
      if (NoiseSuppressor.isAvailable()) {
        NoiseSuppressor noiseSuppressor = NoiseSuppressor.create(audioRecord.getAudioSessionId());
        if (noiseSuppressor != null) {
          noiseSuppressor.setEnableStatusListener(this);
          noiseSuppressor.setEnabled(audioSystem.isDenoise());
          logger.info("Noise suppressor: " + noiseSuppressor.getEnabled());
        }
      }
    }
 @Override
 public void onEnableStatusChange(AudioEffect effect, boolean enabled) {
   logger.info(effect.getDescriptor() + " : " + enabled);
 }
/**
 * Implements an audio <tt>CaptureDevice</tt> using {@link AudioRecord}.
 *
 * @author Lyubomir Marinov
 */
public class DataSource extends AbstractPullBufferCaptureDevice {
  /**
   * The <tt>Logger</tt> used by the <tt>DataSource</tt> class and its instances for logging output.
   */
  private static final Logger logger = Logger.getLogger(DataSource.class);

  /**
   * The priority to be set to the thread executing the {@link AudioRecordStream#read(Buffer)}
   * method of a given <tt>AudioRecordStream</tt>.
   */
  private static final int THREAD_PRIORITY = Process.THREAD_PRIORITY_URGENT_AUDIO;

  /** Initializes a new <tt>DataSource</tt> instance. */
  public DataSource() {}

  /**
   * Initializes a new <tt>DataSource</tt> from a specific <tt>MediaLocator</tt>.
   *
   * @param locator the <tt>MediaLocator</tt> to create the new instance from
   */
  public DataSource(MediaLocator locator) {
    super(locator);
  }

  /**
   * Creates a new <tt>PullBufferStream</tt> which is to be at a specific zero-based index in the
   * list of streams of this <tt>PullBufferDataSource</tt>. The <tt>Format</tt>-related information
   * of the new instance is to be abstracted by a specific <tt>FormatControl</tt>.
   *
   * @param streamIndex the zero-based index of the <tt>PullBufferStream</tt> in the list of streams
   *     of this <tt>PullBufferDataSource</tt>
   * @param formatControl the <tt>FormatControl</tt> which is to abstract the
   *     <tt>Format</tt>-related information of the new instance
   * @return a new <tt>PullBufferStream</tt> which is to be at the specified <tt>streamIndex</tt> in
   *     the list of streams of this <tt>PullBufferDataSource</tt> and which has its
   *     <tt>Format</tt>-related information abstracted by the specified <tt>formatControl</tt>
   * @see AbstractPullBufferCaptureDevice#createStream(int, FormatControl)
   */
  protected AbstractPullBufferStream createStream(int streamIndex, FormatControl formatControl) {
    return new AudioRecordStream(this, formatControl);
  }

  /**
   * Opens a connection to the media source specified by the <tt>MediaLocator</tt> of this
   * <tt>DataSource</tt>.
   *
   * @throws IOException if anything goes wrong while opening the connection to the media source
   *     specified by the <tt>MediaLocator</tt> of this <tt>DataSource</tt>
   * @see AbstractPullBufferCaptureDevice#doConnect()
   */
  @Override
  protected void doConnect() throws IOException {
    super.doConnect();

    /*
     * XXX The AudioRecordStream will connect upon start in order to be able
     * to respect requests to set its format.
     */
  }

  /**
   * Closes the connection to the media source specified by the <tt>MediaLocator</tt> of this
   * <tt>DataSource</tt>.
   *
   * @see AbstractPullBufferCaptureDevice#doDisconnect()
   */
  @Override
  protected void doDisconnect() {
    synchronized (getStreamSyncRoot()) {
      Object[] streams = streams();

      if (streams != null) for (Object stream : streams) ((AudioRecordStream) stream).disconnect();
    }

    super.doDisconnect();
  }

  /** Sets the priority of the calling thread to {@link #THREAD_PRIORITY}. */
  public static void setThreadPriority() {
    setThreadPriority(THREAD_PRIORITY);
  }

  /**
   * Sets the priority of the calling thread to a specific value.
   *
   * @param threadPriority the priority to be set on the calling thread
   */
  public static void setThreadPriority(int threadPriority) {
    Throwable exception = null;

    try {
      Process.setThreadPriority(threadPriority);
    } catch (IllegalArgumentException iae) {
      exception = iae;
    } catch (SecurityException se) {
      exception = se;
    }
    if (exception != null) logger.warn("Failed to set thread priority.", exception);
  }

  /**
   * Attempts to set the <tt>Format</tt> to be reported by the <tt>FormatControl</tt> of a
   * <tt>PullBufferStream</tt> at a specific zero-based index in the list of streams of this
   * <tt>PullBufferDataSource</tt>. The <tt>PullBufferStream</tt> does not exist at the time of the
   * attempt to set its <tt>Format</tt>. Override the default behavior which is to not attempt to
   * set the specified <tt>Format</tt> so that they can enable setting the <tt>Format</tt> prior to
   * creating the <tt>PullBufferStream</tt>.
   *
   * @param streamIndex the zero-based index of the <tt>PullBufferStream</tt> the <tt>Format</tt> of
   *     which is to be set
   * @param oldValue the last-known <tt>Format</tt> for the <tt>PullBufferStream</tt> at the
   *     specified <tt>streamIndex</tt>
   * @param newValue the <tt>Format</tt> which is to be set
   * @return the <tt>Format</tt> to be reported by the <tt>FormatControl</tt> of the
   *     <tt>PullBufferStream</tt> at the specified <tt>streamIndex</tt> in the list of streams of
   *     this <tt>PullBufferStream</tt> or <tt>null</tt> if the attempt to set the <tt>Format</tt>
   *     did not success and any last-known <tt>Format</tt> is to be left in effect
   * @see AbstractPullBufferCaptureDevice#setFormat(int, Format, Format)
   */
  @Override
  protected Format setFormat(int streamIndex, Format oldValue, Format newValue) {
    /*
     * Accept format specifications prior to the initialization of
     * AudioRecordStream. Afterwards, AudioRecordStream will decide whether
     * to accept further format specifications.
     */
    return newValue;
  }

  /** Implements an audio <tt>PullBufferStream</tt> using {@link AudioRecord}. */
  private static class AudioRecordStream extends AbstractPullBufferStream<DataSource>
      implements AudioEffect.OnEnableStatusChangeListener {
    /** The <tt>android.media.AudioRecord</tt> which does the actual capturing of audio. */
    private AudioRecord audioRecord;

    /** The <tt>GainControl</tt> through which the volume/gain of captured media is controlled. */
    private final GainControl gainControl;

    /**
     * The length in bytes of the media data read into a <tt>Buffer</tt> via a call to {@link
     * #read(Buffer)}.
     */
    private int length;

    /**
     * The indicator which determines whether this <tt>AudioRecordStream</tt> is to set the priority
     * of the thread in which its {@link #read(Buffer)} method is executed.
     */
    private boolean setThreadPriority = true;

    /**
     * Initializes a new <tt>OpenSLESStream</tt> instance which is to have its
     * <tt>Format</tt>-related information abstracted by a specific <tt>FormatControl</tt>.
     *
     * @param dataSource the <tt>DataSource</tt> which is creating the new instance so that it
     *     becomes one of its <tt>streams</tt>
     * @param formatControl the <tt>FormatControl</tt> which is to abstract the
     *     <tt>Format</tt>-related information of the new instance
     */
    public AudioRecordStream(DataSource dataSource, FormatControl formatControl) {
      super(dataSource, formatControl);

      MediaServiceImpl mediaServiceImpl = NeomediaActivator.getMediaServiceImpl();

      gainControl =
          (mediaServiceImpl == null)
              ? null
              : (GainControl) mediaServiceImpl.getInputVolumeControl();
    }

    /**
     * Opens a connection to the media source of the associated <tt>DataSource</tt>.
     *
     * @throws IOException if anything goes wrong while opening a connection to the media source of
     *     the associated <tt>DataSource</tt>
     */
    public synchronized void connect() throws IOException {
      javax.media.format.AudioFormat af = (javax.media.format.AudioFormat) getFormat();
      int channels = af.getChannels();
      int channelConfig;

      switch (channels) {
        case Format.NOT_SPECIFIED:
        case 1:
          channelConfig = AudioFormat.CHANNEL_IN_MONO;
          break;
        case 2:
          channelConfig = AudioFormat.CHANNEL_IN_STEREO;
          break;
        default:
          throw new IOException("channels");
      }

      int sampleSizeInBits = af.getSampleSizeInBits();
      int audioFormat;

      switch (sampleSizeInBits) {
        case 8:
          audioFormat = AudioFormat.ENCODING_PCM_8BIT;
          break;
        case 16:
          audioFormat = AudioFormat.ENCODING_PCM_16BIT;
          break;
        default:
          throw new IOException("sampleSizeInBits");
      }

      double sampleRate = af.getSampleRate();

      length =
          (int)
              Math.round(
                  20 /* milliseconds */ * (sampleRate / 1000) * channels * (sampleSizeInBits / 8));

      /*
       * Apart from the thread in which #read(Buffer) is executed, use the
       * thread priority for the thread which will create the AudioRecord.
       */
      setThreadPriority();
      try {
        int minBufferSize =
            AudioRecord.getMinBufferSize((int) sampleRate, channelConfig, audioFormat);

        audioRecord =
            new AudioRecord(
                MediaRecorder.AudioSource.DEFAULT,
                (int) sampleRate,
                channelConfig,
                audioFormat,
                Math.max(length, minBufferSize));

        // tries to configure audio effects if available
        configureEffects();
      } catch (IllegalArgumentException iae) {
        IOException ioe = new IOException();

        ioe.initCause(iae);
        throw ioe;
      }

      setThreadPriority = true;
    }

    /** Configures echo cancellation and noise suppression effects. */
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    private void configureEffects() {
      if (!AndroidUtils.hasAPI(16)) return;

      AudioSystem audioSystem =
          AudioSystem.getAudioSystem(AudioSystem.LOCATOR_PROTOCOL_AUDIORECORD);

      // Creates echo canceler if available
      if (AcousticEchoCanceler.isAvailable()) {
        AcousticEchoCanceler echoCanceller =
            AcousticEchoCanceler.create(audioRecord.getAudioSessionId());
        if (echoCanceller != null) {
          echoCanceller.setEnableStatusListener(this);
          echoCanceller.setEnabled(audioSystem.isEchoCancel());
          logger.info("Echo cancellation: " + echoCanceller.getEnabled());
        }
      }

      // Automatic gain control
      if (AutomaticGainControl.isAvailable()) {
        AutomaticGainControl agc = AutomaticGainControl.create(audioRecord.getAudioSessionId());
        if (agc != null) {
          agc.setEnableStatusListener(this);
          agc.setEnabled(audioSystem.isAutomaticGainControl());
          logger.info("Auto gain control: " + agc.getEnabled());
        }
      }

      // Creates noise suppressor if available
      if (NoiseSuppressor.isAvailable()) {
        NoiseSuppressor noiseSuppressor = NoiseSuppressor.create(audioRecord.getAudioSessionId());
        if (noiseSuppressor != null) {
          noiseSuppressor.setEnableStatusListener(this);
          noiseSuppressor.setEnabled(audioSystem.isDenoise());
          logger.info("Noise suppressor: " + noiseSuppressor.getEnabled());
        }
      }
    }

    /** Closes the connection to the media source of the associated <tt>DataSource</tt>. */
    public synchronized void disconnect() {
      if (audioRecord != null) {
        audioRecord.release();
        audioRecord = null;

        setThreadPriority = true;
      }
    }

    /**
     * Attempts to set the <tt>Format</tt> of this <tt>AbstractBufferStream</tt>.
     *
     * @param format the <tt>Format</tt> to be set as the format of this
     *     <tt>AbstractBufferStream</tt>
     * @return the <tt>Format</tt> of this <tt>AbstractBufferStream</tt> or <tt>null</tt> if the
     *     attempt to set the <tt>Format</tt> did not succeed and any last-known <tt>Format</tt> is
     *     to be left in effect
     * @see AbstractPullBufferStream#doSetFormat(Format)
     */
    @Override
    protected synchronized Format doSetFormat(Format format) {
      return (audioRecord == null) ? format : null;
    }

    /**
     * Reads media data from this <tt>PullBufferStream</tt> into a specific <tt>Buffer</tt> with
     * blocking.
     *
     * @param buffer the <tt>Buffer</tt> in which media data is to be read from this
     *     <tt>PullBufferStream</tt>
     * @throws IOException if anything goes wrong while reading media data from this
     *     <tt>PullBufferStream</tt> into the specified <tt>buffer</tt>
     * @see javax.media.protocol.PullBufferStream#read(javax.media.Buffer)
     */
    public void read(Buffer buffer) throws IOException {
      if (setThreadPriority) {
        setThreadPriority = false;
        setThreadPriority();
      }

      Object data = buffer.getData();
      int length = this.length;

      if (data instanceof byte[]) {
        if (((byte[]) data).length < length) data = null;
      } else data = null;
      if (data == null) {
        data = new byte[length];
        buffer.setData(data);
      }

      int toRead = length;
      byte[] bytes = (byte[]) data;
      int offset = 0;

      buffer.setLength(0);
      while (toRead > 0) {
        int read;

        synchronized (this) {
          if (audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING)
            read = audioRecord.read(bytes, offset, toRead);
          else break;
        }

        if (read < 0) {
          throw new IOException(
              AudioRecord.class.getName() + "#read(byte[], int, int) returned " + read);
        } else {
          buffer.setLength(buffer.getLength() + read);
          offset += read;
          toRead -= read;
        }
      }
      buffer.setOffset(0);

      // Apply software gain.
      if (gainControl != null) {
        BasicVolumeControl.applyGain(gainControl, bytes, buffer.getOffset(), buffer.getLength());
      }
    }

    /**
     * Starts the transfer of media data from this <tt>AbstractBufferStream</tt>.
     *
     * @throws IOException if anything goes wrong while starting the transfer of media data from
     *     this <tt>AbstractBufferStream</tt>
     * @see AbstractBufferStream#start()
     */
    @Override
    public void start() throws IOException {
      /*
       * Connect upon start because the connect has been delayed to allow
       * this AudioRecordStream to respect requests to set its format.
       */
      synchronized (this) {
        if (audioRecord == null) connect();
      }

      super.start();

      synchronized (this) {
        if (audioRecord != null) {
          setThreadPriority = true;
          audioRecord.startRecording();
        }
      }
    }

    /**
     * Stops the transfer of media data from this <tt>AbstractBufferStream</tt>.
     *
     * @throws IOException if anything goes wrong while stopping the transfer of media data from
     *     this <tt>AbstractBufferStream</tt>
     * @see AbstractBufferStream#stop()
     */
    @Override
    public void stop() throws IOException {
      synchronized (this) {
        if (audioRecord != null) {
          audioRecord.stop();
          setThreadPriority = true;
        }
      }

      super.stop();
    }

    @Override
    public void onEnableStatusChange(AudioEffect effect, boolean enabled) {
      logger.info(effect.getDescriptor() + " : " + enabled);
    }
  }
}