/* ----------------------------------------------------------------------------
 * This file was automatically generated by SWIG (http://www.swig.org).
 * Version 2.0.6
 *
 * Do not make changes to this file unless you know what you are doing--modify
 * the SWIG interface file instead.
 * ----------------------------------------------------------------------------- */

package io.humble.video;
import io.humble.ferry.*;
/**
 * A link between two Filters in a FilterGraph.
 */
public class FilterLink extends RefCounted {
  // JNIHelper.swg: Start generated code
  // >>>>>>>>>>>>>>>>>>>>>>>>>>>
  /**
   * This method is only here to use some references and remove
   * a Eclipse compiler warning.
   */
  @SuppressWarnings("unused")
  private void noop()
  {
    Buffer.make(null, 1);
  }

  private volatile long swigCPtr;

  /**
   * Internal Only.
   */
  protected FilterLink(long cPtr, boolean cMemoryOwn) {
    super(VideoJNI.FilterLink_SWIGUpcast(cPtr), cMemoryOwn);
    swigCPtr = cPtr;
  }

  /**
   * Internal Only.
   */
  protected FilterLink(long cPtr, boolean cMemoryOwn,
      java.util.concurrent.atomic.AtomicLong ref)
  {
    super(VideoJNI.FilterLink_SWIGUpcast(cPtr),
     cMemoryOwn, ref);
    swigCPtr = cPtr;
  }

  /**
   * Internal Only.  Not part of public API.
   *
   * Get the raw value of the native object that obj is proxying for.
   *
   * @param obj The java proxy object for a native object.
   * @return The raw pointer obj is proxying for.
   */
  protected static long getCPtr(FilterLink obj) {
    if (obj == null) return 0;
    return obj.getMyCPtr();
  }

  /**
   * Internal Only.  Not part of public API.
   *
   * Get the raw value of the native object that we're proxying for.
   *
   * @return The raw pointer we're proxying for.
   */
  protected long getMyCPtr() {
    if (swigCPtr == 0) throw new IllegalStateException("underlying native object already deleted");
    return swigCPtr;
  }

  /**
   * Create a new FilterLink object that is actually referring to the
   * exact same underlying native object.
   *
   * @return the new Java object.
   */
  @Override
  public FilterLink copyReference() {
    if (swigCPtr == 0)
      return null;
    else
      return new FilterLink(swigCPtr, swigCMemOwn, getJavaRefCount());
  }

  /**
   * Compares two values, returning true if the underlying objects in native code are the same object.
   *
   * That means you can have two different Java objects, but when you do a comparison, you'll find out
   * they are the EXACT same object.
   *
   * @return True if the underlying native object is the same.  False otherwise.
   */
  public boolean equals(Object obj) {
    boolean equal = false;
    if (obj instanceof FilterLink)
      equal = (((FilterLink)obj).swigCPtr == this.swigCPtr);
    return equal;
  }

  /**
   * Get a hashable value for this object.
   *
   * @return the hashable value.
   */
  public int hashCode() {
     return (int)swigCPtr;
  }

  // <<<<<<<<<<<<<<<<<<<<<<<<<<<
  // JNIHelper.swg: End generated code

/**
 * Get the FilterGraph this FilterLink belongs to.
 */
  public FilterGraph getFilterGraph() {
    long cPtr = VideoJNI.FilterLink_getFilterGraph(swigCPtr, this);
    return (cPtr == 0) ? null : new FilterGraph(cPtr, false);
  }

/**
 * @return the input filter for this link.
 */
  public Filter getInputFilter() {
    long cPtr = VideoJNI.FilterLink_getInputFilter(swigCPtr, this);
    return (cPtr == 0) ? null : new Filter(cPtr, false);
  }

/**
 * @return the input pad name for this link.
 */
  public String getInputPadName() {
    return VideoJNI.FilterLink_getInputPadName(swigCPtr, this);
  }

/**
 * @return the input pad type for this link. Should be the same as #getMediaType().
 */
  public MediaDescriptor.Type getInputPadType() {
    return MediaDescriptor.Type.swigToEnum(VideoJNI.FilterLink_getInputPadType(swigCPtr, this));
  }

/**
 * @return the output filter for this link.
 */
  public Filter getOutputFilter() {
    long cPtr = VideoJNI.FilterLink_getOutputFilter(swigCPtr, this);
    return (cPtr == 0) ? null : new Filter(cPtr, false);
  }

/**
 * @return the output pad name for this link.
 */
  public String getOutputPadName() {
    return VideoJNI.FilterLink_getOutputPadName(swigCPtr, this);
  }

/**
 * @return the output pad type for this link. Should be the same as #getMediaType().
 */
  public MediaDescriptor.Type getOutputPadType() {
    return MediaDescriptor.Type.swigToEnum(VideoJNI.FilterLink_getOutputPadType(swigCPtr, this));
  }

/**
 * @return the MediaDescriptor.Type for this link.
 */
  public MediaDescriptor.Type getMediaType() {
    return MediaDescriptor.Type.swigToEnum(VideoJNI.FilterLink_getMediaType(swigCPtr, this));
  }

/**
 * @return width of video agreed between the input filter and output filter if MediaDescriptor.Type = MediaDescriptor.Type.MEDIA_VIDEO.
 */
  public int getWidth() {
    return VideoJNI.FilterLink_getWidth(swigCPtr, this);
  }

/**
 * @return height of video agreed between the input filter and output filter if MediaDescriptor.Type = MediaDescriptor.Type.MEDIA_VIDEO.
 */
  public int getHeight() {
    return VideoJNI.FilterLink_getHeight(swigCPtr, this);
  }

/**
 * @return pixel format of video agreed between the input filter and output filter if MediaDescriptor.Type = MediaDescriptor.Type.MEDIA_VIDEO.
 */
  public PixelFormat.Type getPixelFormat() {
    return PixelFormat.Type.swigToEnum(VideoJNI.FilterLink_getPixelFormat(swigCPtr, this));
  }

/**
 * @return pixel aspect ratio of video agreed between the input filter and output filter if MediaDescriptor.Type = MediaDescriptor.Type.MEDIA_VIDEO.
 */
  public Rational getPixelAspectRatio() {
    long cPtr = VideoJNI.FilterLink_getPixelAspectRatio(swigCPtr, this);
    return (cPtr == 0) ? null : new Rational(cPtr, false);
  }

/**
 * @return channel layout agreed between the input filter and output filter if MediaDescriptor.Type = MediaDescriptor.Type.MEDIA_AUDIO.
 */
  public AudioChannel.Layout getChannelLayout() {
    return AudioChannel.Layout.swigToEnum(VideoJNI.FilterLink_getChannelLayout(swigCPtr, this));
  }

/**
 * @return sample rate agreed between the input filter and output filter if MediaDescriptor.Type = MediaDescriptor.Type.MEDIA_AUDIO.
 */
  public int getSampleRate() {
    return VideoJNI.FilterLink_getSampleRate(swigCPtr, this);
  }

/**
 * @return sample format agreed between the input filter and output filter if MediaDescriptor.Type = MediaDescriptor.Type.MEDIA_AUDIO.
 */
  public AudioFormat.Type getSampleFormat() {
    return AudioFormat.Type.swigToEnum(VideoJNI.FilterLink_getSampleFormat(swigCPtr, this));
  }

/**
 * Define the time base used by the PTS of the frames/samples

 * which will pass through this link.

 * During the configuration stage, each filter is supposed to

 * change only the output timebase, while the timebase of the

 * input link is assumed to be an unchangeable property.

 *

 * @return the timebase
 */
  public Rational getTimeBase() {
    long cPtr = VideoJNI.FilterLink_getTimeBase(swigCPtr, this);
    return (cPtr == 0) ? null : new Rational(cPtr, false);
  }

/**
 * Insert a filter into this link between the current input and output.

 * @param filter filter to insert

 * @param srcPadIndex the index of the pad on filter to connect the output of the start of the prior link to.

 * @param dstPadIndex the index of the pad on filter to connect the input of the end of the prior link to.
 */
  public void insertFilter(Filter filter, int srcPadIndex, int dstPadIndex) {
    VideoJNI.FilterLink_insertFilter(swigCPtr, this, Filter.getCPtr(filter), filter, srcPadIndex, dstPadIndex);
  }

}


/* ----------------------------------------------------------------------------
 * This file was automatically generated by SWIG (http://www.swig.org).
 * Version 2.0.6
 *
 * Do not make changes to this file unless you know what you are doing--modify
 * the SWIG interface file instead.
 * ----------------------------------------------------------------------------- */

package io.humble.video;
import io.humble.ferry.*;
/**
 * The parent class for all Raw media data.
 */
public class MediaRaw extends Media {
  // JNIHelper.swg: Start generated code
  // >>>>>>>>>>>>>>>>>>>>>>>>>>>
  /**
   * This method is only here to use some references and remove
   * a Eclipse compiler warning.
   */
  @SuppressWarnings("unused")
  private void noop()
  {
    Buffer.make(null, 1);
  }

  private volatile long swigCPtr;

  /**
   * Internal Only.
   */
  protected MediaRaw(long cPtr, boolean cMemoryOwn) {
    super(VideoJNI.MediaRaw_SWIGUpcast(cPtr), cMemoryOwn);
    swigCPtr = cPtr;
  }

  /**
   * Internal Only.
   */
  protected MediaRaw(long cPtr, boolean cMemoryOwn,
      java.util.concurrent.atomic.AtomicLong ref)
  {
    super(VideoJNI.MediaRaw_SWIGUpcast(cPtr),
     cMemoryOwn, ref);
    swigCPtr = cPtr;
  }

  /**
   * Internal Only.  Not part of public API.
   *
   * Get the raw value of the native object that obj is proxying for.
   *
   * @param obj The java proxy object for a native object.
   * @return The raw pointer obj is proxying for.
   */
  protected static long getCPtr(MediaRaw obj) {
    if (obj == null) return 0;
    return obj.getMyCPtr();
  }

  /**
   * Internal Only.  Not part of public API.
   *
   * Get the raw value of the native object that we're proxying for.
   *
   * @return The raw pointer we're proxying for.
   */
  protected long getMyCPtr() {
    if (swigCPtr == 0) throw new IllegalStateException("underlying native object already deleted");
    return swigCPtr;
  }

  /**
   * Create a new MediaRaw object that is actually referring to the
   * exact same underlying native object.
   *
   * @return the new Java object.
   */
  @Override
  public MediaRaw copyReference() {
    if (swigCPtr == 0)
      return null;
    else
      return new MediaRaw(swigCPtr, swigCMemOwn, getJavaRefCount());
  }

  /**
   * Compares two values, returning true if the underlying objects in native code are the same object.
   *
   * That means you can have two different Java objects, but when you do a comparison, you'll find out
   * they are the EXACT same object.
   *
   * @return True if the underlying native object is the same.  False otherwise.
   */
  public boolean equals(Object obj) {
    boolean equal = false;
    if (obj instanceof MediaRaw)
      equal = (((MediaRaw)obj).swigCPtr == this.swigCPtr);
    return equal;
  }

  /**
   * Get a hashable value for this object.
   *
   * @return the hashable value.
   */
  public int hashCode() {
     return (int)swigCPtr;
  }

  // <<<<<<<<<<<<<<<<<<<<<<<<<<<
  // JNIHelper.swg: End generated code

/**
 * Get the time stamp of this object in getTimeBase() units.

 *

 * @return the time stamp
 */
  public long getTimeStamp() {
    return VideoJNI.MediaRaw_getTimeStamp(swigCPtr, this);
  }

/**
 * Set the time stamp for this object in getTimeBase() units.

 *

 * @param aTimeStamp The time stamp
 */
  public void setTimeStamp(long aTimeStamp) {
    VideoJNI.MediaRaw_setTimeStamp(swigCPtr, this, aTimeStamp);
  }

/**
 * Get the time base that time stamps of this object are represented in.

 *

 * @return the time base.
 */
  public Rational getTimeBase() {
    long cPtr = VideoJNI.MediaRaw_getTimeBase(swigCPtr, this);
    return (cPtr == 0) ? null : new Rational(cPtr, false);
  }

/**
 * Is this object a key object?  i.e. it can be interpreted without needing any other media objects

 *

 * @return true if it's a key, false if not
 */
  public boolean isKey() {
    return VideoJNI.MediaRaw_isKey(swigCPtr, this);
  }

/**
 * Get the presentation time stamp
 */
  public long getPts() {
    return VideoJNI.MediaRaw_getPts(swigCPtr, this);
  }

/**
 * Get any meta-data associated with this media item
 */
  public KeyValueBag getMetaData() {
    long cPtr = VideoJNI.MediaRaw_getMetaData(swigCPtr, this);
    return (cPtr == 0) ? null : new KeyValueBag(cPtr, false);
  }

/**
 * pts copied from the Packet that was decoded to produce this frame

 * - encoding: unused

 * - decoding: Read by user.
 */
  public long getPacketPts() {
    return VideoJNI.MediaRaw_getPacketPts(swigCPtr, this);
  }

/**
 * dts copied from the Packet that triggered returning this frame

 * - encoding: unused

 * - decoding: Read by user.
 */
  public long getPacketDts() {
    return VideoJNI.MediaRaw_getPacketDts(swigCPtr, this);
  }

/**
 * size of the corresponding packet containing the compressed

 * frame.

 * It is set to a negative value if unknown.

 * - encoding: unused

 * - decoding: set by libavcodec, read by user.
 */
  public int getPacketSize() {
    return VideoJNI.MediaRaw_getPacketSize(swigCPtr, this);
  }

/**
 * duration of the corresponding packet, expressed in

 * ContainerStream.getTimeBase() units, 0 if unknown.

 * - encoding: unused

 * - decoding: Read by user.
 */
  public long getPacketDuration() {
    return VideoJNI.MediaRaw_getPacketDuration(swigCPtr, this);
  }

/**
 * frame timestamp estimated using various heuristics, in stream time base

 * - encoding: unused

 * - decoding: set by libavcodec, read by user.
 */
  public long getBestEffortTimeStamp() {
    return VideoJNI.MediaRaw_getBestEffortTimeStamp(swigCPtr, this);
  }

/**
 * @param value is the object complete or not.

 * @see #isComplete()
 */
  public void setComplete(boolean value) {
    VideoJNI.MediaRaw_setComplete(swigCPtr, this, value);
  }

/**
 * Sets the timebase on this object.

 *

 * Note: This will NOT automatically rescale the timestamp set -- so if you change

 * the timebase, you almost definitely want to change the timestamp as well.
 */
  public void setTimeBase(Rational timeBase) {
    VideoJNI.MediaRaw_setTimeBase(swigCPtr, this, Rational.getCPtr(timeBase), timeBase);
  }

}


/*******************************************************************************
 * Copyright (c) 2013, Art Clarke.  All rights reserved.
 *
 * This file is part of Humble-Video.
 *
 * Humble-Video is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Humble-Video is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Humble-Video.  If not, see <http://www.gnu.org/licenses/>.
 *******************************************************************************/

package io.humble.video.customio;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.humble.video.customio.FfmpegIO;
import io.humble.video.customio.FileProtocolHandlerFactory;
import io.humble.video.customio.IURLProtocolHandler;
import io.humble.video.customio.IURLProtocolHandlerFactory;
import io.humble.video.customio.NullProtocolHandlerFactory;
import io.humble.video.customio.URLProtocolManager;

/**
 * This class manages URL protocols that we have handlers for.  It is used
 * (and called from) the Native code that integrates with FFMPEG.
 *
 * To register a handler you would use the singleton manager as follows:
 *
 *

 *  URLProtocolManager mgr = URLProtocolManager.getManager();
 *  mgr.registerFactory("myprotocol", myProtocolHandlerFactory);
 *

 *
 */
public class URLProtocolManager
{
  private final Logger log = LoggerFactory.getLogger(this.getClass());
  private final ConcurrentMap<String, IURLProtocolHandlerFactory> mProtocols;

  public static final String DEFAULT_PROTOCOL = "humblefile";
  public static final String NULL_PROTOCOL = "humblenull";

  private static final URLProtocolManager sManager = new URLProtocolManager();

  /**
   * Factory method to get the global protocol manager.
   */
  public static URLProtocolManager getManager()
  {
    return sManager;
  }

  /**
   * Method to be called to initialize this library.
   */
  public static void init()
  {
    // just calling this method causes sManager to be initialized
    // in the static constructor, which is the only initialization
    // we currently need.
  }

  // Don't let people create one of these; instead they must
  // use the Singleton factory method.
  private URLProtocolManager()
  {
    mProtocols = new ConcurrentHashMap<String, IURLProtocolHandlerFactory>();
    // Always register the DEFAULT
    registerFactory(DEFAULT_PROTOCOL, new FileProtocolHandlerFactory());
    // And the NULL protocols
    registerFactory(NULL_PROTOCOL, new NullProtocolHandlerFactory());
  }

  /**
   * Register a new factory for IURLProtocolHandlers for a given protocol.
   *

   * FFMPEG is very picky; protocols must be only alpha characters (no numbers).
   *

   * @param protocol The protocol, without colon, this factory is to be used for.
   * @param factory The factory for the manager to use whenever a handler is requested for a registered protocol;
   *   null means disable handling for this factory.
   * @return The factory that was previously used for that protocol (or null if none).
   */
  public IURLProtocolHandlerFactory registerFactory(String protocol,
      IURLProtocolHandlerFactory factory)
  {
    if (protocol == null)
      throw new IllegalArgumentException("protocol required");

    IURLProtocolHandlerFactory oldFactory;
    if (factory == null)
      oldFactory = mProtocols.remove(protocol);
    else
      oldFactory = mProtocols.put(protocol, factory);

    log.trace("Registering factory for URLProtocol: {}", protocol);

    if (oldFactory == null)
    {
      // we previously didn't have something registered for this factory
      // so tell FFMPEG we're now the protocol manager for this protocol.
      log.trace("Letting FFMPEG know about an additional protocol: {}",
          protocol);
      FfmpegIO.registerProtocolHandler(protocol, this);
    }
    return oldFactory;
  }

  /*
   * Get a IURLProtocolHandler for this url.
   *

   * IMPORTANT: This function is called from native code, and so the name and signature cannot
   * change without changing the native code.
   *

   * This function is eventually invoked whenever someone tries to call url_open("yourprotocol:...", flags)
   * from FFMPEG native code.  It returns a protocol handler which will then have open(...) called on it.
   *

   * @param url The URL we want to handle.
   * @param flags   Any flags that the url_open() function will want to pass.
   *
   * @returns A protocol handler to use.
   */
  public IURLProtocolHandler getHandler(String url, int flags)
  {
    IURLProtocolHandler result = null;

    log.trace("looking for protocol handler for: {}", url);
    if (url == null || url.length() == 0)
      throw new IllegalArgumentException("expected valid URL");
    int colonIndex = url.indexOf(":");
    String protocol = null;
    if (colonIndex > 0)
    {
      protocol = url.substring(0, colonIndex);
    }
    else
    {
      protocol = DEFAULT_PROTOCOL;
    }

    IURLProtocolHandlerFactory factory = mProtocols.get(protocol);
    if (factory != null)
    {
      result = factory.getHandler(protocol, url, flags);
    }
    else
    {
      log.error("asked to get handler for unsupported protocol: {}",
          protocol);
    }

    return result;
  }

  /**
   * Get the resource portion of a url.  For example for the URL
   *

   * http://www.humble.io/video
   *

   * The protocol string is http and the resource
   * string is www.humble.io/video
   *
   * @param url The url to parse
   * @return The resource
   */

  public static String getResourceFromURL(String url)
  {
    String retval = url;
    if (url != null && url.length() > 0)
    {
      int colonIndex;
      colonIndex = url.indexOf("://");
      if (colonIndex > 0)
        retval = url.substring(colonIndex+3);
      else {
        colonIndex = url.indexOf(":");
        if (colonIndex > 1) // handle windows drive letters.
        {
          // remove the URL prefix
          retval = url.substring(colonIndex + 1);
        }
      }
    }
    return retval;
  }

  /**
   * Get the protocol portion of a url.  For example for the URL
   *

   * http://www.humble.io/video
   *

   * The protocol string is http and the resource
   * string is //www.humble.io/video
   *
   * @param url The url to parse
   * @return The protocol
   */

  public static String getProtocolFromURL(String url)
  {
    String retval = null;
    if (url != null && url.length() > 0)
    {
      int colonIndex = url.indexOf(":");
      if (colonIndex > 0)
      {
        // remove the URL suffix
        retval = url.substring(0, colonIndex);
      }
    }
    return retval;
  }

}


/*******************************************************************************
 * Copyright (c) 2013, Art Clarke.  All rights reserved.
 *
 * This file is part of Humble-Video.
 *
 * Humble-Video is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Humble-Video is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Humble-Video.  If not, see <http://www.gnu.org/licenses/>.
 *******************************************************************************/
package io.humble.ferry;

/**
 * A set of utilities that can be used to find out
 * information about the Native environment that
 * we are running within.
 * @author aclarke
 *
 */
public class JNIEnv
{
  /**
   * The CPU architecture (i.e. chip architecture).
   * @author aclarke
   *
   */
  enum CPUArch{
    /** 32-bit systems such as the i386, i486, i586 or i686 */
    X86,
    /** 64-bit systems based on the x86 family */
    X86_64,
    /** 32-bit PowerPC based systems */
    PPC,
    /** 64-bit PowerPC based systems */
    PPC64,
    /** A chip architecture unknown to {@link Ferry}. */
    UNKNOWN,
  }
  /** The Operating system family */
  enum OSFamily {
    /** Linux family of operating systems */
    LINUX,
    /** Macintosh (darwin) family of operating systems */
    MAC,
    /** Microsoft Windows family of operating systems */
    WINDOWS,
    /** An unknown OS that {@link Ferry} knows nothing about */
    UNKNOWN,
  }
  /**
   * Our singleton.
   */
  private static final JNIEnv mEnv = new JNIEnv();

  private final CPUArch mCPUArch;
  private final OSFamily mOSFamily;

  /** Private. Only one {@link JNIEnv} is allowed per Java Virtual machine. */
  private JNIEnv() {
    mCPUArch = getCPUArch(System.getProperty("os.arch"));
    mOSFamily = getOSFamily(System.getProperty("os.name"));
  }

  /**
   * Get the (static) {@link JNIEnv} for this JVM.
   * @return the environment
   */
  public static JNIEnv getEnv() { return mEnv; }

  /**
   * Get the CPU architecture based on the passed in javaCPUArch specifier.
   *
   * The string should be of a format returned from {@link System#getProperty(String)}
   * for the property "os.arch".
   *
   * @param javaCPU the string to parse
   * @return the CPU architecture
   * @see System#getProperty(String)
   */
  public static CPUArch getCPUArch(String javaCPU)
  {
    final CPUArch javaArch;
    final String javaCPUArch = javaCPU != null ? javaCPU.toLowerCase() : "";

    // first parse the java arch
    if (javaCPUArch.startsWith("x86_64") ||
        javaCPUArch.startsWith("amd64") ||
        javaCPUArch.startsWith("ia64")) {
      javaArch = CPUArch.X86_64;
    } else if (
        javaCPUArch.startsWith("ppc64") ||
        javaCPUArch.startsWith("powerpc64")
        ) {
      javaArch = CPUArch.PPC64;
    } else if (
        javaCPUArch.startsWith("ppc") ||
        javaCPUArch.startsWith("powerpc")
        ) {
      javaArch = CPUArch.PPC;
    } else if (
        javaCPUArch.contains("86")
        )
    {
      javaArch = CPUArch.X86;
    } else {
      javaArch = CPUArch.UNKNOWN;
    }
    return javaArch;
  }

  /**
   * Return a CPUArch from parsing a GNU autoconf triple.
   *
   * For example "x86_64-w64-mingw32" will return {@link JNIEnv.CPUArch#X86_64}
   * and "ppc-apple-darwin" will return {@link JNIEnv.CPUArch#PPC}.
   *
   * @param gnuString the GNU string
   * @return the architecture
   */
  public static CPUArch getCPUArchFromGNUString(String gnuString)
  {
    final String nativeCpu = gnuString.toLowerCase();
    final CPUArch nativeArch;
    // then the native arch
    if (nativeCpu.startsWith("x86_64") ||
        nativeCpu.startsWith("amd64") ||
        nativeCpu.startsWith("ia64"))
      nativeArch = CPUArch.X86_64;
    else if (
        nativeCpu.startsWith("ppc64") ||
        nativeCpu.startsWith("powerpc64")
        )
      nativeArch = CPUArch.PPC64;
    else if (
        nativeCpu.startsWith("ppc") ||
        nativeCpu.startsWith("powerpc")
        )
      nativeArch = CPUArch.PPC;
    else if (
        nativeCpu.contains("86")
        )
      nativeArch = CPUArch.X86;
    else
      nativeArch = CPUArch.UNKNOWN;

    return nativeArch;
  }

  /**
   * Get the OSFamily based on the passed in osName specifier.
   *
   * The string should be of a format returned from {@link System#getProperty(String)}
   * for the property "os.name".
   *
   * @param osName the string to parse
   * @return the OSFamily
   * @see System#getProperty(String)
   */
  public static OSFamily getOSFamily(String osName)
  {
    final OSFamily retval;

    if (osName != null && osName.length() > 0)
    {
      if (osName.startsWith("Windows"))
        retval = OSFamily.WINDOWS;
      else if (osName.startsWith("Mac"))
        retval = OSFamily.MAC;
      else if (osName.startsWith("Linux"))
        retval = OSFamily.LINUX;
      else
        retval = OSFamily.UNKNOWN;
    } else
      retval = OSFamily.UNKNOWN;
    return retval;
  }

  /**
   * Return an OS Family from parsing a GNU autoconf triple.
   *
   * For example "x86_64-w64-mingw32" will return {@link JNIEnv.OSFamily#WINDOWS}
   * and "ppc-apple-darwin" will return {@link JNIEnv.OSFamily#MAC}.
   *
   * @param gnuString the GNU string
   * @return the OSFamily
   */
  public static OSFamily getOSFamilyFromGNUString(String gnuString)
  {
    final String nativeOs = (gnuString != null ? gnuString.toLowerCase() : "");
    final OSFamily retval;
    if (nativeOs.startsWith("mingw") || nativeOs.startsWith("cygwin"))
      retval = OSFamily.WINDOWS;
    else if (nativeOs.startsWith("darwin"))
      retval = OSFamily.MAC;
    else if (nativeOs.startsWith("linux"))
      retval = OSFamily.LINUX;
    else
      retval = OSFamily.UNKNOWN;
    return retval;
  }

  /**
   * @return the {@link JNIEnv.CPUArch} of this Java Virtual Machine.
   */
  public CPUArch getCPUArch()
  {
    return mCPUArch;
  }

  /**
   * @return the {@link JNIEnv.OSFamily} of this Java Virtual Machine.
   */
  public OSFamily getOSFamily()
  {
    return mOSFamily;
  }
}


/* ----------------------------------------------------------------------------
 * This file was automatically generated by SWIG (http://www.swig.org).
 * Version 2.0.6
 *
 * Do not make changes to this file unless you know what you are doing--modify
 * the SWIG interface file instead.
 * ----------------------------------------------------------------------------- */

package io.humble.video;
import io.humble.ferry.*;
/**
 * The parent class of all media objects than can be gotten from or added to a Stream.
 */
public class Media extends RefCounted {
  // JNIHelper.swg: Start generated code
  // >>>>>>>>>>>>>>>>>>>>>>>>>>>
  /**
   * This method is only here to use some references and remove
   * a Eclipse compiler warning.
   */
  @SuppressWarnings("unused")
  private void noop()
  {
    Buffer.make(null, 1);
  }

  private volatile long swigCPtr;

  /**
   * Internal Only.
   */
  protected Media(long cPtr, boolean cMemoryOwn) {
    super(VideoJNI.Media_SWIGUpcast(cPtr), cMemoryOwn);
    swigCPtr = cPtr;
  }

  /**
   * Internal Only.
   */
  protected Media(long cPtr, boolean cMemoryOwn,
      java.util.concurrent.atomic.AtomicLong ref)
  {
    super(VideoJNI.Media_SWIGUpcast(cPtr),
     cMemoryOwn, ref);
    swigCPtr = cPtr;
  }

  /**
   * Internal Only.  Not part of public API.
   *
   * Get the raw value of the native object that obj is proxying for.
   *
   * @param obj The java proxy object for a native object.
   * @return The raw pointer obj is proxying for.
   */
  protected static long getCPtr(Media obj) {
    if (obj == null) return 0;
    return obj.getMyCPtr();
  }

  /**
   * Internal Only.  Not part of public API.
   *
   * Get the raw value of the native object that we're proxying for.
   *
   * @return The raw pointer we're proxying for.
   */
  protected long getMyCPtr() {
    if (swigCPtr == 0) throw new IllegalStateException("underlying native object already deleted");
    return swigCPtr;
  }

  /**
   * Create a new Media object that is actually referring to the
   * exact same underlying native object.
   *
   * @return the new Java object.
   */
  @Override
  public Media copyReference() {
    if (swigCPtr == 0)
      return null;
    else
      return new Media(swigCPtr, swigCMemOwn, getJavaRefCount());
  }

  /**
   * Compares two values, returning true if the underlying objects in native code are the same object.
   *
   * That means you can have two different Java objects, but when you do a comparison, you'll find out
   * they are the EXACT same object.
   *
   * @return True if the underlying native object is the same.  False otherwise.
   */
  public boolean equals(Object obj) {
    boolean equal = false;
    if (obj instanceof Media)
      equal = (((Media)obj).swigCPtr == this.swigCPtr);
    return equal;
  }

  /**
   * Get a hashable value for this object.
   *
   * @return the hashable value.
   */
  public int hashCode() {
     return (int)swigCPtr;
  }

  // <<<<<<<<<<<<<<<<<<<<<<<<<<<
  // JNIHelper.swg: End generated code

  // used to correct timezone offsets for timestamp format

  private static final long TIME_OFFSET = -java.util.Calendar.getInstance()
    .getTimeZone().getRawOffset();

  /** The default time stamp format. */

  public static final String DEFAULT_TIME_STAMP_FORMAT =
    "%1$tH:%1$tM:%1$tS.%1$tL";

  /**
   * Get a string representation of the time stamp for this {@link
   * Media}.  The time is formatted as: HH:MM:SS.ms
   *
   * @return the printable string form of the time stamp of this media
   *
   * @see #getFormattedTimeStamp(String)
   * @see #DEFAULT_TIME_STAMP_FORMAT
   */

  public String getFormattedTimeStamp()
  {
    return getFormattedTimeStamp(DEFAULT_TIME_STAMP_FORMAT);
  }

  /**
   * Get a string representation of the time stamp for this {@link
   * Media}.  The format of the resulting string is specified by
   * the format parameter.  See {@link java.util.Formatter} for
   * details on how to specify formats, however a good place to start
   * is with the following format: %1$tH:%1$tM:%1$tS.%1$tL
   *
   * @param format the format for the time stamp string
   *
   * @return the printable string form of the timestamp
   *
   * @see #getFormattedTimeStamp()
   * @see #DEFAULT_TIME_STAMP_FORMAT
   * @see java.util.Formatter
   */

  public String getFormattedTimeStamp(String format)
  {
    String retval = null;
    java.util.Formatter formatter = new java.util.Formatter();

    try {
      Rational timeBase = getTimeBase();
      if (timeBase == null)
        timeBase = Rational.make(1,(int)Global.DEFAULT_PTS_PER_SECOND);
      retval = formatter.format(format,
          (long)(getTimeStamp() * timeBase.getDouble() * 1000) +
          TIME_OFFSET).toString();
      timeBase.delete();
    } finally {
      formatter.close();
    }
    return retval;
  }

/**
 * Get the time stamp of this object in getTimeBase() units.

 *

 * @return the time stamp
 */
  public long getTimeStamp() {
    return VideoJNI.Media_getTimeStamp(swigCPtr, this);
  }

/**
 * Set the time stamp for this object in getTimeBase() units.

 *

 * @param aTimeStamp The time stamp
 */
  public void setTimeStamp(long aTimeStamp) {
    VideoJNI.Media_setTimeStamp(swigCPtr, this, aTimeStamp);
  }

/**
 * Get the time base that time stamps of this object are represented in.

 *

 * Caller must release the returned value.

 *

 * @return the time base.
 */
  public Rational getTimeBase() {
    long cPtr = VideoJNI.Media_getTimeBase(swigCPtr, this);
    return (cPtr == 0) ? null : new Rational(cPtr, false);
  }

/**
 * Is this object a key object?  i.e. it can be interpreted without needing any other media objects

 *

 * @return true if it's a key, false if not
 */
  public boolean isKey() {
    return VideoJNI.Media_isKey(swigCPtr, this);
  }

/**
 * Returns whether or not we think this buffer has been filled

 * with data.

 *

 *

 * @return Has setComplete() been called and the buffer populated.
 */
  public boolean isComplete() {
    return VideoJNI.Media_isComplete(swigCPtr, this);
  }

}


/*******************************************************************************
 * Copyright (c) 2014, Art Clarke.  All rights reserved.
 *
 * This file is part of Humble-Video.
 *
 * Humble-Video is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Humble-Video is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with Humble-Video.  If not, see <http://www.gnu.org/licenses/>.
 *******************************************************************************/
package io.humble.video.demos;

import java.io.IOException;
import java.nio.ByteBuffer;

import io.humble.video.Decoder;
import io.humble.video.Demuxer;
import io.humble.video.DemuxerStream;
import io.humble.video.MediaAudio;
import io.humble.video.MediaDescriptor;
import io.humble.video.MediaPacket;
import io.humble.video.Muxer;
import io.humble.video.javaxsound.AudioFrame;
import io.humble.video.javaxsound.MediaAudioConverter;
import io.humble.video.javaxsound.MediaAudioConverterFactory;
import javax.sound.sampled.LineUnavailableException;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;

/**
 * Opens a media file, finds the first audio stream, and then plays it.
 * This is meant as a demonstration program to teach the use of the Humble API.
 *

 * Concepts introduced:
 *

 *

 *
MediaPacket: An {@link MediaPacket} object can read from Media {@link Demuxer} objects and written {@link Muxer} objects, and represents encoded/compressed media-data.

 *
MediaAudio: {@link MediaAudio} objects represent uncompressed audio in Humble.

 *
Decoder: {@link Decoder} objects can be used to convert {@link MediaPacket} objects into uncompressed {@link MediaAudio} objects.
 *
Decoding loops: This introduces the concept of reading {@link MediaPacket} objects from a {@link Demuxer} and then decoding them into raw data.
 *

 *
 *

 * To run from maven, do:
 *

 *

 * mvn install exec:java -Dexec.mainClass="io.humble.video.demos.DecodeAndPlayAudio" -Dexec.args="filename.mp4"
 *

 *
 * @author aclarke
 *
 */
public class DecodeAndPlayAudio {

  /**
   * Opens a file, and plays the audio from it on the speakers.
   * @param filename The file or URL to play.
   * @throws LineUnavailableException
   */
  private static void playSound(String filename) throws InterruptedException, IOException, LineUnavailableException {
    /*
     * Start by creating a container object, in this case a demuxer since
     * we are reading, to get audio data from.
     */
    Demuxer demuxer = Demuxer.make();

    /*
     * Open the demuxer with the filename passed on.
     */
    demuxer.open(filename, null, false, true, null, null);

    /*
     * Query how many streams the call to open found
     */
    int numStreams = demuxer.getNumStreams();

    /*
     * Iterate through the streams to find the first audio stream
     */
    int audioStreamId = -1;
    Decoder audioDecoder = null;
    for(int i = 0; i < numStreams; i++)
    {
      final DemuxerStream stream = demuxer.getStream(i);
      final Decoder decoder = stream.getDecoder();
      if (decoder != null && decoder.getCodecType() == MediaDescriptor.Type.MEDIA_AUDIO) {
        audioStreamId = i;
        audioDecoder = decoder;
        // stop at the first one.
        break;
      }
    }
    if (audioStreamId == -1)
      throw new RuntimeException("could not find audio stream in container: "+filename);

    /*
     * Now we have found the audio stream in this file.  Let's open up our decoder so it can
     * do work.
     */
    audioDecoder.open(null, null);

    /*
     * We allocate a set of samples with the same number of channels as the
     * coder tells us is in this buffer.
     */
    final MediaAudio samples = MediaAudio.make(
        audioDecoder.getFrameSize(),
        audioDecoder.getSampleRate(),
        audioDecoder.getChannels(),
        audioDecoder.getChannelLayout(),
        audioDecoder.getSampleFormat());

    /*
     * A converter object we'll use to convert Humble Audio to a format that
     * Java Audio can actually play. The details are complicated, but essentially
     * this converts any audio format (represented in the samples object) into
     * a default audio format suitable for Java's speaker system (which will
     * be signed 16-bit audio, stereo (2-channels), resampled to 22,050 samples
     * per second).
     */

    final MediaAudioConverter converter =
        MediaAudioConverterFactory.createConverter(
            MediaAudioConverterFactory.DEFAULT_JAVA_AUDIO,
            samples);

    /*
     * An AudioFrame is a wrapper for the Java Sound system that abstracts away
     * some stuff. Go read the source code if you want -- it's not very complicated.
     */
    final AudioFrame audioFrame = AudioFrame.make(converter.getJavaFormat());
    if (audioFrame == null)
      throw new LineUnavailableException();

    /* We will use this to cache the raw-audio we pass to and from
     * the java sound system.
     */
    ByteBuffer rawAudio = null;

    /*
     * Now, we start walking through the container looking at each packet. This
     * is a decoding loop, and as you work with Humble you'll write a lot
     * of these.
     *
     * Notice how in this loop we reuse all of our objects to avoid
     * reallocating them. Each call to Humble resets objects to avoid
     * unnecessary reallocation.
     */
    final MediaPacket packet = MediaPacket.make();
    while(demuxer.read(packet) >= 0) {
      /*
       * Now we have a packet, let's see if it belongs to our audio stream
       */
      if (packet.getStreamIndex() == audioStreamId)
      {
        /*
         * A packet can actually contain multiple sets of samples (or frames of samples
         * in audio-decoding speak).  So, we may need to call decode audio multiple
         * times at different offsets in the packet's data.  We capture that here.
         */
        int offset = 0;
        int bytesRead = 0;
        do {
          bytesRead += audioDecoder.decode(samples, packet, offset);
          if (samples.isComplete()) {
            rawAudio = converter.toJavaAudio(rawAudio, samples);
            audioFrame.play(rawAudio);
          }
          offset += bytesRead;
        } while (offset < packet.getSize());
      }
    }

    // Some audio decoders (especially advanced ones) will cache
    // audio data before they begin decoding, so when you are done you need
    // to flush them. The convention to flush Encoders or Decoders in Humble Video
    // is to keep passing in null until incomplete samples or packets are returned.
    do {
      audioDecoder.decode(samples, null, 0);
      if (samples.isComplete()) {
        rawAudio = converter.toJavaAudio(rawAudio, samples);
        audioFrame.play(rawAudio);
      }
    } while (samples.isComplete());

    // It is good practice to close demuxers when you're done to free
    // up file handles. Humble will EVENTUALLY detect if nothing else
    // references this demuxer and close it then, but get in the habit
    // of cleaning up after yourself, and your future girlfriend/boyfriend
    // will appreciate it.
    demuxer.close();

    // similar with the demuxer, for the audio playback stuff, clean up after yourself.
    audioFrame.dispose();
  }

  /**
   * Takes a media container (file) as the first argument, opens it,
   * opens up the default audio device on your system, and plays back the audio.
   *
   * @param args Must contain one string which represents a filename
   * @throws IOException
   * @throws InterruptedException
   * @throws LineUnavailableException
   */
  public static void main(String[] args) throws InterruptedException, IOException, LineUnavailableException
  {
    final Options options = new Options();
    options.addOption("h", "help", false, "displays help");
    options.addOption("v", "version", false, "version of this library");

    final CommandLineParser parser = new org.apache.commons.cli.BasicParser();
    try {
      final CommandLine cmd = parser.parse(options, args);
      if (cmd.hasOption("version")) {
        // let's find what version of the library we're running
        final String version = io.humble.video_native.Version.getVersionInfo();
        System.out.println("Humble Version: " + version);
      } else if (cmd.hasOption("help") || args.length == 0) {
        final HelpFormatter formatter = new HelpFormatter();
        formatter.printHelp(DecodeAndPlayAudio.class.getCanonicalName() + " <filename>", options);
      } else {
        final String[] parsedArgs = cmd.getArgs();
        for(String arg: parsedArgs)
          playSound(arg);
      }
    } catch (ParseException e) {
      System.err.println("Exception parsing command line: " + e.getLocalizedMessage());
    }
  }

}


/* ----------------------------------------------------------------------------
 * This file was automatically generated by SWIG (http://www.swig.org).
 * Version 2.0.6
 *
 * Do not make changes to this file unless you know what you are doing--modify
 * the SWIG interface file instead.
 * ----------------------------------------------------------------------------- */

package io.humble.ferry;

/**
 * Parent of all Ferry objects -- it mains reference counts

 * in native code.

 *

 * RefCounted objects cannot be made with new.  They must be

 * constructed with special factory methods, usually called

 * make(...).

 *

 */
public class RefCounted {
  // JNIHelper.swg: Start generated code
  // >>>>>>>>>>>>>>>>>>>>>>>>>>>
  private volatile long swigCPtr;
  /**
   * Internal Only.
   */
  protected boolean swigCMemOwn;
  private io.humble.ferry.JNIReference mRefCounter;
  private Long mLifecycleReference;

  // the next object looks like it is unused by the compiler, but that's not true; it most
  // definitely is used :)
  @SuppressWarnings("unused")
  private io.humble.ferry.JNINativeFinalizer mObjectToForceFinalize;
  private java.util.concurrent.atomic.AtomicLong mJavaRefCount;

  /**
   * Internal Only.  Not part of public API.
   */
  protected RefCounted(long cPtr, boolean cMemoryOwn) {
    this(cPtr, cMemoryOwn, new java.util.concurrent.atomic.AtomicLong(0L));
  }

  /**
   * Internal Only.  Not part of public API.
   */
  protected RefCounted(long cPtr, boolean cMemoryOwn,
      java.util.concurrent.atomic.AtomicLong ref)
  {
    swigCMemOwn = cMemoryOwn;
    swigCPtr = cPtr;
    // Create the reference count that java-only objects will use
    // to avoid calling acquire and release on copyReference
    ref.incrementAndGet();
    mJavaRefCount = ref;
    if (swigCPtr != 0)
    {
      // This object must:
      // (a) have no references to any other java object
      // (b) Have a finalizer that just calls gc() on our JNIMemoryManager
      // It exists only to force gc() to be called even if someone calls the Humble
      // libraries once and never again
      mObjectToForceFinalize = new io.humble.ferry.JNINativeFinalizer();
      // This is what the weak reference will watch; this object should
      // (a) have no references to any other Java object
      // (b) MUST NOT have a finalizer
      // (c) MUST NOT acquire any other references to it except for the strong reference
      //     right here, and the weak reference in mRefCounter
      mLifecycleReference = new Long(swigCPtr);
      // Force the creation of a new weak reference, will will actually pin itself
      // inside the reference class.
      mRefCounter = JNIReference.createReference(
          this,
          mLifecycleReference, swigCPtr,
          mJavaRefCount);
    }
  }

  /**
   * Internal Only.  Not part of public API.
   *
   * Get the raw value of the native object that obj is proxying for.
   *
   * @param obj The java proxy object for a native object.
   * @return The raw pointer obj is proxying for.
   */
  protected static long getCPtr(RefCounted obj) {
    if (obj == null) return 0;
    return obj.getMyCPtr();
  }

  /**
   * Internal Only.  Not part of public API.
   *
   * Get the raw value of the native object that we're proxying for.
   *
   * @return The raw pointer we're proxying for.
   */
  protected long getMyCPtr() {
    if (swigCPtr == 0) throw new NullPointerException("underlying native object already deleted");
    return swigCPtr;
  }

  /**
   * Releases any underlying native memory and marks this object
   * as invalid.
   *

   * Normally Ferry manages when to release native memory.
   *

   *

   * In the unlikely event you want to control EXACTLY when a native
   * object is released, each Humble object has a {@link #delete()}
   * method that you can use. Once you call {@link #delete()},
   * you must ENSURE your object is never referenced again from
   * that Java object -- Ferry tries to help you avoid crashes if you
   * accidentally use an object after deletion but on this but we
   * cannot offer 100% protection (specifically if another thread
   *  is accessing that object EXACTLY when you {@link #delete()} it).
   *

   */

   public void delete()
   {
     if(swigCPtr != 0) {
       // assigning to an object removes an incorrect java
       // compiler warning for some
       // generated files
       Object object = this;
       if (object instanceof RefCounted && mRefCounter != null) {
         mRefCounter.delete();
       } else if (swigCMemOwn) {
         swigCMemOwn = false;
       }

     }
     mJavaRefCount = null;
     mRefCounter = null;
     mObjectToForceFinalize = null;
     mLifecycleReference = null;
     swigCPtr = 0;
   }

  /**
   * Create a new RefCounted object that is actually referring to the
   * exact same underlying native object.
   *
   * @return the new Java object.
   */
  public RefCounted copyReference() {
    if (swigCPtr == 0)
      return null;
    else
      return new RefCounted(swigCPtr, swigCMemOwn, getJavaRefCount());
  }

  /**
   * Internal Only.  Do not use.
   */
  protected java.util.concurrent.atomic.AtomicLong getJavaRefCount()
  {
    return mJavaRefCount;
  }

    /**
     * Return the current reference count on this object.
     *

     * The number returned represents the value at the instant
     * the message was called, and the value can change even
     * before this method returns.  Callers are advised not to
     * depend on the value of this except to check that
     * the value == 1.
     *

     *

     * If the value returned is one, and you know you have a valid
     * reference to that object, then congratulations; you are the
     * only person referencing that object.
     *

     * @return The current ref count.
     */
  public long getCurrentRefCount()
  {
    return mJavaRefCount.get()+getCurrentNativeRefCount()-1;
  }
     // <<<<<<<<<<<<<<<<<<<<<<<<<<<
   // JNIHelper.swg: End generated code

/**
 * Internal Only.  DO NOT USE FROM JAVA.

 *

 * Acquire a reference to this object.

 * This increments the native internal ref count in native code by +1.

 *

 *

 * This method is called internally by Ferry in Java, and you should

 * not call it without knowing what you are doing.  But if you do

 * call it, make sure you call #release() once for each call

 * you make to this method.

 *

 * @return The refcount after the acquire.  Note due to multi-threaded issues, you should not rely on this value,

 * as it may change before the method returns to you.
 */
  protected int acquire() {
    return FerryJNI.RefCounted_acquire(swigCPtr, this);
  }

/**
 * Internal Only.  DO NOT USE FROM JAVA.

 *

 * This decrements the native internal ref count by -1; the object is destroyed if its ref count reaches zero.

 *

 *

 * This method is called internally by Ferry in Java, and you should

 * not call it without knowing what you are doing.  But if you do

 * call it, make sure you had previously called #acquire() once for each call

 * to #release() you make.

 *

 * @return The ref count after the release.  Note due to multi-threaded issues, you should not rely on this value,

 * as it may change before the method returns to you.
 */
  protected int release() {
    return FerryJNI.RefCounted_release(swigCPtr, this);
  }

/**
 * Return the current reference count on this object.

 *

 * The number returned represents the value at the instant

 * the message was called, and the value can change even

 * before this method returns.  Callers are advised not to

 * depend on the value of this except to check that

 * the value == 1.

 *

 * If the value returned is one, and you know you have a valid

 * reference to that object, then congratulations; you are the

 * only person referencing that object.

 *

 * @return The current ref count.
 */
  private int getCurrentNativeRefCount() {
    return FerryJNI.RefCounted_getCurrentNativeRefCount(swigCPtr, this);
  }

}


/*******************************************************************************
 * Copyright (c) 2014, Art Clarke.  All rights reserved.
 *
 * This file is part of Humble-Video.
 *
 * Humble-Video is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Humble-Video is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with Humble-Video.  If not, see <http://www.gnu.org/licenses/>.
 *******************************************************************************/

package io.humble.ferry;

import java.io.File;
import java.util.LinkedList;
import java.util.List;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Internal Only.  Finds and loads native libraries that we depend on.
 *

 * It supplements the Java builtin {@link System#loadLibrary(String)} by looking
 * in more places.
 *

 *

 * Many methods in this class are marked as "package level". In reality, they
 * are private, but are at this protection level so our test suite can test
 * internals.
 *

 *
 */
public final class JNILibraryLoader
{
  private static final Logger log = LoggerFactory
      .getLogger(JNILibraryLoader.class);

  /**
   * An enumeration of the types of OS's we will do special handling for.
   */
  enum OSName
  {
    Unknown, Windows, MacOSX, Linux
  };

  /**
   * @deprecated Use {@link JNILibrary} instead.
   *
   * Attempts to find and load the given library, with the given version of the
   * library if version is asked for.
   *

   * First, if we detect that we've already loaded the given library, we'll just
   * return rather than attempt a second load (which will fail on some OSes).
   *

   *

   * If we haven't already loaded this library, we will search in the path
   * defined by the property java.library.path for the library, creating
   * OS-dependent names, and using version strings. If we can't find it in that
   * property, we'll search through the OS-dependent shared-library
   * specification environment variable.
   *

   *

   * If we can't find a versioned library (and one was requested) but can find
   * an unversioned library, we'll use the unversioned library. But we will
   * first search all directories for a versioned library under the assumption
   * that if you asked for a version, you care more about meeting that
   * requirement than finding it in the first directory we run across.
   *

   *

   * If all that still fails, we'll fall back to the
   * {@link System#loadLibrary(String)} method (for example, if we cannot guess
   * a libtool-like convention for the OS we're on).
   *

   *

   * We assume a libtool-like library name for the shared library, but will
   * check for common variants on that name.
   *

   *

   * Hopefully an illustration will make this all clearer. Assume we're looking
   * for a library named "foo" with version 1, this method will search as
   * follows:
   *

   *

   *

   *

   * OS
   * Filenames searched for (in order)
   * Directories looked in (in order)
   *

   *

   *
   * On Windows:
   *
   *

   *
foo-1.dll

   *
libfoo-1.dll

   *
cygfoo-1.dll

   *
foo.dll

   *
libfoo.dll

   *
cygfoo.dll

   *

   *
   *
   *

   *
Every directory in the java property java.library.path

   *
Every directory in the environment variable PATH

   *

   *
   *

   *

   *
   * On Linux:
   *
   *

   *
libfoo.so.1

   *
libfoo.so

   *

   *
   *
   *

   *
Every directory in the java property java.library.path

   *
Every directory in the environment variable LD_LIBRARY_PATH

   *

   *
   *

   *

   *
   * On Apple OS-X:
   *
   *

   *
libfoo.1.dylib

   *
libfoo.dylib

   *

   *
   *
   *

   *
Every directory in the java property java.library.path

   *
Every directory in the environment variable DYLD_LIBRARY_PATH

   *

   *
   *

   *

   *
   * @param aLibraryName
   *          The name of the library, without any OS-dependent adornments like
   *          "lib", ".so" or ".dll".
   * @param aMajorVersion
   *          The major version of this library, or null if you want to take any
   *          version.
   *
   * @throws UnsatisfiedLinkError
   *           If we cannot find the library after searching in all the
   *           aforementioned locations.
   */
  @Deprecated
  public static void loadLibrary(String aLibraryName, Long aMajorVersion)
  {
    getInstance().loadLibrary0(aLibraryName, aMajorVersion);
  }

  /**
   * Redirects to {@link #loadLibrary(String, Long)}, but leaves the version as
   * null (not requested).
   *
   * @param aLibraryName
   *          The name of the library, without any OS-dependent adornments like
   *          "lib", ".so" or ".dll".
   */
  public static void loadLibrary(String aLibraryName)
  {
    loadLibrary(aLibraryName, null);
  }

  /**
   * The singleton instance of the {@link JNILibraryLoader}. We only allow one
   * per class-loader instance. Technically it would be nice to ensure only one
   * per process, but that's currently beyond my Java ken.
   */
  private static JNILibraryLoader mGlobalLoader = new JNILibraryLoader();

  /**
   * The set of directories in the java.library.path property; this is queried
   * once per object and then cached, as the property is read-only per Sun's
   * documentation and will not change during the lifetime of this process.
   */
  private String[] mJavaPropPaths;

  /**
   * The set of directories in the environment variable this OS uses to denote
   * places where shared objects may live (e.g. LD_LIBRARY_PATH on Linux); this
   * is queried once per object and then cached, as the property is read-only
   * per Sun's documentation and will not change during the lifetime of this
   * process.
   */
  private String[] mJavaEnvPaths;

  /**
   * What we think the Operating System we're running on is. This is used to
   * guess which environment variable to use for shared objects, and how to turn
   * a library name into a fully qualified file name.
   */
  private OSName mOS = null;

  /**
   * A cache that maps a library name to a set of versions we've loaded.
   */
  private Map<String, Set<Long>> mLoadedLibraries = new HashMap<String, Set<Long>>();

  /**
   * Get the singleton instance of the {@link JNILibraryLoader}.
   *
   * @return The singleton instance to use for all queries.
   */
  static JNILibraryLoader getInstance()
  {
    return mGlobalLoader;
  }

  /**
   * Constructor for {@link JNILibraryLoader}. Should not be callable from
   * outside this class so as to ensure only the singleton object can exist.
   */
  private JNILibraryLoader()
  {
    log.trace("<init>");
  }

  /**
   * This is the method that actually loads the library. It maintains an object
   * level lock, and since this class only allows a singleton object, that is a
   * class-level lock. That means if you're loading a library on one thread,
   * other threads will block until it finishes.
   *
   * This should be OK in general.
   *
   * @param aLibraryName
   *          The library name.
   * @param aMajorVersion
   *          The version, or null if you don't care.
   */
  synchronized void loadLibrary0(String aLibraryName, Long aMajorVersion)
  {
    if (alreadyLoadedLibrary(aLibraryName, aMajorVersion))
      // our work is done.
      return;

    List<String> libCandidates = getLibraryCandidates(aLibraryName,
        aMajorVersion);
    if (libCandidates != null && libCandidates.size() > 0
        && !loadCandidateLibrary(aLibraryName, aMajorVersion, libCandidates))
    {
      // finally, try the System.loadLibrary call
      try
      {
        System.loadLibrary(aLibraryName);
      }
      catch (UnsatisfiedLinkError e)
      {
        log
            .error(
                "Could not load library: {}; version: {}.",
                aLibraryName, aMajorVersion == null ? "" : aMajorVersion);
        throw e;

      }
      // and if we get here it means we successfully loaded since no
      // exception was thrown. Add our library to the cache.
      setLoadedLibrary(aLibraryName, aMajorVersion);
    }
    log.trace("Successfully Loaded library: {}; Version: {}", aLibraryName, aMajorVersion);
  }

  /**
   * Tell the cache that we've loaded this version.
   *
   * @param aLibraryName
   * @param aMajorVersion
   */
  void setLoadedLibrary(String aLibraryName, Long aMajorVersion)
  {
    Set<Long> foundVersions = mLoadedLibraries.get(aLibraryName);
    if (foundVersions == null)
    {
      foundVersions = new HashSet<Long>();
      mLoadedLibraries.put(aLibraryName, foundVersions);
    }
    foundVersions.add(aMajorVersion);
  }

  /**
   * Iterates through the set of aLibCandidates until it succeeds in loading a
   * library. If it succeeds, it lets the cache know.
   *
   * @param aLibraryName
   *          The library name.
   * @param aMajorVersion
   *          The version we want, or null if we don't care.
   * @param aLibCandidates
   *          The set of candidates generated by
   *          {@link #getLibraryCandidates(String, Long)}
   * @return true if we succeeded in loading a library; false otherwise
   */
  boolean loadCandidateLibrary(String aLibraryName, Long aMajorVersion,
      List<String> aLibCandidates)
  {
    boolean retval = false;
    for (String candidate : aLibCandidates)
    {
      log
          .trace(
              "Attempt: library load of library: {}; version: {}: relative path: {}",
              new Object[]
              {
                  aLibraryName,
                  aMajorVersion == null ? "<unspecified>" : aMajorVersion
                      .longValue(), candidate
              });
      File candidateFile = new File(candidate);
      if (candidateFile.exists())
      {
        String absPath = candidateFile.getAbsolutePath();
        try
        {
          log
              .trace(
                  "Attempt: library load of library: {}; version: {}: absolute path: {}",
                  new Object[]
                  {
                      aLibraryName,
                      aMajorVersion == null ? "<unspecified>" : aMajorVersion
                          .longValue(), absPath
                  });
          // Here's where we attempt the actual load.
          System.load(absPath);
          log
              .trace(
                  "Success: library load of library: {}; version: {}: absolute path: {}",
                  new Object[]
                  {
                      aLibraryName,
                      aMajorVersion == null ? "<unspecified>" : aMajorVersion
                          .longValue(), absPath
                  });
          // if we got here, we loaded successfully
          setLoadedLibrary(aLibraryName, aMajorVersion);
          retval = true;
          break;
        }
        catch (UnsatisfiedLinkError e)
        {
          log
              .warn(
                  "Failure: library load of library: {}; version: {}: absolute path: {}; error: {}",
                  new Object[]
                  {
                      aLibraryName,
                      aMajorVersion == null ? "<unspecified>" : aMajorVersion
                          .longValue(), absPath, e
                  });
        }
        catch (SecurityException e)
        {
          log
              .warn(
                  "Failure: library load of library: {}; version: {}: absolute path: {}; error: {}",
                  new Object[]
                  {
                      aLibraryName,
                      aMajorVersion == null ? "<unspecified>" : aMajorVersion
                          .longValue(), absPath, e
                  });
        }
      }
    }
    return retval;
  }

  /**
   * For a given library, and the OS we're running on, this method generates a
   * list of potential absolute file paths that
   * {@link #loadCandidateLibrary(String, Long, String[])} should attempt (in
   * order) to load. This method will not check for existence and readability of
   * the file we're attempting to load.
   *
   * @param aLibraryName
   *          The library name
   * @param aMajorVersion
   *          The version, or null if we don't care.
   * @return The set of absolute file paths to try.
   */
  List<String> getLibraryCandidates(String aLibraryName, Long aMajorVersion)
  {
    final List<String> retval = new LinkedList<String>();
    // Note: when done each of these variables must be set to a non-null, non
    // empty string array
    final String[] prefixes;
    final String[] suffixes;
    final String[] preSuffixVersions;
    final String[] postSuffixVersions;

    switch (getOS())
    {
      case Unknown:
      case Linux:
        prefixes = new String[]
        {
            "lib", ""
        };
        suffixes = new String[]
        {
          ".so"
        };
        preSuffixVersions = new String[]
        {
          ""
        };
        postSuffixVersions = (aMajorVersion == null ? new String[]
        {
          ""
        } : new String[]
        {
          "." + aMajorVersion.longValue()
        });
        break;
      case Windows:
        prefixes = new String[]
        {
            "lib", "", "cyg"
        };
        suffixes = new String[]
        {
          ".dll"
        };
        preSuffixVersions = (aMajorVersion == null ? new String[]
        {
          ""
        } : new String[]
        {
          "-" + aMajorVersion.longValue()
        });
        postSuffixVersions = new String[]
        {
          ""
        };
        break;
      case MacOSX:
        prefixes = new String[]
        {
            "lib", ""
        };
        suffixes = new String[]
        {
          ".dylib"
        };
        preSuffixVersions = (aMajorVersion == null ? new String[]
        {
          ""
        } : new String[]
        {
          "." + aMajorVersion.longValue()
        });
        postSuffixVersions = new String[]
        {
          ""
        };
        break;
      default:
        // really no cases should get here
        prefixes = null;
        suffixes = null;
        preSuffixVersions = null;
        postSuffixVersions = null;
        break;
    }
    initializeSearchPaths();

    // First check the versioned paths
    if (aMajorVersion != null)
    {
      for (String directory : mJavaPropPaths)
      {
        generateFileNames(retval, directory, aLibraryName, prefixes, suffixes,
            preSuffixVersions, postSuffixVersions, true);
      }
      for (String directory : mJavaEnvPaths)
      {
        generateFileNames(retval, directory, aLibraryName, prefixes, suffixes,
            preSuffixVersions, postSuffixVersions, true);
      }
    }
    for (String directory : mJavaPropPaths)
    {
      generateFileNames(retval, directory, aLibraryName, prefixes, suffixes,
          preSuffixVersions, postSuffixVersions, false);
    }
    for (String directory : mJavaEnvPaths)
    {
      generateFileNames(retval, directory, aLibraryName, prefixes, suffixes,
          preSuffixVersions, postSuffixVersions, false);
    }
    return retval;
  }

  void generateFileNames(List<String> aResults, String aDirectory,
      String aLibraryName, String[] aPrefixes, String[] aSuffixes,
      String[] aPreSuffixVersions, String[] aPostSuffixVersions,
      boolean aIncludeVersion)
  {
    // make sure aDirectory ends with correct terminator
    String dirSeparator = File.separator;
    if (!aDirectory.endsWith(dirSeparator))
      aDirectory = aDirectory + dirSeparator;
    for (String suffix : aSuffixes)
    {
      for (String prefix : aPrefixes)
      {
        if (aIncludeVersion)
        {
          for (String preSuffixVersion : aPreSuffixVersions)
          {
            for (String postSuffixVersion : aPostSuffixVersions)
            {
              String result = aDirectory + prefix + aLibraryName
                  + preSuffixVersion + suffix + postSuffixVersion;
              aResults.add(result);
            }
          }
        }
        else
        {
          String result = aDirectory + prefix + aLibraryName + suffix;
          aResults.add(result);
        }
      }
    }
  }

  /**
   * Initialize the paths we'll search for libraries in.
   */
  private void initializeSearchPaths()
  {
    String pathVar = null;
    if (mJavaPropPaths == null)
    {
      pathVar = System.getProperty("java.library.path", "");
      log.trace("property java.library.path: {}", pathVar);
      mJavaPropPaths = getEntitiesFromPath(pathVar);
    }
    if (mJavaEnvPaths == null)
    {
      String envVar = getSystemRuntimeLibraryPathVar();
      pathVar = System.getenv(envVar);
      log.trace("OS environment runtime shared library path ({}): {}", envVar,
          pathVar);
      mJavaEnvPaths = getEntitiesFromPath(pathVar);
    }

  }

  OSName getOS()
  {
    if (mOS != null)
      return mOS;
    OSName retval = OSName.Linux;
    String osName = System.getProperty("os.name", "Linux");
    if (osName.length() > 0)
    {
      if (osName.startsWith("Windows"))
        retval = OSName.Windows;
      else if (osName.startsWith("Mac"))
        retval = OSName.MacOSX;
      else if (osName.startsWith("Linux"))
        retval = OSName.Linux;
      else
        // default everything to Linux
        retval = OSName.Linux;
    }
    mOS = retval;
    log.trace("Detected OS: {}", mOS);
    return retval;
  }

  /**
   * For internal use only. This method allows tests to override the guessed OS
   * name.
   *
   * @param os
   *          The OS to set
   */
  void setOS(OSName os)
  {
    mOS = os;
  }

  String getSystemRuntimeLibraryPathVar()
  {
    String retval = "LD_LIBRARY_PATH";
    switch (getOS())
    {
      case Windows:
        retval = "PATH";
        break;
      case MacOSX:
        retval = "DYLD_LIBRARY_PATH";
        break;
      case Linux:
      case Unknown:
        break;
    }
    return retval;
  }

  String[] getEntitiesFromPath(String aPathVar)
  {
    String[] retval = null;
    String sep = File.pathSeparator;
    if (aPathVar == null || aPathVar.length() == 0)
    {
      retval = new String[1];
      retval[0] = ".";
      log
          .trace("Have empty path var; assuming current directory to find native libraries");
    }
    else
    {
      log.trace("Parsing path var: {}", aPathVar);
      int len = aPathVar.length();

      // find out how many paths there are
      int i = 0;
      int n = 0;
      int j = 0;

      n = 1;
      i = aPathVar.indexOf(sep);
      while (i >= 0)
      {
        n++;
        i = aPathVar.indexOf(sep, i + 1);
      }

      log.trace("Found {} paths in path var: {}", n, aPathVar);
      // Create the return array
      retval = new String[n];

      // now fill in the actual strings
      i = 0;
      n = 0;
      j = aPathVar.indexOf(sep);
      while (j >= 0)
      {
        if (j - i > 0)
        {
          // a non zero entry was found
          retval[n] = aPathVar.substring(i, j);
          log.trace("Added path {} for path var: {}", retval[n], aPathVar);
          ++n;
        }
        else if (j - i == 0)
        {
          // someone put two path separators, with nothing in
          // between. just assume they meant the current directory
          retval[n] = ".";
          log.trace("Added path {} for path var: {}", retval[n], aPathVar);
          ++n;
        }
        i = j + 1;
        j = aPathVar.indexOf(sep, i);
      }
      // and get the last entry which should have no separator
      retval[n] = aPathVar.substring(i, len);
      log.trace("Adding last path {} for path var: {}", retval[n], aPathVar);
      if (retval[n] == null || retval[n].length() == 0)
      {
        // A malformed path with a separator at the end; we just add
        // the current directory again.
        retval[n] = ".";
        log.trace("Faking last path {} for malformed path var: {}", retval[n],
            aPathVar);
      }

    }
    return retval;
  }

  /**
   * Checks our cache to see if we've already loaded this library.
   *
   * We will also detect if we've already loaded another version of this
   * library, and log a warning, but otherwise will return false in that case.
   *
   * @param aLibraryName
   *          The library name.
   * @param aMajorVersion
   *          The version, or null if we don't care.
   * @return true if in cache; false otherwise
   */
  boolean alreadyLoadedLibrary(String aLibraryName, Long aMajorVersion)
  {
    boolean retval = false;
    Set<Long> foundVersions = mLoadedLibraries.get(aLibraryName);
    if (foundVersions != null)
    {
      // we found at least some versions
      if (aMajorVersion == null || foundVersions.contains(aMajorVersion))
      {
        retval = true;
      }
      else
      {
        log
            .warn(
                "Attempting load of {}, version {}, but already loaded verions: {}."
                    + "  We will attempt to load the specified version but behavior is undefined",
                new Object[]
                {
                    aLibraryName, aMajorVersion, foundVersions.toArray()
                });
      }
    }
    return retval;
  }
}


/* ----------------------------------------------------------------------------
 * This file was automatically generated by SWIG (http://www.swig.org).
 * Version 2.0.6
 *
 * Do not make changes to this file unless you know what you are doing--modify
 * the SWIG interface file instead.
 * ----------------------------------------------------------------------------- */

package io.humble.video;
import io.humble.ferry.*;
/**
 * Decodes MediaPacket objects into MediaAudio, MediaPicture or MediaSubtitle objects.
 */
public class Decoder extends Coder {
  // JNIHelper.swg: Start generated code
  // >>>>>>>>>>>>>>>>>>>>>>>>>>>
  /**
   * This method is only here to use some references and remove
   * a Eclipse compiler warning.
   */
  @SuppressWarnings("unused")
  private void noop()
  {
    Buffer.make(null, 1);
  }

  private volatile long swigCPtr;

  /**
   * Internal Only.
   */
  protected Decoder(long cPtr, boolean cMemoryOwn) {
    super(VideoJNI.Decoder_SWIGUpcast(cPtr), cMemoryOwn);
    swigCPtr = cPtr;
  }

  /**
   * Internal Only.
   */
  protected Decoder(long cPtr, boolean cMemoryOwn,
      java.util.concurrent.atomic.AtomicLong ref)
  {
    super(VideoJNI.Decoder_SWIGUpcast(cPtr),
     cMemoryOwn, ref);
    swigCPtr = cPtr;
  }

  /**
   * Internal Only.  Not part of public API.
   *
   * Get the raw value of the native object that obj is proxying for.
   *
   * @param obj The java proxy object for a native object.
   * @return The raw pointer obj is proxying for.
   */
  protected static long getCPtr(Decoder obj) {
    if (obj == null) return 0;
    return obj.getMyCPtr();
  }

  /**
   * Internal Only.  Not part of public API.
   *
   * Get the raw value of the native object that we're proxying for.
   *
   * @return The raw pointer we're proxying for.
   */
  protected long getMyCPtr() {
    if (swigCPtr == 0) throw new IllegalStateException("underlying native object already deleted");
    return swigCPtr;
  }

  /**
   * Create a new Decoder object that is actually referring to the
   * exact same underlying native object.
   *
   * @return the new Java object.
   */
  @Override
  public Decoder copyReference() {
    if (swigCPtr == 0)
      return null;
    else
      return new Decoder(swigCPtr, swigCMemOwn, getJavaRefCount());
  }

  /**
   * Compares two values, returning true if the underlying objects in native code are the same object.
   *
   * That means you can have two different Java objects, but when you do a comparison, you'll find out
   * they are the EXACT same object.
   *
   * @return True if the underlying native object is the same.  False otherwise.
   */
  public boolean equals(Object obj) {
    boolean equal = false;
    if (obj instanceof Decoder)
      equal = (((Decoder)obj).swigCPtr == this.swigCPtr);
    return equal;
  }

  /**
   * Get a hashable value for this object.
   *
   * @return the hashable value.
   */
  public int hashCode() {
     return (int)swigCPtr;
  }

  // <<<<<<<<<<<<<<<<<<<<<<<<<<<
  // JNIHelper.swg: End generated code

/**
 * Create a Decoder that will use the given Codec.

 *

 * @return a Decoder

 * @throws InvalidArgument if codec is null or codec cannot decode.
 */
  public static Decoder make(Codec codec) {
    long cPtr = VideoJNI.Decoder_make__SWIG_0(Codec.getCPtr(codec), codec);
    return (cPtr == 0) ? null : new Decoder(cPtr, false);
  }

/**
 * Creates a Decoder from a given Coder (either an encoder or a decoder).

 * @return a Decoder

 * @throws InvalidArgument if src is null
 */
  public static Decoder make(Coder src) {
    long cPtr = VideoJNI.Decoder_make__SWIG_1(Coder.getCPtr(src), src);
    return (cPtr == 0) ? null : new Decoder(cPtr, false);
  }

/**
 * Flush this Decoder, getting rid of any cached packets (call after seek).

 * Next packet given to decode should be a key packet.
 */
  public void flush() {
    VideoJNI.Decoder_flush(swigCPtr, this);
  }

/**
 * Decode this packet into output.  It will

 * try to fill up the audio samples object, starting

 * from the byteOffset inside this packet.

 *

 * The caller is responsible for allocating the

 * MediaAudio object.  This function will overwrite

 * any data in the samples object.

 *

 * @param output The MediaAudio we decode to. Caller must check if it is complete on return.

 * @param packet    The packet we're attempting to decode from.

 * @param byteOffset Where in the packet payload to start decoding

 *

 * @return number of bytes actually processed from the packet, or negative for error
 */
  public int decodeAudio(MediaAudio output, MediaPacket packet, int byteOffset) {
    return VideoJNI.Decoder_decodeAudio(swigCPtr, this, MediaAudio.getCPtr(output), output, MediaPacket.getCPtr(packet), packet, byteOffset);
  }

/**
 * Decode this packet into output.

 *

 * The caller is responsible for allocating the

 * MediaPicture object.  This function will potentially

 * overwrite any data in the frame object, but

 * you should pass the same MediaPicture into this function

 * repeatedly until Media.isComplete() is true.

 *

 * Note on memory for MediaPicture: For a multitude of reasons,

 * if you created MediaPicture from a buffer, decodeVideo will discard

 * it and replace it with a buffer that is aligned correctly for different

 * CPUs and different codecs. If you must have a copy of the image data

 * in memory managed by you, then pass in a MediaPicture allocated without

 * a buffer to DecodeVideo, and then copy that into your own media picture.

 *

 *

 * @param output The MediaPicture we decode. Caller must check if it is complete on return.

 * @param packet  The packet we're attempting to decode from.

 * @param byteOffset Where in the packet payload to start decoding

 *

 * @return number of bytes actually processed from the packet, or negative for error
 */
  public int decodeVideo(MediaPicture output, MediaPacket packet, int byteOffset) {
    return VideoJNI.Decoder_decodeVideo(swigCPtr, this, MediaPicture.getCPtr(output), output, MediaPacket.getCPtr(packet), packet, byteOffset);
  }

/**
 * Decode this packet into output.  It will

 * try to fill up the media object, starting

 * from the byteOffset inside this packet.

 *

 * The caller is responsible for allocating the

 * correct underlying Media object.  This function will overwrite

 * any data in the samples object.

 *

 * @param output The Media we decode to. Caller must check if it is complete on return.

 * @param packet    The packet we're attempting to decode from.

 * @param byteOffset Where in the packet payload to start decoding

 *

 * @return number of bytes actually processed from the packet, or negative for error

 *

 * @throws InvalidArgument if the media type is not compatible with this decoder.

 * @see decodeVideo

 * @see decodeAudio
 */
  public int decode(MediaSampled output, MediaPacket packet, int byteOffset) {
    return VideoJNI.Decoder_decode(swigCPtr, this, MediaSampled.getCPtr(output), output, MediaPacket.getCPtr(packet), packet, byteOffset);
  }

}


/*******************************************************************************
 * Copyright (c) 2014, Art Clarke.  All rights reserved.
 *
 * This file is part of Humble-Video.
 *
 * Humble-Video is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Humble-Video is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with Humble-Video.  If not, see <http://www.gnu.org/licenses/>.
 *******************************************************************************/
package io.humble.video.demos;

import java.awt.AWTException;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.io.IOException;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;

import io.humble.video.Codec;
import io.humble.video.Encoder;
import io.humble.video.MediaAudio;
import io.humble.video.MediaPacket;
import io.humble.video.MediaPicture;
import io.humble.video.Muxer;
import io.humble.video.MuxerFormat;
import io.humble.video.PixelFormat;
import io.humble.video.Rational;
import io.humble.video.awt.MediaPictureConverter;
import io.humble.video.awt.MediaPictureConverterFactory;

/**
 * Records the contents of your computer screen to a media file for the passed in duration.
 * This is meant as a demonstration program to teach the use of the Humble API.
 *

 * Concepts introduced:
 *

 *

 *
Muxer: A {@link Muxer} object is a container you can write media data to.

 *
Encoders: An {@link Encoder} object lets you convert {@link MediaAudio} or {@link MediaPicture} objects into {@link MediaPacket} objects
 * so they can be written to {@link Muxer} objects.

 *

 *
 *

 * To run from maven, do:
 *

 *

 * mvn install exec:java -Dexec.mainClass="io.humble.video.demos.RecordAndEncodeVideo" -Dexec.args="filename.mp4"
 *

 *
 * @author aclarke
 *
 */
public class RecordAndEncodeVideo {

  /**
   * Records the screen
   */
  private static void recordScreen(String filename, String formatname,
      String codecname, int duration, int snapsPerSecond) throws AWTException, InterruptedException, IOException {
    /**
     * Set up the AWT infrastructure to take screenshots of the desktop.
     */
    final Robot robot = new Robot();
    final Toolkit toolkit = Toolkit.getDefaultToolkit();
    final Rectangle screenbounds = new Rectangle(toolkit.getScreenSize());

    final Rational framerate = Rational.make(1, snapsPerSecond);

    /** First we create a muxer using the passed in filename and formatname if given. */
    final Muxer muxer = Muxer.make(filename, null, formatname);

    /** Now, we need to decide what type of codec to use to encode video. Muxers
     * have limited sets of codecs they can use. We're going to pick the first one that
     * works, or if the user supplied a codec name, we're going to force-fit that
     * in instead.
     */
    final MuxerFormat format = muxer.getFormat();
    final Codec codec;
    if (codecname != null) {
      codec = Codec.findEncodingCodecByName(codecname);
    } else {
      codec = Codec.findEncodingCodec(format.getDefaultVideoCodecId());
    }

    /**
     * Now that we know what codec, we need to create an encoder
     */
    Encoder encoder = Encoder.make(codec);

    /**
     * Video encoders need to know at a minimum:
     *   width
     *   height
     *   pixel format
     * Some also need to know frame-rate (older codecs that had a fixed rate at which video files could
     * be written needed this). There are many other options you can set on an encoder, but we're
     * going to keep it simpler here.
     */
    encoder.setWidth(screenbounds.width);
    encoder.setHeight(screenbounds.height);
    // We are going to use 420P as the format because that's what most video formats these days use
    final PixelFormat.Type pixelformat = PixelFormat.Type.PIX_FMT_YUV420P;
    encoder.setPixelFormat(pixelformat);
    encoder.setTimeBase(framerate);

    /** An annoynace of some formats is that they need global (rather than per-stream) headers,
     * and in that case you have to tell the encoder. And since Encoders are decoupled from
     * Muxers, there is no easy way to know this beyond
     */
    if (format.getFlag(MuxerFormat.Flag.GLOBAL_HEADER))
      encoder.setFlag(Encoder.Flag.FLAG_GLOBAL_HEADER, true);

    /** Open the encoder. */
    encoder.open(null, null);

    /** Add this stream to the muxer. */
    muxer.addNewStream(encoder);

    /** And open the muxer for business. */
    muxer.open(null, null);

    /** Next, we need to make sure we have the right MediaPicture format objects
     * to encode data with. Java (and most on-screen graphics programs) use some
     * variant of Red-Green-Blue image encoding (a.k.a. RGB or BGR). Most video
     * codecs use some variant of YCrCb formatting. So we're going to have to
     * convert. To do that, we'll introduce a MediaPictureConverter object later. object.
     */
    MediaPictureConverter converter = null;
    final MediaPicture picture = MediaPicture.make(
        encoder.getWidth(),
        encoder.getHeight(),
        pixelformat);
    picture.setTimeBase(framerate);

    /** Now begin our main loop of taking screen snaps.
     * We're going to encode and then write out any resulting packets. */
    final MediaPacket packet = MediaPacket.make();
    for (int i = 0; i < duration / framerate.getDouble(); i++) {
      /** Make the screen capture && convert image to TYPE_3BYTE_BGR */
      final BufferedImage screen = convertToType(robot.createScreenCapture(screenbounds), BufferedImage.TYPE_3BYTE_BGR);

      /** This is LIKELY not in YUV420P format, so we're going to convert it using some handy utilities. */
      if (converter == null)
        converter = MediaPictureConverterFactory.createConverter(screen, picture);
      converter.toPicture(picture, screen, i);

      do {
        encoder.encode(packet, picture);
        if (packet.isComplete())
          muxer.write(packet, false);
      } while (packet.isComplete());

      /** now we'll sleep until it's time to take the next snapshot. */
      Thread.sleep((long) (1000 * framerate.getDouble()));
    }

    /** Encoders, like decoders, sometimes cache pictures so it can do the right key-frame optimizations.
     * So, they need to be flushed as well. As with the decoders, the convention is to pass in a null
     * input until the output is not complete.
     */
    do {
      encoder.encode(packet, null);
      if (packet.isComplete())
        muxer.write(packet,  false);
    } while (packet.isComplete());

    /** Finally, let's clean up after ourselves. */
    muxer.close();
  }

  @SuppressWarnings("static-access")
  public static void main(String[] args) throws InterruptedException, IOException, AWTException
  {
    final Options options = new Options();
    options.addOption("h", "help", false, "displays help");
    options.addOption("v", "version", false, "version of this library");
    options.addOption(OptionBuilder.withArgName("format")
        .withLongOpt("format")
        .hasArg().
        withDescription("muxer format to use. If unspecified, we will guess from filename")
        .create("f"));
    options.addOption(OptionBuilder.withArgName("codec")
        .withLongOpt("codec")
        .hasArg()
        .withDescription("codec to use when encoding video; If unspecified, we will guess from format")
        .create("c"));
    options.addOption(OptionBuilder.withArgName("duration")
        .withLongOpt("duration")
        .hasArg()
        .withDescription("number of seconds of screenshot to record; defaults to 10.")
        .create("d"));
    options.addOption(OptionBuilder.withArgName("snaps per second")
        .withLongOpt("snaps")
        .hasArg()
        .withDescription("number of pictures to take per second (i.e. the frame rate); defaults to 5")
        .create("s"));

    final CommandLineParser parser = new org.apache.commons.cli.BasicParser();
    try {
      final CommandLine cmd = parser.parse(options, args);
      final String[] parsedArgs = cmd.getArgs();
      if (cmd.hasOption("version")) {
        // let's find what version of the library we're running
        final String version = io.humble.video_native.Version.getVersionInfo();
        System.out.println("Humble Version: " + version);
      } else if (cmd.hasOption("help") || parsedArgs.length != 1) {
        final HelpFormatter formatter = new HelpFormatter();
        formatter.printHelp(RecordAndEncodeVideo.class.getCanonicalName() + " <filename>", options);
      } else {
        /**
         * Read in some option values and their defaults.
         */
        final int duration = Integer.parseInt(cmd.getOptionValue("duration", "10"));
        if (duration <= 0)
          throw new IllegalArgumentException("duration must be > 0");
        final int snaps = Integer.parseInt(cmd.getOptionValue("snaps", "5"));
        if (snaps <= 0)
          throw new IllegalArgumentException("snaps must be > 0");
        final String codecname = cmd.getOptionValue("codec");
        final String formatname = cmd.getOptionValue("format");
        final String filename = cmd.getArgs()[0];

        recordScreen(filename, formatname, codecname, duration, snaps);
      }
    } catch (ParseException e) {
      System.err.println("Exception parsing command line: " + e.getLocalizedMessage());
    }
  }
  /**
   * Convert a {@link BufferedImage} of any type, to {@link BufferedImage} of a
   * specified type. If the source image is the same type as the target type,
   * then original image is returned, otherwise new image of the correct type is
   * created and the content of the source image is copied into the new image.
   *
   * @param sourceImage
   *          the image to be converted
   * @param targetType
   *          the desired BufferedImage type
   *
   * @return a BufferedImage of the specifed target type.
   *
   * @see BufferedImage
   */

  public static BufferedImage convertToType(BufferedImage sourceImage,
      int targetType)
  {
    BufferedImage image;

    // if the source image is already the target type, return the source image

    if (sourceImage.getType() == targetType)
      image = sourceImage;

    // otherwise create a new image of the target type and draw the new
    // image

    else
    {
      image = new BufferedImage(sourceImage.getWidth(),
          sourceImage.getHeight(), targetType);
      image.getGraphics().drawImage(sourceImage, 0, 0, null);
    }

    return image;
  }

}


/*******************************************************************************
 * Copyright (c) 2013, Art Clarke.  All rights reserved.
 *
 * This file is part of Humble-Video.
 *
 * Humble-Video is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Humble-Video is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Humble-Video.  If not, see <http://www.gnu.org/licenses/>.
 *******************************************************************************/

package io.humble.ferry;

import java.lang.ref.WeakReference;
import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicLong;

import io.humble.ferry.FerryJNI;

/**
 * Returned by {@link Buffer#getByteBuffer(int, int, java.util.concurrent.atomic.AtomicReference)}
 * for users that want to explicitly manage when the returned {@link java.nio.ByteBuffer}
 * is released.
 *

 * This class creates a {@link WeakReference} that Ferry classes will use for
 * memory management. We do this to avoid relying on Java's finalizer thread to
 * keep up and instead make every new native allocation first release any
 * unreachable objects.
 *

 * Most times these objects are managed behind the scenes when you
 * call {@link RefCounted#delete()}.  But when we return
 * {@link java.nio.ByteBuffer} objects, there is no equivalent of
 * delete(), so this object can be used if you want to explicitly control
 * when the {@link ByteBuffer}'s underlying native memory is freed.
 *

 *
 */
public final class JNIReference extends WeakReference<Object>
{
  private final AtomicLong mSwigCPtr = new AtomicLong(0);

  // This memory manager will outlive the Java object we're referencing; that
  // means this class will sometimes show up as a potential leak, but trust us
  // here; ignore all the refs to here and see who else is holding the ref to
  // the JNIMemoryAllocator and that's your likely leak culprit (if we didn't
  // do this, then you'd get no indication of who is leaking your native object
  // so stop complaining now).
  private volatile JNIMemoryAllocator mMemAllocator;

  private final boolean mIsFerryObject;

  private final AtomicLong mJavaRefCount;

  // A static memory allocator that is only constructed once
  private static final JNIMemoryAllocator sMemAllocator = new JNIMemoryAllocator();
  private final boolean sUseStaticMemAllocator = true;

  // Only turn this to true if you need memory debugging on.  Given how
  // hot this code is, remembering debug info is an unnecessary extra
  // step.
  private static volatile boolean mMemoryDebugging = false;
  /* package */ static void setMemoryDebugging(boolean value) {
    mMemoryDebugging = value;
  }
  /* package */ static boolean isMemoryDebugging() {
    return mMemoryDebugging;
  }
  private static class DebugInfo {
    private final int mHashCode;
    private final Class<? extends Object> mClass;
    public DebugInfo(Object aObject)
    {
      mClass = aObject.getClass();
      mHashCode = aObject.hashCode();
    }
    public Class<? extends Object> getObjectClass()
    {
      return mClass;
    }
    public int getObjectHashCode()
    {
      return mHashCode;
    }
  }
  final private DebugInfo mDebugInfo;
  private JNIReference(
      final Object proxy,
      final Object aReferent,
      final long nativeVal,
      final boolean isFerry,
      final AtomicLong javaRefCount)
  {
    super(aReferent, JNIMemoryManager.getMgr().getQueue());
    mIsFerryObject = isFerry;
    mJavaRefCount = javaRefCount;
    mSwigCPtr.set(nativeVal);
    if (mMemoryDebugging) {
      mDebugInfo = new DebugInfo(proxy);
    } else {
      mDebugInfo = null;
    }
    JNIMemoryManager.MemoryModel model = JNIMemoryManager.getMemoryModel();
    if (model == JNIMemoryManager.MemoryModel.JAVA_DIRECT_BUFFERS ||
        model == JNIMemoryManager.MemoryModel.NATIVE_BUFFERS)
      // don't use our allocators
      return;
    if (mJavaRefCount.get() == 1 &&
        FerryJNI.RefCounted_getCurrentNativeRefCount(nativeVal, null) == 1)
    {
      // it's only safe to set the allocator if you're the only reference
      // holder. Otherwise
      // we default to having a null allocator, and allocations are anonymous,
      // but at least
      // won't crash under heavy multi-threaded situations.
      if (sUseStaticMemAllocator)
        mMemAllocator = sMemAllocator;
      else
        mMemAllocator = new JNIMemoryAllocator();
      // we are the only owner of this object; tell it we're the object it can
      // allocate from
      JNIMemoryAllocator.setAllocator(nativeVal, mMemAllocator);
    }
    else
    {
      // This creates a Strong reference to the allocator currently being used
      // so that if the Java proxy object the native object depended upon goes
      // away, the memory manager it was using stays around until this reference
      // is killed
      mMemAllocator = JNIMemoryAllocator.getAllocator(nativeVal);
    }
  }

  /**
   * Returns the {@link JNIMemoryManager} we're using.
   *
   * @return the manager
   */
  public static JNIMemoryManager getMgr()
  {
    return JNIMemoryManager.getMgr();
  }

  static JNIReference createReference(Object proxy,
      Object aReferent, long swigCPtr,
      boolean isFerry, AtomicLong javaRefCount)
  {
    // Clear out any pending native objects
    JNIMemoryManager.getMgr().gcInternal();

    JNIReference ref = new JNIReference(
        proxy, aReferent,
        swigCPtr, isFerry, javaRefCount);
    JNIMemoryManager.getMgr().addReference(ref);
    //System.err.println("added  : "+ref+"; "+swigCPtr+" ("+isFerry+")");
    return ref;
  }
  static JNIReference createReference(Object proxy,
      Object aReferent, long swigCPtr,
      AtomicLong javaRefCount)
  {
    return createReference(proxy, aReferent, swigCPtr, true, javaRefCount);
  }
  static JNIReference createNonFerryReference(
      Object proxy, Object aReferent,
      long swigCPtr, AtomicLong javaRefCount)
  {
    return createReference(proxy, aReferent, swigCPtr, false, javaRefCount);
  }

  /**
   * Explicitly deletes the underlying native storage used by
   * the object this object references.  The underlying native
   * object is now no long valid, and attempts to use it could
   * cause unspecified behavior.
   *
   */
  public void delete()
  {
    // acquire lock for minimum time
    final long swigPtr = mSwigCPtr.getAndSet(0);
    if (swigPtr != 0)
    {
      if (mJavaRefCount.decrementAndGet() == 0)
      {
        // log.debug("deleting: {}; {}", this, mSwigCPtr);
        FerryJNI.RefCounted_release(swigPtr, null);
      }
      // Free the memory manager we use
      mMemAllocator = null;
    }

  }

  /**
   * Returns true if this object derived from {@link RefCounted}.
   * @return true if a ferry object.
   */
  boolean isFerryObject()
  {
    return mIsFerryObject;
  }

  /**
   * Returns true if the underlying reference has had {@link #delete()} called.
   * @return True if {@link #delete()} was called.
   */
  boolean isDeleted()
  {
    return mSwigCPtr.get() == 0;
  }
  /**
   * Creates a string representation of this reference.  If the underlying
   * object this reference points to has been deleted, then "native" will
   * be zero.  If {@link JNIMemoryManager#isMemoryDebugging()} is true,
   * then the class and hashcode of the object this reference points to (or
   * used to point to) is also printed.
   *

   * If the reference still points to an actual object, we will also print
   * the contents of that object.  It may return "null" in which case the
   * underlying object is no longer reachable, but if native != 0, it means
   * it has not yet been collected by Ferry.
   *

   */
  @Override
  public String toString()
  {
    final StringBuilder builder = new StringBuilder();
    builder.append(super.toString());
    builder.append("[");
    builder.append("native=").append(mSwigCPtr.get()).append(";");
    if (mDebugInfo != null) {
      builder.append("proxyClass=").append(mDebugInfo.getObjectClass().getCanonicalName()).append(";");
      builder.append("hashCode=").append(mDebugInfo.getObjectHashCode()).append(";");
    }
    builder.append("object=[").append(get()).append("];");
    builder.append("];");

    return builder.toString();
  }
}