Package net.sf.fmj.media.renderer.video

Source Code of net.sf.fmj.media.renderer.video.JPEGRTPRenderer

package net.sf.fmj.media.renderer.video;

import java.awt.Component;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.util.logging.Logger;

import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.plugins.jpeg.JPEGHuffmanTable;
import javax.imageio.plugins.jpeg.JPEGImageReadParam;
import javax.imageio.plugins.jpeg.JPEGQTable;
import javax.imageio.stream.ImageInputStream;
import javax.media.Buffer;
import javax.media.Format;
import javax.media.Owned;
import javax.media.control.FrameRateControl;
import javax.media.format.VideoFormat;
import javax.media.renderer.VideoRenderer;

import net.sf.fmj.media.AbstractVideoRenderer;
import net.sf.fmj.media.codec.video.jpeg.RFC2035;
import net.sf.fmj.utility.ArrayUtility;
import net.sf.fmj.utility.LoggerSingleton;

/**
*
* Fast JPEG/RTP processing, depacketize and renders the JPEG/RTP stream
*
* @author mgodehardt
*
*/
public class JPEGRTPRenderer extends AbstractVideoRenderer implements VideoRenderer
{
  private static final Logger logger = LoggerSingleton.logger;
  private static final boolean TRACE = true;
 
  private final Format[] supportedInputFormats = new Format[] {new VideoFormat(VideoFormat.JPEG_RTP, null, -1, Format.byteArray, -1.0f)};
 
  private JVideoComponent component = new JVideoComponent();
 
    private JPEGRTPFrame currentFrame;
 
  private ImageReader decoder;
  private JPEGImageReadParam param;
  private JPEGHuffmanTable[] huffmanDCTables;
  private JPEGHuffmanTable[] huffmanACTables;
  private JPEGQTable[] qtable;
  private int quality = -1;                            // last seen quality
 
  private float frameRate = -1;
  private int framesProcessed;
  private long lastTimestamp;

  private BufferedImage itsImage;
 
  // ctor
  public JPEGRTPRenderer()
  {
    addControl(this);
    addControl(new VideoFrameRateControl());
  }
 
  //@Override
  @Override
  public String getName()
  {
    return "JPEG/RTP Renderer";
  }
 
  //@Override
  @Override
  public Format[] getSupportedInputFormats()
  {
    return supportedInputFormats;
  }
 
  @Override
  public Component getComponent()
  {
    return component;
  }
 
  @Override
  public void close()
  {
    if ( null != decoder )
    {
      decoder.dispose();
    }
  }
 
  //@Override
  @Override
  public Format setInputFormat(Format format)
  {
    VideoFormat chosenFormat = (VideoFormat) super.setInputFormat(format);
    if (chosenFormat != null)
    {
      getComponent().setPreferredSize(chosenFormat.getSize());
    }
    return chosenFormat;
  }
 
  private void initDecoder(int q)
  {
    if ( null != decoder )
    {
      decoder.dispose();
    }
   
    decoder = ImageIO.getImageReadersByFormatName("JPEG").next();
    param = new JPEGImageReadParam();
    huffmanACTables = createACHuffmanTables();
    huffmanDCTables = createDCHuffmanTables();
    qtable = createQTable(q);
    param.setDecodeTables(qtable, huffmanDCTables, huffmanACTables);
  }
 
  private JPEGQTable[] createQTable(int q)
  {
    byte[] lumQ = new byte[64];
    byte[] chmQ = new byte[64];
   
    RFC2035.MakeTables(q, lumQ, chmQ, RFC2035.jpeg_luma_quantizer_normal, RFC2035.jpeg_chroma_quantizer_normal);
   
    JPEGQTable qtable_luma = new JPEGQTable(ArrayUtility.byteArrayToIntArray(lumQ));
    JPEGQTable qtable_chroma = new JPEGQTable(ArrayUtility.byteArrayToIntArray(chmQ));
    JPEGQTable[] result = {qtable_luma, qtable_chroma};
    return result;
  }
 
  private JPEGHuffmanTable[] createACHuffmanTables()
  {
    JPEGHuffmanTable acChm = new JPEGHuffmanTable(RFC2035.chm_ac_codelens, RFC2035.chm_ac_symbols);
    JPEGHuffmanTable acLum = new JPEGHuffmanTable(RFC2035.lum_ac_codelens, RFC2035.lum_ac_symbols);
    JPEGHuffmanTable[] result = {acLum, acChm};
    return result;
  }
 
  private JPEGHuffmanTable[] createDCHuffmanTables()
  {
    JPEGHuffmanTable dcChm = new JPEGHuffmanTable(RFC2035.chm_dc_codelens, RFC2035.chm_dc_symbols);
    JPEGHuffmanTable dcLum = new JPEGHuffmanTable(RFC2035.lum_dc_codelens, RFC2035.lum_dc_symbols);
    JPEGHuffmanTable[] result = {dcLum, dcChm};
    return result;
  }
 
  @Override
  public int doProcess(Buffer buffer)
  {
    long timestamp = buffer.getTimeStamp();
    if ( null == currentFrame )
    {
      currentFrame = new JPEGRTPFrame(timestamp);
    }
   
    if ( timestamp < currentFrame.timestamp ) // drop packets for older frames
    {
      if (TRACE) logger.fine("JPEGRTPRenderer: dropping packet ts=" + timestamp);
    }
    else if ( timestamp > currentFrame.timestamp ) // packets of a new frame
    {
      if (TRACE) logger.fine("JPEGRTPRenderer: dropping current frame ts=" + currentFrame.timestamp + ", got new packet ts=" + timestamp);
     
      currentFrame.clear(timestamp);
      currentFrame.add(buffer);
    }
    else // packet for the current frame
    {
      currentFrame.add(buffer);
    }
   
    if ( currentFrame.isComplete() )
    {
      byte[] data = currentFrame.getData();
      ///dump(data, data.length);
     
      ///if (TRACE) logger.fine("JPEGRTPRenderer: frame complete ts=" + currentFrame.timestamp + " data len=" + data.length);
      currentFrame = null;
     
      try
      {
        ByteArrayInputStream in = new ByteArrayInputStream(data);
        ImageInputStream stream = ImageIO.createImageInputStream(in);
       
        decoder.setInput(stream, false, false);
        param.setDestination(itsImage);
        decoder.read(0, param);
       
        component.setImage(itsImage);
       
        stream.close();
        in.close();

        // mgodehardt: will measure the real framerate
        long currentTimestamp = System.nanoTime();
        if ( -1 == lastTimestamp )
        {
          lastTimestamp = currentTimestamp;
        }
       
        framesProcessed++;
       
        if ( (currentTimestamp - lastTimestamp) > 1000000000L )
        {
          float diffTime = (float)(currentTimestamp - lastTimestamp) / 1000000L;
          frameRate = (float)framesProcessed * (1000.0f / diffTime);
         
          framesProcessed = 0;
          lastTimestamp = currentTimestamp;
        }
      }
      catch ( Exception ex )
      {
        ex.printStackTrace();
      }
    }
   
    return BUFFER_PROCESSED_OK;
  }
 
  private void dump(byte[] data, int length)
  {
   
    int index = 0;
    while ( index < length )
    {
      String aString = "";
      for (int i=0; i<16; i++)
      {
        String s = Integer.toHexString(data[index++] & 0xFF);
        aString += (s.length() < 2) ? ("0" + s) : s;
        aString += " ";
       
        if ( index >= length )
        {
          break;
        }
      }
      System.out.println(aString);
    }
    System.out.println(" ");
  }
 
  private class JPEGRTPFrame
  {
    // container
    public JPEGRTPFrame firstItem;
    public long         timestamp;
    public boolean      hasRTPMarker;
    public int          count;
    public int          dataLength;
   
    // item
    public JPEGRTPFrame nextItem;
    public Buffer       itemData;
    public long         fragmentOffset;
   
    byte[] jpegHeader =
    {
      (byte)0xff, (byte)0xd8,                                     // SOI
      (byte)0xff, (byte)0xe0,                                     // APP0
      (byte)0x00, (byte)0x10,
      (byte)0x4a, (byte)0x46, (byte)0x49, (byte)0x46, (byte)0x00, // JFIF
      (byte)0x01, (byte)0x02,                                     // version 1.2
      (byte)0x00,                                                 // No units, aspect ratio only specified
      (byte)0x00, (byte)0x01,                                     // Integer horizontal pixel density
      (byte)0x00, (byte)0x01,                                     // Integer vertical pixel density
      (byte)0x00, (byte)0x00,                                     // thumbnail width and height
      (byte)0xff, (byte)0xc0,                                     // SOF0
      (byte)0x00, (byte)0x11,
      (byte)0x08,
      (byte)0x00, (byte)0x90,                                     // height
      (byte)0x00, (byte)0xb0,                                     // width
      (byte)0x03,                                                 // 3 components
      (byte)0x01, (byte)0x22, (byte)0x00,                         // comp #1
      (byte)0x02, (byte)0x11, (byte)0x01,                         // comp #2
      (byte)0x03, (byte)0x11, (byte)0x01,                         // comp #3
      (byte)0xff, (byte)0xda,
      (byte)0x00, (byte)0x0c,
      (byte)0x03,
      (byte)0x01, (byte)0x00,
      (byte)0x02, (byte)0x11,
      (byte)0x03, (byte)0x11,
      (byte)0x00, (byte)0x3f, (byte)0x00
    };
   
    public JPEGRTPFrame(long timestamp)
    {
      this.timestamp = timestamp;
    }
   
    public JPEGRTPFrame(Buffer buffer)
    {
      itemData = buffer;
    }
   
    public byte[] getData()
    {
      byte[] frame = new byte[jpegHeader.length + dataLength + 2];
      System.arraycopy(jpegHeader, 0, frame, 0, jpegHeader.length);
     
      JPEGRTPFrame aItem = firstItem;
      long expectedFragmentOffset = 0;
      int offset = jpegHeader.length;
     
      // copy data from packets into frame
      while ( aItem != null )
      {
        aItem = firstItem;
        while ( aItem != null )
        {
          if ( aItem.fragmentOffset == expectedFragmentOffset )
          {
            break;
          }
          aItem = aItem.nextItem;
        }
       
        if ( null != aItem )
        {
          int len = aItem.itemData.getLength() - 8;
         
          byte[] data = (byte[])aItem.itemData.getData();
          System.arraycopy(data, aItem.itemData.getOffset() + 8, frame, offset, len);
         
          offset += len;
          expectedFragmentOffset += len;
        }
      }
     
      byte[] packetData = (byte[])firstItem.itemData.getData();
     
      // insert width and height
      int width = packetData[firstItem.itemData.getOffset() + 6] << 3;
      int height = packetData[firstItem.itemData.getOffset() + 7] << 3;
     
      frame[25] = (byte)((height >> 8) & 0xFF);
      frame[26] = (byte)(height & 0xFF);

      frame[27] = (byte)((width >> 8) & 0xFF);
      frame[28] = (byte)(width & 0xFF);
     
      // add EOI
      frame[frame.length - 2] = (byte)0xFF;
      frame[frame.length - 1] = (byte)0xD9;
     
      // init tables
      int q = packetData[firstItem.itemData.getOffset() + 5];
      if ( null == decoder )
      {
        initDecoder(q);
      }
     
      // did the quality change
      if ( (quality != -1) && (q != quality) )
      {
        initDecoder(q);
      }
      quality = q;
     
      if ( null == itsImage )
      {
        itsImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
      }
     
      return frame;
    }
   
    public boolean isComplete()
    {
      if ( hasRTPMarker )
      {
        JPEGRTPFrame aItem = firstItem;
        long expectedFragmentOffset = 0;
        dataLength = 0;
       
        // check if packets are continous
        while ( aItem != null )
        {
          aItem = firstItem;
          while ( aItem != null )
          {
            if ( aItem.fragmentOffset == expectedFragmentOffset )
            {
              break;
            }
            aItem = aItem.nextItem;
          }
         
          if ( null != aItem )
          {
            int len = aItem.itemData.getLength() - 8;
            dataLength += len;
           
            if ( (aItem.itemData.getFlags() & Buffer.FLAG_RTP_MARKER) > 0 )
            {
              return true;
            }
           
            expectedFragmentOffset += len;
          }
        }
      }
      return false;
    }
   
    public void clear(long timestamp)
    {
      firstItem = null;
      this.timestamp = timestamp;
      hasRTPMarker = false;
      count = 0;
      dataLength = 0;
    }
   
    public void add(Buffer buffer)
    {
      JPEGRTPFrame aNewItem = new JPEGRTPFrame((Buffer)buffer.clone());
     
      if ( (buffer.getFlags() & Buffer.FLAG_RTP_MARKER) > 0 )
      {
        hasRTPMarker = true;
      }
      count++;
     
      if ( null == firstItem )
      {
        firstItem = aNewItem;
      }
      else
      {
        JPEGRTPFrame aItem = firstItem;
        while ( aItem.nextItem != null )
        {
          aItem = aItem.nextItem;
        }
       
        aItem.nextItem = aNewItem;
      }
     
      byte[] data = (byte[])buffer.getData();
     
      aNewItem.fragmentOffset = 0;
      for (int i=0; i<3; i++)
      {
        aNewItem.fragmentOffset <<= 8;
        aNewItem.fragmentOffset += data[i + 13] & 0xff;
      }
     
      ///System.out.println(">>> added packet seq=" + buffer.getSequenceNumber() + " len=" + buffer.getLength() + " ofs=" + aNewItem.fragmentOffset);
      ///dump((byte[])buffer.getData(), buffer.getLength() + buffer.getOffset());
    }
  }

    private class VideoFrameRateControl implements FrameRateControl, Owned
    {
        public Object getOwner()
    {
      return JPEGRTPRenderer.this;
    }
       
        public float getFrameRate()
        {
            return frameRate;
        }
       
        public float setFrameRate(float newFrameRate)
        {
            return -1;
        }
       
        public float getMaxSupportedFrameRate()
        {
            return -1;
        }
       
        public float getPreferredFrameRate()
        {
            return -1;
        }
       
        public java.awt.Component getControlComponent()
        {
            return null;
        }
    }
}
TOP

Related Classes of net.sf.fmj.media.renderer.video.JPEGRTPRenderer

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.