Package net.sf.fmj.theora_java

Source Code of net.sf.fmj.theora_java.NativeOggParser$PullSourceStreamTrack

package net.sf.fmj.theora_java;

import java.awt.Dimension;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.media.BadHeaderException;
import javax.media.Buffer;
import javax.media.Duration;
import javax.media.Format;
import javax.media.IncompatibleSourceException;
import javax.media.ResourceUnavailableException;
import javax.media.Time;
import javax.media.Track;
import javax.media.format.AudioFormat;
import javax.media.format.RGBFormat;
import javax.media.format.VideoFormat;
import javax.media.protocol.ContentDescriptor;
import javax.media.protocol.DataSource;
import javax.media.protocol.PullDataSource;
import javax.media.protocol.PullSourceStream;
import javax.media.util.ImageToBuffer;

import net.sf.fmj.media.AbstractDemultiplexer;
import net.sf.fmj.media.AbstractTrack;
import net.sf.fmj.utility.LoggerSingleton;
import net.sf.fmj.utility.URLUtils;
import net.sf.theora_java.jna.OggLibrary;
import net.sf.theora_java.jna.TheoraLibrary;
import net.sf.theora_java.jna.VorbisLibrary;
import net.sf.theora_java.jna.OggLibrary.ogg_page;
import net.sf.theora_java.jna.OggLibrary.ogg_stream_state;
import net.sf.theora_java.jna.OggLibrary.ogg_sync_state;
import net.sf.theora_java.jna.TheoraLibrary.theora_comment;
import net.sf.theora_java.jna.TheoraLibrary.theora_info;
import net.sf.theora_java.jna.TheoraLibrary.theora_state;
import net.sf.theora_java.jna.TheoraLibrary.yuv_buffer;
import net.sf.theora_java.jna.VorbisLibrary.vorbis_block;
import net.sf.theora_java.jna.VorbisLibrary.vorbis_comment;
import net.sf.theora_java.jna.VorbisLibrary.vorbis_dsp_state;
import net.sf.theora_java.jna.VorbisLibrary.vorbis_info;
import net.sf.theora_java.jna.XiphLibrary.ogg_packet;
import net.sf.theora_java.jna.utils.YUVConverter;

import com.sun.jna.NativeLong;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.PointerByReference;

/**
* Uses theora-jna to parse Ogg files, and decode vorbis and theora data within them.
* Adapted from theora-jna's PlayerExample, which is adapted from player_example.c.
* @author Ken Larson
*
*/
public class NativeOggParser extends AbstractDemultiplexer
{
  private static final Logger logger = LoggerSingleton.logger;

  private final TheoraLibrary THEORA;
  private final OggLibrary OGG;
  private final VorbisLibrary VORBIS;
 
  private static final boolean ENABLE_VIDEO = true;
  private static final boolean ENABLE_AUDIO = true
 
  /* never forget that globals are a one-way ticket to Hell */
  /* Ogg and codec state for demux/decode */
  private final ogg_sync_state   oy = new ogg_sync_state();
  private final ogg_page         og = new ogg_page();
  private ogg_stream_state vo = new ogg_stream_state();
  private ogg_stream_state to = new ogg_stream_state();
  private final theora_info      ti = new theora_info();
  private final theora_comment   tc = new theora_comment();
  private final theora_state     td = new theora_state();
  private final vorbis_info      vi = new vorbis_info();
  private final vorbis_dsp_state vd = new vorbis_dsp_state();
  private final vorbis_block     vb = new vorbis_block();
  private vorbis_comment   vc = new vorbis_comment();

  private int              theora_p=0;
  private int              vorbis_p=0;
  private int              stateflag=0;

  /* single frame video buffering */
  private int          videobuf_ready=0;
  private long /*ogg_int64_t*/  videobuf_granulepos=-1;
  private double       videobuf_time=0// in seconds

  /* single audio fragment audio buffering */
  private int          audiobuf_fill=0;
  private int          audiobuf_ready=0;
  private short []audiobuf;
  private long /*ogg_int64_t*/  audiobuf_granulepos=0; /* time position of last sample */


  /** In bytes. */
  private int          audiofd_fragsize;      /* read and write only complete fragments
                                         so that SNDCTL_DSP_GETOSPACE is
                                         accurate immediately after a bank
                                         switch */
 
   private final ogg_packet op = new ogg_packet();
 
  // if USE_DATASOURCE_URL_ONLY is true, this is a bit of a hack - we don't really use the DataSource, we just grab its URL.  So arbitrary data sources won't work.
  private final boolean USE_DATASOURCE_URL_ONLY = false;
 
  private ContentDescriptor[] supportedInputContentDescriptors = new ContentDescriptor[] {
      new ContentDescriptor("video.ogg"),
      new ContentDescriptor("audio.ogg"),
      new ContentDescriptor("application.ogg"),
      new ContentDescriptor("application.x_ogg"),
      // TODO: content descriptors are problematic, because an .ogg file will be interpreted as an audio file, and
      // another handler may try it.
     
      // See http://wiki.xiph.org/index.php/MIME_Types_and_File_Extensions
      // for mime type info.
  };
 
  // also, once this parser is able to simply extract the tracks, rather than just decode the data,
  // information on the container-less mime types is available at http://wiki.xiph.org/index.php/MIME_Types_and_File_Extensions
 
 
  public NativeOggParser()
  {
    try
    {
      THEORA = TheoraLibrary.INSTANCE;
      OGG = OggLibrary.INSTANCE;
      VORBIS = VorbisLibrary.INSTANCE;
    }
    catch (Throwable t)
    {
      logger.log(Level.WARNING, "Unable to initialize ffmpeg libraries: " + t);
      throw new RuntimeException(t);
    }
  }
 
  private static final Object OGG_SYNC_OBJ = new Boolean(true)// synchronize on this before using the libraries, to prevent threading problems.
 
  private PullDataSource source;
 
  private PullSourceStreamTrack[] tracks;
 
 
  @Override
  public ContentDescriptor[] getSupportedInputContentDescriptors()
  {
    return supportedInputContentDescriptors;
  }

 
  @Override
  public Track[] getTracks() throws IOException, BadHeaderException
  {
    return tracks;
  }

 
  @Override
  public void setSource(DataSource source) throws IOException, IncompatibleSourceException
  {
    final String protocol = source.getLocator().getProtocol();
   
    if (USE_DATASOURCE_URL_ONLY)
    {
      if (!(protocol.equals("file")))
        throw new IncompatibleSourceException();

    }
    else
    {

   
    if (!(source instanceof PullDataSource))
      throw new IncompatibleSourceException();
    }
   
    this.source = (PullDataSource) source;
   
  }
 
 
  private FileInputStream infile;
  private PullSourceStream instream;

  private int buffer_data(ogg_sync_state oy) throws IOException
 
    if (USE_DATASOURCE_URL_ONLY)
      return buffer_data(infile, oy);
    else
      return buffer_data(instream, oy);
  }
 
  private int buffer_data(FileInputStream in, ogg_sync_state oy) throws IOException
  {
    final int BUFSIZE = 4096;
    Pointer buffer = OGG.ogg_sync_buffer(oy, new NativeLong(BUFSIZE));
    byte[] buffer2 = new byte[BUFSIZE]; // TODO: this is inefficient.
    int bytes = in.read(buffer2, 0, BUFSIZE);
    if (bytes < 0)
      return bytes; // EOF
    buffer.write(0, buffer2, 0, bytes);
    OGG.ogg_sync_wrote(oy, new NativeLong(bytes));
    return (bytes);
  }
 
  private int buffer_data(PullSourceStream in, ogg_sync_state oy) throws IOException
  {
    final int BUFSIZE = 4096;
    Pointer buffer = OGG.ogg_sync_buffer(oy, new NativeLong(BUFSIZE));
    byte[] buffer2 = new byte[BUFSIZE]; // TODO: this is inefficient.
    int bytes = in.read(buffer2, 0, BUFSIZE);
    if (bytes < 0)
      return bytes; // EOF
    buffer.write(0, buffer2, 0, bytes);
    OGG.ogg_sync_wrote(oy, new NativeLong(bytes));
    return (bytes);
  }

  /** dump the theora (or vorbis) comment header */
  int dump_comments(theora_comment tc)
  {
    int i, len;
   
    logger.info("Encoded by " + tc.vendor.getString(0));
    if (tc.comments > 0)
    {
      logger.info("theora comment header:");
      for (i = 0; i < tc.comments; i++)
      {
        final Pointer[] user_comments = tc.user_comments.getPointerArray(0, tc.comments);
        final int[] comment_lengths = tc.comment_lengths.getIntArray(0, tc.comments);

        if (user_comments[i] != null)
        {
          len = comment_lengths[i];
          final String value = new String(user_comments[i].getByteArray(0, len));
          logger.info("\t" + value);
        }
      }
    }
    return (0);
  }

  /**
   * Report the encoder-specified colorspace for the video, if any. We
   * don't actually make use of the information in this example; a real
   * player should attempt to perform color correction for whatever
   * display device it supports.
   */
  static void report_colorspace(theora_info ti)
  {
    switch (ti.colorspace)
    {
    case TheoraLibrary.OC_CS_UNSPECIFIED:
      /* nothing to report */
      break;
    case TheoraLibrary.OC_CS_ITU_REC_470M:
      logger.info("  encoder specified ITU Rec 470M (NTSC) color.");
      break;
    case TheoraLibrary.OC_CS_ITU_REC_470BG:
      logger.info("  encoder specified ITU Rec 470BG (PAL) color.");
      break;
    default:
      logger.warning("warning: encoder specified unknown colorspace (" + ti.colorspace + ")");
      break;
    }
  }

  /** helper: push a page into the appropriate steam
   * this can be done blindly; a stream won't accept a page that doesn't
   * belong to it
   */
  int queue_page(ogg_page page)
  {
    if (theora_p != 0)
      OGG.ogg_stream_pagein(to, og);
    if (vorbis_p != 0)
      OGG.ogg_stream_pagein(vo, og);
    return 0;
  }
 
  // @Override
  @Override
  public void open() throws ResourceUnavailableException
  {
    synchronized (OGG_SYNC_OBJ)
    {

      try
      {
        final String urlStr;

        if (USE_DATASOURCE_URL_ONLY)
        {
          // just use the file URL from the datasource
          final File f = new File(URLUtils.extractValidPathFromFileUrl(source.getLocator().toExternalForm()));
          infile = new FileInputStream(f);
        } else
        {
          source.connect();
          source.start()// TODO: stop/disconnect on stop/close.
          instream = source.getStreams()[0];
         
        }

        /* start up Ogg stream synchronization layer */
        OGG.ogg_sync_init(oy);

        /* init supporting Vorbis structures needed in header parsing */
        VORBIS.vorbis_info_init(vi);
        VORBIS.vorbis_comment_init(vc);

        /* init supporting Theora structures needed in header parsing */
        THEORA.theora_comment_init(tc);
        THEORA.theora_info_init(ti);

        // System.out.println("Parsing headers...");
        /* Ogg file open; parse the headers */
        /* Only interested in Vorbis/Theora streams */
        while (stateflag == 0)
        {
          int ret = buffer_data(oy);
          if (ret <= 0)
            break;
          while (OGG.ogg_sync_pageout(oy, og) > 0)
          {
            ogg_stream_state test = new ogg_stream_state();

            /*
             * is this a mandated initial header? If not, stop
             * parsing
             */
            if (OGG.ogg_page_bos(og) == 0)
            {
              /*
               * don't leak the page; get it into the appropriate
               * stream
               */
              queue_page(og);
              stateflag = 1;
              break;
            }

            OGG.ogg_stream_init(test, OGG.ogg_page_serialno(og));
            OGG.ogg_stream_pagein(test, og);
            OGG.ogg_stream_packetout(test, op);

            /* identify the codec: try theora */
            if (ENABLE_VIDEO && theora_p == 0 && THEORA.theora_decode_header(ti, tc, op) >= 0)
            {
              /* it is theora */
              to = test;
              theora_p = 1;
            } else if (ENABLE_AUDIO && vorbis_p == 0 && VORBIS.vorbis_synthesis_headerin(vi, vc, op) >= 0)
            {
              /* it is vorbis */
              vo = test;
              vorbis_p = 1;
            } else
            {
              /* whatever it is, we don't care about it */
              OGG.ogg_stream_clear(test);
            }
          }
          /* fall through to non-bos page parsing */
        }

        /* we're expecting more header packets. */
        while ((theora_p != 0 && theora_p < 3) || (vorbis_p != 0 && vorbis_p < 3))
        {
          int ret;

          /* look for further theora headers */
          while (theora_p != 0 && (theora_p < 3) && ((ret = OGG.ogg_stream_packetout(to, op))) != 0)
          {
            if (ret < 0)
            {
              throw new ResourceUnavailableException("Error parsing Theora stream headers; corrupt stream?");
            }
            if (THEORA.theora_decode_header(ti, tc, op) != 0)
            {
              throw new ResourceUnavailableException("Error parsing Theora stream headers; corrupt stream?");
            }
            theora_p++;
            if (theora_p == 3)
              break;
          }

          /* look for more vorbis header packets */
          while (vorbis_p != 0 && (vorbis_p < 3) && ((ret = OGG.ogg_stream_packetout(vo, op))) != 0)
          {
            if (ret < 0)
            {
              throw new ResourceUnavailableException("Error parsing Vorbis stream headers; corrupt stream?");
            }

            if (VORBIS.vorbis_synthesis_headerin(vi, vc, op) != 0)
            {
              throw new ResourceUnavailableException("Error parsing Vorbis stream headers; corrupt stream?");
            }
            vorbis_p++;
            if (vorbis_p == 3)
              break;
          }

          /*
           * The header pages/packets will arrive before anything else
           * we care about, or the stream is not obeying spec
           */

          if (OGG.ogg_sync_pageout(oy, og) > 0)
          {
            queue_page(og); /* demux into the appropriate stream */
          } else
          {
            final int ret2 = buffer_data(oy); /* someone needs more data  */
            if (ret2 <= 0)
            {
              throw new ResourceUnavailableException("End of file while searching for codec headers.");
            }
          }
        }

        /* and now we have it all. initialize decoders */
        if (theora_p != 0)
        {
          THEORA.theora_decode_init(td, ti);
          final double fps = (double) ti.fps_numerator / (double) ti.fps_denominator;
          logger.info("Ogg logical stream " + Integer.toHexString( to.serialno.intValue()) + " is Theora " + ti.width + "x" + ti.height + " " + fps + " fps");
          switch (ti.pixelformat)
          {
          case TheoraLibrary.OC_PF_420:
            logger.info(" 4:2:0 video");
            break;
          case TheoraLibrary.OC_PF_422:
            logger.info(" 4:2:2 video");
            break;
          case TheoraLibrary.OC_PF_444:
            logger.info(" 4:4:4 video");
            break;
          case TheoraLibrary.OC_PF_RSVD:
          default:
            logger.info(" video\n  (UNKNOWN Chroma sampling!)");
            break;
          }
          if (ti.width != ti.frame_width || ti.height != ti.frame_height)
          {  logger.warning("  Frame content is " + ti.frame_width + "x" + ti.frame_height + " with offset (" +  ti.offset_x + "," + ti.offset_y + ").");
            // TODO: we need to handle cropping properly.
          }
          report_colorspace(ti);
          dump_comments(tc);
        } else
        {
          /* tear down the partial theora setup */
          THEORA.theora_info_clear(ti);
          THEORA.theora_comment_clear(tc);
        }
        if (vorbis_p != 0)
        {
          VORBIS.vorbis_synthesis_init(vd, vi);
          VORBIS.vorbis_block_init(vd, vb);
          logger.info("Ogg logical stream " + Integer.toHexString(vo.serialno.intValue()) + " is Vorbis " + vi.channels + " channel " + vi.rate.intValue() + " Hz audio.");
        } else
        {
          /* tear down the partial vorbis setup */
          VORBIS.vorbis_info_clear(vi);
          VORBIS.vorbis_comment_clear(vc);
        }

        stateflag = 0; /* playback has not begun */

        VideoTrack videoTrack = null;
        AudioTrack audioTrack = null;

        if (theora_p != 0)
        {
          videoTrack = new VideoTrack();
        }
        if (vorbis_p != 0)
        {
          audioTrack = new AudioTrack();
        }

        if (audioTrack == null && videoTrack == null)
          throw new ResourceUnavailableException("No audio or video track found");
        else if (audioTrack != null && videoTrack != null)
          tracks = new PullSourceStreamTrack[] { videoTrack, audioTrack };
        else if (audioTrack != null)
          tracks = new PullSourceStreamTrack[] { audioTrack };
        else
          tracks = new PullSourceStreamTrack[] { videoTrack };

      } catch (IOException e)
      {
        logger.log(Level.WARNING, "" + e, e);
        throw new ResourceUnavailableException("" + e);
      }
    }

    super.open();

  }

  @Override
  public void close()
  {

    synchronized (OGG_SYNC_OBJ)
    {
      if (tracks != null)
      {
        for (int i = 0; i < tracks.length; ++i)
        {
          if (tracks[i] != null)
          {
            tracks[i].deallocate();
            tracks[i] = null;
          }
        }
        tracks = null;
      }

      if (vorbis_p != 0)
      {
        OGG.ogg_stream_clear(vo);
        VORBIS.vorbis_block_clear(vb);
        VORBIS.vorbis_dsp_clear(vd);
        VORBIS.vorbis_comment_clear(vc);
        VORBIS.vorbis_info_clear(vi);
      }
      if (theora_p != 0)
      {
        OGG.ogg_stream_clear(to);
        THEORA.theora_clear(td);
        THEORA.theora_comment_clear(tc);
        THEORA.theora_info_clear(ti);
      }
      OGG.ogg_sync_clear(oy);

      try
      {
        if (infile != null)
          infile.close();
      } catch (IOException e)
      {
        logger.log(Level.WARNING, "" + e, e);
      }
    }
    super.close();
  }

  // @Override
  @Override
  public void start() throws IOException
  {

  }
 
  // TODO: should we stop data source in stop?
//  // @Override
//  public void stop()
//  {
//    try
//    {
//      source.stop();
//    } catch (IOException e)
//    {
//      logger.log(Level.WARNING, "" + e, e);
//    }
//  }
 
 
 
  @Override
  public boolean isPositionable()
  {
    return false// TODO
  }
 
//  @Override
//  public Time setPosition(Time where, int rounding)
//  {
//    synchronized (OGG_SYNC_OBJ)
//    { 
//     
//    }
//  }

 
  @Override
  public boolean isRandomAccess()
  {
    return super.isRandomAccess()// TODO: can we determine this from the data source?
  }
 
    public static VideoFormat convertCodecPixelFormat(theora_info ti)
    {
      // resulting format based on what YUVConverter will return.  Depends on a bit of internal
      // knowledge of how YUVConverter works.
     
      // TODO: we are ignoring any cropping here.
     
    final Dimension size = new Dimension(ti.width, ti.height);
    final int maxDataLength = ti.width * ti.height;
    final Class dataType = int[].class;
    final int bitsPerPixel = 32
    final float frameRate = (float) ti.fps_numerator / (float) ti.fps_denominator; 
   
    final int red;
    final int green;
    final int blue;

    // YUVConverter returns TYPE_INT_RGB
    final int bufferedImageType = BufferedImage.TYPE_INT_RGB;
   
    if (bufferedImageType == BufferedImage.TYPE_INT_BGR)
    {
      // TODO: test
      red = 0xFF;
      green = 0xFF00
      blue = 0xFF0000;
    }
    else if (bufferedImageType == BufferedImage.TYPE_INT_RGB)
    {
      red = 0xFF0000;
      green = 0xFF00;
      blue = 0xFF;
    }
    else if (bufferedImageType == BufferedImage.TYPE_INT_ARGB)
    {
      red = 0xFF0000;
      green = 0xFF00;
      blue = 0xFF;
      // just ignore alpha
    }
    else
      throw new IllegalArgumentException("Unsupported buffered image type: " + bufferedImageType);
     
    return new RGBFormat(size, maxDataLength, dataType, frameRate, bitsPerPixel, red, green, blue);
     
    }
   
   
    public static AudioFormat convertCodecAudioFormat(vorbis_info vi)
    {
    return new AudioFormat(AudioFormat.LINEAR, vi.rate.floatValue(), 16, vi.channels, AudioFormat.LITTLE_ENDIAN, AudioFormat.SIGNED);
    }
   
    private boolean eomAudio;  // set to true on end of media
    private boolean eomVideo;  // set to true on end of media
    private int videoFrameNo = -1;
   
    private void nextVideoBuffer() throws IOException
  {
      synchronized (OGG_SYNC_OBJ)
    {
      int i;
      int j;
     
      while (theora_p != 0 && videobuf_ready == 0)
      {
        /* theora is one in, one out... */
        if (OGG.ogg_stream_packetout(to, op) > 0)
        {
          ++videoFrameNo;
         
          final int ret = THEORA.theora_decode_packetin(td, op);
          if (ret < 0)
            throw new IOException("theora_decode_packetin failed: " + ret);
          videobuf_granulepos = td.granulepos;

          videobuf_time = THEORA.theora_granule_time(td, videobuf_granulepos);
          if (videobuf_time == 0.0)
          {  // TODO: for some reason, some videos, like Apollo_15_liftoff_from_inside_LM.ogg (available from wikimedia)
            // always report the videobuf_time as zero.  So we'll just calculate it based on the frame rate and
            // the frame number.
            videobuf_time = (double) videoFrameNo * (double) ti.fps_denominator / ti.fps_numerator;
          }
         
          // /* is it already too old to be useful? This is only actually
          // useful cosmetically after a SIGSTOP. Note that we have to
          // decode the frame even if we don't show it (for now) due to
          // keyframing. Soon enough libtheora will be able to deal
          // with non-keyframe seeks. */
          //
          // if(videobuf_time>=get_time())
          videobuf_ready = 1;
         

        } else
          break;
      }

      // if(videobuf_ready == 0 && audiobuf_ready == 0 &&
      // feof(infile))break;

      if (videobuf_ready == 0)
      {
        /* no data yet for somebody. Grab another page */
        int bytes = buffer_data(oy);
        if (bytes < 0)
        {
          eomVideo = true;
        }
        while (OGG.ogg_sync_pageout(oy, og) > 0)
        {
          queue_page(og);
        }
      }

      // /* If playback has begun, top audio buffer off immediately. */
      // if(stateflag != 0) audio_write_nonblocking();
      // 
      // /* are we at or past time for this video frame? */
      // if(stateflag != 0 && videobuf_ready != 0
      // // && videobuf_time<=get_time()
      // )
      // {
      // video_write();
      // videobuf_ready=0;
      // }

      /*
       * if our buffers either don't exist or are ready to go, we can
       * begin playback
       */
      if ((theora_p == 0 || videobuf_ready != 0))
        stateflag = 1;

      // /* same if we've run out of input */
      // if(feof(infile))stateflag=1;
    }
  }
   
    private void nextAudioBuffer() throws IOException
  {
      synchronized (OGG_SYNC_OBJ)
    {
      int i;
      int j;
      /*
       * we want a video and audio frame ready to go at all times. If we
       * have to buffer incoming, buffer the compressed data (ie, let ogg
       * do the buffering)
       */
      while (vorbis_p != 0 && audiobuf_ready == 0)
      {
        int ret;
        final PointerByReference pcm = new PointerByReference();

        /* if there's pending, decoded audio, grab it */
        if ((ret = VORBIS.vorbis_synthesis_pcmout(vd, pcm)) > 0)
        {

          final Pointer ppChannels = pcm.getValue();
          final Pointer[] pChannels = ppChannels.getPointerArray(0, vi.channels);

          final float[][] floatArrays = new float[pChannels.length][];
          for (int k = 0; k < pChannels.length; ++k)
          {
            floatArrays[k] = pChannels[k].getFloatArray(0, ret);
          }

          int count = audiobuf_fill / 2;
          final int maxsamples = (audiofd_fragsize - audiobuf_fill) / 2 / vi.channels;
          for (i = 0; i < ret && i < maxsamples; i++)
          {
            for (j = 0; j < vi.channels; j++)
            {

              int val = Math.round(floatArrays[j][i] * 32767.f);
              if (val > 32767)
                val = 32767;
              if (val < -32768)
                val = -32768;
              audiobuf[count++] = (short) val;
            }
          }

          VORBIS.vorbis_synthesis_read(vd, i);
          audiobuf_fill += i * vi.channels * 2;
          if (audiobuf_fill == audiofd_fragsize)
            audiobuf_ready = 1;
          if (vd.granulepos >= 0)
            audiobuf_granulepos = vd.granulepos - ret + i;
          else
            audiobuf_granulepos += i;

        } else
        {

          /* no pending audio; is there a pending packet to decode? */
          if (OGG.ogg_stream_packetout(vo, op) > 0)
          {
            if (VORBIS.vorbis_synthesis(vb, op) == 0) /*
                                   * test for
                                   * success!
                                   */
              VORBIS.vorbis_synthesis_blockin(vd, vb);
          } else
            /* we need more data; break out to suck in another page */
            break;
        }
      }
      // if(videobuf_ready == 0 && audiobuf_ready == 0 &&
      // feof(infile))break;

      if (audiobuf_ready == 0)
      {
        /* no data yet for somebody. Grab another page */
        int bytes = buffer_data(oy);
        if (bytes < 0)
        {
          eomAudio = true;
        }
        while (OGG.ogg_sync_pageout(oy, og) > 0)
        {
          queue_page(og);
        }
      }

      // /* If playback has begun, top audio buffer off immediately. */
      // if(stateflag != 0) audio_write_nonblocking();
      // 
      // /* are we at or past time for this video frame? */
      // if(stateflag != 0 && videobuf_ready != 0
      // // && videobuf_time<=get_time()
      // )
      // {
      // video_write();
      // videobuf_ready=0;
      // }

      /*
       * if our buffers either don't exist or are ready to go, we can
       * begin playback
       */
      if ((vorbis_p == 0 || audiobuf_ready != 0))
        stateflag = 1;

      // /* same if we've run out of input */
      // if(feof(infile))stateflag=1;
    }
  }

  private abstract class PullSourceStreamTrack extends AbstractTrack
  {
    public abstract void deallocate();

  }
 


 
  private class VideoTrack extends PullSourceStreamTrack
  {
    // TODO: track listener
   
    private final VideoFormat format;
   
    public VideoTrack() throws ResourceUnavailableException
    {
      super();
   
   
      synchronized (OGG_SYNC_OBJ)
        {
          // set format
        format = convertCodecPixelFormat(ti);
        }
    }
   
   
    @Override
    public void deallocate()
    {
    }

    /**
     *
     * @return nanos skipped, 0 if unable to skip.
     * @throws IOException
     */
    public long skipNanos(long nanos) throws IOException
    {
      return 0// TODO
     
    }
   
    public boolean canSkipNanos()
    {
      return false;
    }

    @Override
    public Format getFormat()
    {
      return format;
    }

//      TODO: from JAVADOC:
//       This method might block if the data for a complete frame is not available. It might also block if the stream contains intervening data for a different interleaved Track. Once the other Track is read by a readFrame call from a different thread, this method can read the frame. If the intervening Track has been disabled, data for that Track is read and discarded.
//
//      Note: This scenario is necessary only if a PullDataSource Demultiplexer implementation wants to avoid buffering data locally and copying the data to the Buffer passed in as a parameter. Implementations might decide to buffer data and not block (if possible) and incur data copy overhead.
    
    @Override
    public void readFrame(Buffer buffer)
    {
      synchronized (OGG_SYNC_OBJ)
      {
        try
        {
          nextVideoBuffer();
        } catch (IOException e)
        {
          buffer.setLength(0);
          buffer.setDiscard(true);
          throw new RuntimeException(e); // TODO: how to handle?
        }

        /* are we at or past time for this video frame? */
        if (stateflag != 0 && videobuf_ready != 0
        // && videobuf_time<=get_time()
        )
        {
          final yuv_buffer yuv = new yuv_buffer();

          THEORA.theora_decode_YUVout(td, yuv);

          final BufferedImage bi = YUVConverter.toBufferedImage(yuv, ti);

          final Buffer b = ImageToBuffer.createBuffer(bi, format.getFrameRate());

          buffer.setData(b.getData());
          buffer.setLength(b.getLength());
          buffer.setOffset(b.getOffset());
          buffer.setEOM(false);
          buffer.setDiscard(false);
          buffer.setTimeStamp((long) secondsToNanos(videobuf_time));

          //System.out.println("Generated video buffer");
          videobuf_ready = 0;
        } else
        {
          buffer.setEOM(eomVideo);
          buffer.setLength(0);
          if (!eomVideo)
            buffer.setDiscard(true);
         
        }
      }

    }

   
    @Override
    public Time mapFrameToTime(int frameNumber)
    {
      return TIME_UNKNOWN; 
    }

    @Override
    public int mapTimeToFrame(Time t)
    { 
      return FRAME_UNKNOWN;   
    }
   
 
   
    @Override
    public Time getDuration()
    {
      return Duration.DURATION_UNKNOWN;  // TODO
    }

  }

 
  private class AudioTrack extends PullSourceStreamTrack
  {
    // TODO: track listener
   
    private final AudioFormat format;
   
    public AudioTrack() throws ResourceUnavailableException
    {
      super();

      audiofd_fragsize=10000// TODO: this is just a hack
      audiobuf = new short[audiofd_fragsize/2]// audiofd_fragsize is in bytes, so divide by two to get shorts
     
      synchronized (OGG_SYNC_OBJ)
        {
        format = convertCodecAudioFormat(vi);
        }
       

    }
   
   
    @Override
    public void deallocate()
    {
    }

    // TODO: implement seeking using av_seek_frame
    /**
     *
     * @return nanos skipped, 0 if unable to skip.
     * @throws IOException
     */
    public long skipNanos(long nanos) throws IOException
    {
      return 0;
     
    }
   
    public boolean canSkipNanos()
    {
      return false;
    }

    @Override
    public Format getFormat()
    {
      return format;
    }

//      TODO: from JAVADOC:
//       This method might block if the data for a complete frame is not available. It might also block if the stream contains intervening data for a different interleaved Track. Once the other Track is read by a readFrame call from a different thread, this method can read the frame. If the intervening Track has been disabled, data for that Track is read and discarded.
//
//      Note: This scenario is necessary only if a PullDataSource Demultiplexer implementation wants to avoid buffering data locally and copying the data to the Buffer passed in as a parameter. Implementations might decide to buffer data and not block (if possible) and incur data copy overhead.
    
    @Override
    public void readFrame(Buffer buffer)
    {

      synchronized (OGG_SYNC_OBJ)
      {
        try
        {
          nextAudioBuffer()// TODO: this often generates discard buffers, we could be smarter about it.  Same for video.
        } catch (IOException e)
        {
          buffer.setLength(0);
          buffer.setDiscard(true);
          throw new RuntimeException(e); // TODO: how to handle?
        }

        /* If playback has begun, top audio buffer off immediately. */
        if (stateflag == 0)
        {
          buffer.setEOM(eomAudio);
          buffer.setLength(0);
          if (!eomAudio)
            buffer.setDiscard(true);
         
          return;
        } else
        {
          if (audiobuf_ready == 0)
          {
            buffer.setEOM(eomAudio);
            buffer.setLength(0);
            if (!eomAudio)
              buffer.setDiscard(true);
            //System.out.println("Generated discard buffer: ");
            return;
          } else
          {

            // convert from short array to byte array. TODO: inefficient, should just store in byte array to begin with.
            final byte[] data = new byte[audiobuf.length * 2];
            for (int i = 0; i < audiobuf.length; ++i)
            {
              // little-endian:
              data[i * 2] = (byte) (audiobuf[i] & 0xff);
              data[i * 2 + 1] = (byte) ((audiobuf[i] >> 8) & 0xff);
            }

            buffer.setData(data);
            buffer.setLength(data.length);
            buffer.setOffset(0);
            buffer.setEOM(false);
            buffer.setDiscard(false);
            buffer.setTimeStamp(System.currentTimeMillis()); // TODO

            //System.out.println("Generated audio buffer: " + data.length);
           
            audiobuf_fill = 0;
            audiobuf_ready = 0;

          }
        }
      }
    }
   
    @Override
    public Time mapFrameToTime(int frameNumber)
    {
      return TIME_UNKNOWN; 
    }

    @Override
    public int mapTimeToFrame(Time t)
    { 
      return FRAME_UNKNOWN;   
    }

    @Override
    public Time getDuration()
    {
      return Duration.DURATION_UNKNOWN; // TODO
    }

  }
 
  private static final double secondsToNanos(double secs)
  {  return secs * 1000000000.0;
  }
}
 
TOP

Related Classes of net.sf.fmj.theora_java.NativeOggParser$PullSourceStreamTrack

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.