/** Gets the correct codec from the camera */
  public Codec fetchCodec(Format inFormat, Format outFormat) {
    Vector codecs = PlugInManager.getPlugInList(inFormat, outFormat, PlugInManager.CODEC);
    if (codecs == null) return null;

    for (int i = 0; i < codecs.size(); i++) {
      Codec codec = null;
      try {
        Class codecClass = Class.forName((String) codecs.elementAt(i));
        if (codecClass != null) codec = (Codec) codecClass.newInstance();
      } catch (Exception e) {
        continue;
      }
      if (codec == null || codec.setInputFormat(inFormat) == null) continue;

      Format[] outFormats = codec.getSupportedOutputFormats(inFormat);
      if (outFormats == null) continue;
      for (int j = 0; j < outFormats.length; j++) {
        if (outFormats[j].matches(outFormat)) {
          try {
            codec.setOutputFormat(outFormats[j]);
            codec.open();
            return codec;
          } catch (Exception e) {
          }
        }
      }
    }
    return null;
  }
 /** Free the data line. */
 public void close() {
   logger.info("closing...");
   controls.clear();
   if (codec != null) {
     codec.close();
     codec = null;
   }
   sourceLine.close();
   sourceLine = null;
 }
  /** Gets an image via the cam */
  boolean camFetchFrame() {
    try {
      if (converter == null) {
        pbs.read(buffer);
      } else {
        if (inputBuffer == null) {
          inputBuffer = new Buffer();
        }
        pbs.read(inputBuffer);
        converter.process(inputBuffer, buffer);
      }
      if (buffer.isDiscard()) {
        buffer.setDiscard(false);
        return false;
      }
      if (buffer.getData() == null) {
        return false;
      }
    } catch (Exception e) {
      return false;
    }

    return true;
  }
  /** Write the buffer to the SourceDataLine. */
  public int process(Buffer buffer) {

    // if we need to convert the format, do so using the codec.
    if (codec != null) {
      final int codecResult = codec.process(buffer, codecBuffer);
      if (codecResult == BUFFER_PROCESSED_FAILED) return BUFFER_PROCESSED_FAILED;
      if (codecResult == OUTPUT_BUFFER_NOT_FILLED) return BUFFER_PROCESSED_OK;
      buffer = codecBuffer;
    }

    int length = buffer.getLength();
    int offset = buffer.getOffset();

    final Format format = buffer.getFormat();

    final Class type = format.getDataType();
    if (type != Format.byteArray) {
      return BUFFER_PROCESSED_FAILED;
    }

    final byte[] data = (byte[]) buffer.getData();

    final boolean bufferNotConsumed;
    final int newBufferLength; // only applicable if bufferNotConsumed
    final int newBufferOffset; // only applicable if bufferNotConsumed

    if (NON_BLOCKING) {
      // TODO: handle sourceLine.available().  This code currently causes choppy audio.

      if (length > sourceLine.available()) {
        // we should only write sourceLine.available() bytes, then return INPUT_BUFFER_NOT_CONSUMED.
        length = sourceLine.available(); // don't try to write more than available
        bufferNotConsumed = true;
        newBufferLength = buffer.getLength() - length;
        newBufferOffset = buffer.getOffset() + length;

      } else {
        bufferNotConsumed = false;
        newBufferLength = length;
        newBufferOffset = offset;
      }
    } else {
      bufferNotConsumed = false;
      newBufferLength = 0;
      newBufferOffset = 0;
    }

    if (length == 0) {
      logger.finer("Buffer has zero length, flags = " + buffer.getFlags());
    }

    // make sure all the bytes are written.
    while (length > 0) {

      // logger.fine("Available: " + sourceLine.available());
      // logger.fine("length: " + length);
      // logger.fine("sourceLine.getBufferSize(): " + sourceLine.getBufferSize());

      final int n =
          sourceLine.write(
              data, offset, length); // TODO: this can block for a very long time if it doesn't
      if (n >= length) break;
      else if (n == 0) {
        // TODO: we could choose to handle a write failure this way,
        // assuming that it is considered legal to call stop while process is being called.
        // however, that seems like a bad idea in general.
        //				if (!sourceLine.isRunning())
        //				{
        //					buffer.setLength(offset);
        //					buffer.setOffset(length);
        //					return INPUT_BUFFER_NOT_CONSUMED;	// our write was interrupted.
        //				}

        logger.warning(
            "sourceLine.write returned 0, offset="
                + offset
                + "; length="
                + length
                + "; available="
                + sourceLine.available()
                + "; frame size in bytes"
                + sourceLine.getFormat().getFrameSize()
                + "; sourceLine.isActive() = "
                + sourceLine.isActive()
                + "; "
                + sourceLine.isOpen()
                + "; sourceLine.isRunning()="
                + sourceLine.isRunning());
        return BUFFER_PROCESSED_FAILED; // sourceLine.write docs indicate that this will only happen
                                        // if there is an error.

      } else {
        offset += n;
        length -= n;
      }
    }

    if (bufferNotConsumed) {
      // return INPUT_BUFFER_NOT_CONSUMED if not all bytes were written

      buffer.setLength(newBufferLength);
      buffer.setOffset(newBufferOffset);
      return INPUT_BUFFER_NOT_CONSUMED;
    }

    if (buffer.isEOM()) {
      // TODO: the proper way to do this is to implement Drainable, and let the processor call our
      // drain method.
      sourceLine
          .drain(); // we need to ensure that the media finishes playing, otherwise the EOM event
                    // will
      // be posted before the media finishes playing.
    }

    return BUFFER_PROCESSED_OK;
  }
  /**
   * Open the plugin. Must be called after the formats have been determined and before "process" is
   * called.
   *
   * <p>Open the DataLine.
   */
  public void open() throws ResourceUnavailableException {
    javax.sound.sampled.AudioFormat audioFormat = convertFormat(inputFormat);
    logger.info("opening with javax.sound format: " + audioFormat);
    try {

      if (!inputFormat.getEncoding().equals(AudioFormat.LINEAR)) {
        logger.info("JavaSoundRenderer: Audio format is not linear, creating conversion");

        if (inputFormat.getEncoding().equals(AudioFormat.ULAW))
          codec =
              new net.sf.fmj.media.codec.audio.ulaw
                  .Decoder(); // much more efficient than JavaSoundCodec
        else if (inputFormat.getEncoding().equals(AudioFormat.ALAW))
          codec =
              new net.sf.fmj.media.codec.audio.alaw
                  .Decoder(); // much more efficient than JavaSoundCodec
        else
          throw new ResourceUnavailableException(
              "Unsupported input format encoding: " + inputFormat.getEncoding());
        // codec = new net.sf.fmj.media.codec.JavaSoundCodec();
        codec.setInputFormat(inputFormat);
        final Format[] outputFormats = codec.getSupportedOutputFormats(inputFormat);
        if (outputFormats.length < 1)
          throw new ResourceUnavailableException(
              "Unable to get an output format for input format: " + inputFormat);
        final AudioFormat codecOutputFormat =
            (AudioFormat) outputFormats[0]; // TODO: choose the best quality one.
        codec.setOutputFormat(codecOutputFormat);
        audioFormat = convertFormat(codecOutputFormat);

        codec.open();

        logger.info(
            "JavaSoundRenderer: Audio format is not linear, created conversion from "
                + inputFormat
                + " to "
                + codecOutputFormat);
      }

      sourceLine = getSourceDataLine(audioFormat);
      sourceLine.open(audioFormat);

      {
        FloatControl gainFloatControl = null;
        BooleanControl muteBooleanControl = null;

        try {
          gainFloatControl = (FloatControl) sourceLine.getControl(FloatControl.Type.MASTER_GAIN);
        } catch (Exception e) {
          e.printStackTrace();
        }

        try {
          muteBooleanControl = (BooleanControl) sourceLine.getControl(BooleanControl.Type.MUTE);
        } catch (Exception e) {
          e.printStackTrace();
        }

        // TODO add other controls
        JavaSoundGainControl gainControl =
            new JavaSoundGainControl(gainFloatControl, muteBooleanControl);
        controls.addControl(gainControl);
      }

      logControls(sourceLine.getControls());
    } catch (LineUnavailableException e) {
      throw new ResourceUnavailableException(e.getMessage());
    }
  }