Android Open Source - spydroid-ipcamera Rtp Socket






From Project

Back to project page spydroid-ipcamera.

License

The source code is released under:

GNU General Public License

If you think the Android project spydroid-ipcamera listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/*
 * Copyright (C) 2011-2014 GUIGUI Simon, fyhertz@gmail.com
 * /*  w w w  .  ja  va 2s.  c om*/
 * This file is part of libstreaming (https://github.com/fyhertz/libstreaming)
 * 
 * Spydroid is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This source code 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 General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this source code; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package net.majorkernelpanic.streaming.rtp;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

import net.majorkernelpanic.streaming.rtcp.SenderReport;
import android.os.SystemClock;
import android.util.Log;

/**
 * A basic implementation of an RTP socket.
 * It implements a buffering mechanism, relying on a FIFO of buffers and a Thread.
 * That way, if a packetizer tries to send many packets too quickly, the FIFO will
 * grow and packets will be sent one by one smoothly.
 */
public class RtpSocket implements Runnable {

  public static final String TAG = "RtpSocket";

  public static final int RTP_HEADER_LENGTH = 12;
  public static final int MTU = 1300;

  private MulticastSocket mSocket;
  private DatagramPacket[] mPackets;
  private byte[][] mBuffers;
  private long[] mTimestamps;

  private SenderReport mReport;
  
  private Semaphore mBufferRequested, mBufferCommitted;
  private Thread mThread;

  private long mCacheSize;
  private long mClock = 0;
  private long mOldTimestamp = 0;
  private int mSsrc, mSeq = 0, mPort = -1;
  private int mBufferCount, mBufferIn, mBufferOut;
  private int mCount = 0;
  
  private AverageBitrate mAverageBitrate;

  /**
   * This RTP socket implements a buffering mechanism relying on a FIFO of buffers and a Thread.
   * @throws IOException
   */
  public RtpSocket() {
    
    mCacheSize = 00;
    mBufferCount = 300; // TODO: reajust that when the FIFO is full 
    mBuffers = new byte[mBufferCount][];
    mPackets = new DatagramPacket[mBufferCount];
    mReport = new SenderReport();
    mAverageBitrate = new AverageBitrate();
    
    resetFifo();

    for (int i=0; i<mBufferCount; i++) {

      mBuffers[i] = new byte[MTU];
      mPackets[i] = new DatagramPacket(mBuffers[i], 1);

      /*                   Version(2)  Padding(0)                     */
      /*                   ^      ^      Extension(0)            */
      /*                   |      |        ^                */
      /*                   | --------        |                */
      /*                   | |---------------------                */
      /*                   | ||  -----------------------> Source Identifier(0)  */
      /*                   | ||  |                        */
      mBuffers[i][0] = (byte) Integer.parseInt("10000000",2);

      /* Payload Type */
      mBuffers[i][1] = (byte) 96;

      /* Byte 2,3        ->  Sequence Number                   */
      /* Byte 4,5,6,7    ->  Timestamp                         */
      /* Byte 8,9,10,11  ->  Sync Source Identifier            */

    }

    try {
    mSocket = new MulticastSocket();
    } catch (Exception e) {
      throw new RuntimeException(e.getMessage());
    }
    
  }

  private void resetFifo() {
    mCount = 0;
    mBufferIn = 0;
    mBufferOut = 0;
    mTimestamps = new long[mBufferCount];
    mBufferRequested = new Semaphore(mBufferCount);
    mBufferCommitted = new Semaphore(0);
    mReport.reset();
    mAverageBitrate.reset();
  }
  
  /** Closes the underlying socket. */
  public void close() {
    mSocket.close();
  }

  /** Sets the SSRC of the stream. */
  public void setSSRC(int ssrc) {
    this.mSsrc = ssrc;
    for (int i=0;i<mBufferCount;i++) {
      setLong(mBuffers[i], ssrc,8,12);
    }
    mReport.setSSRC(mSsrc);
  }

  /** Returns the SSRC of the stream. */
  public int getSSRC() {
    return mSsrc;
  }

  /** Sets the clock frquency of the stream in Hz. */
  public void setClockFrequency(long clock) {
    mClock = clock;
  }

  /** Sets the size of the FIFO in ms. */
  public void setCacheSize(long cacheSize) {
    mCacheSize = cacheSize;
  }
  
  /** Sets the Time To Live of the UDP packets. */
  public void setTimeToLive(int ttl) throws IOException {
    mSocket.setTimeToLive(ttl);
  }

  /** Sets the destination address and to which the packets will be sent. */
  public void setDestination(InetAddress dest, int dport, int rtcpPort) {
    mPort = dport;
    for (int i=0;i<mBufferCount;i++) {
      mPackets[i].setPort(dport);
      mPackets[i].setAddress(dest);
    }
    mReport.setDestination(dest, rtcpPort);
  }

  public int getPort() {
    return mPort;
  }

  public int getLocalPort() {
    return mSocket.getLocalPort();
  }

  public SenderReport getRtcpSocket() {
    return mReport;
  }
  
  /** 
   * Returns an available buffer from the FIFO, it can then be modified. 
   * Call {@link #commitBuffer(int)} to send it over the network. 
   * @throws InterruptedException 
   **/
  public byte[] requestBuffer() throws InterruptedException {
    mBufferRequested.acquire();
    mBuffers[mBufferIn][1] &= 0x7F;
    return mBuffers[mBufferIn];
  }

  /** Puts the buffer back into the FIFO without sending the packet. */
  public void commitBuffer() throws IOException {

    if (mThread == null) {
      mThread = new Thread(this);
      mThread.start();
    }
    
    if (++mBufferIn>=mBufferCount) mBufferIn = 0;
    mBufferCommitted.release();

  }  
  
  /** Sends the RTP packet over the network. */
  public void commitBuffer(int length) throws IOException {
    updateSequence();
    mPackets[mBufferIn].setLength(length);

    mAverageBitrate.push(length);

    if (++mBufferIn>=mBufferCount) mBufferIn = 0;
    mBufferCommitted.release();

    if (mThread == null) {
      mThread = new Thread(this);
      mThread.start();
    }    
    
  }

  /** Returns an approximation of the bitrate of the RTP stream in bit per seconde. */
  public long getBitrate() {
    return mAverageBitrate.average();
  }

  /** Increments the sequence number. */
  private void updateSequence() {
    setLong(mBuffers[mBufferIn], ++mSeq, 2, 4);
  }

  /** 
   * Overwrites the timestamp in the packet.
   * @param timestamp The new timestamp in ns.
   **/
  public void updateTimestamp(long timestamp) {
    mTimestamps[mBufferIn] = timestamp;
    setLong(mBuffers[mBufferIn], (timestamp/100L)*(mClock/1000L)/10000L, 4, 8);
  }

  /** Sets the marker in the RTP packet. */
  public void markNextPacket() {
    mBuffers[mBufferIn][1] |= 0x80;
  }

  /** The Thread sends the packets in the FIFO one by one at a constant rate. */
  @Override
  public void run() {
    Statistics stats = new Statistics(50,3000);
    try {
      // Caches mCacheSize milliseconds of the stream in the FIFO.
      Thread.sleep(mCacheSize);
      long delta = 0;
      while (mBufferCommitted.tryAcquire(4,TimeUnit.SECONDS)) {
        if (mOldTimestamp != 0) {
          // We use our knowledge of the clock rate of the stream and the difference between two timestamps to
          // compute the time lapse that the packet represents.
          if ((mTimestamps[mBufferOut]-mOldTimestamp)>0) {
            stats.push(mTimestamps[mBufferOut]-mOldTimestamp);
            long d = stats.average()/1000000;
            //Log.d(TAG,"delay: "+d+" d: "+(mTimestamps[mBufferOut]-mOldTimestamp)/1000000);
            // We ensure that packets are sent at a constant and suitable rate no matter how the RtpSocket is used.
            if (mCacheSize>0) Thread.sleep(d);
          } else if ((mTimestamps[mBufferOut]-mOldTimestamp)<0) {
            Log.e(TAG, "TS: "+mTimestamps[mBufferOut]+" OLD: "+mOldTimestamp);
          }
          delta += mTimestamps[mBufferOut]-mOldTimestamp;
          if (delta>500000000 || delta<0) {
            //Log.d(TAG,"permits: "+mBufferCommitted.availablePermits());
            delta = 0;
          }
        }
        mReport.update(mPackets[mBufferOut].getLength(), System.nanoTime(),(mTimestamps[mBufferOut]/100L)*(mClock/1000L)/10000L);
        mOldTimestamp = mTimestamps[mBufferOut];
        if (mCount++>30) mSocket.send(mPackets[mBufferOut]);
        if (++mBufferOut>=mBufferCount) mBufferOut = 0;
        mBufferRequested.release();
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
    mThread = null;
    resetFifo();
  }

  private void setLong(byte[] buffer, long n, int begin, int end) {
    for (end--; end >= begin; end--) {
      buffer[end] = (byte) (n % 256);
      n >>= 8;
    }
  }

  /** 
   * Computes an average bit rate. 
   **/
  protected static class AverageBitrate {

    private final static long RESOLUTION = 200;
    
    private long mOldNow, mNow, mDelta;
    private long[] mElapsed, mSum;
    private int mCount, mIndex, mTotal;
    private int mSize;
    
    public AverageBitrate() {
      mSize = 5000/((int)RESOLUTION);
      reset();
    }
    
    public AverageBitrate(int delay) {
      mSize = delay/((int)RESOLUTION);
      reset();
    }
    
    public void reset() {
      mSum = new long[mSize];
      mElapsed = new long[mSize];
      mNow = SystemClock.elapsedRealtime();
      mOldNow = mNow;
      mCount = 0;
      mDelta = 0;
      mTotal = 0;
      mIndex = 0;
    }
    
    public void push(int length) {
      mNow = SystemClock.elapsedRealtime();
      if (mCount>0) {
        mDelta += mNow - mOldNow;
        mTotal += length;
        if (mDelta>RESOLUTION) {
          mSum[mIndex] = mTotal;
          mTotal = 0;
          mElapsed[mIndex] = mDelta;
          mDelta = 0;
          mIndex++;
          if (mIndex>=mSize) mIndex = 0;
        }
      }
      mOldNow = mNow;
      mCount++;
    }
    
    public int average() {
      long delta = 0, sum = 0;
      for (int i=0;i<mSize;i++) {
        sum += mSum[i];
        delta += mElapsed[i];
      }
      //Log.d(TAG, "Time elapsed: "+delta);
      return (int) (delta>0?8000*sum/delta:0);
    }
    
  }
  
  /** Computes the proper rate at which packets are sent. */
  protected static class Statistics {

    public final static String TAG = "Statistics";
    
    private int count=500, c = 0;
    private float m = 0, q = 0;
    private long elapsed = 0;
    private long start = 0;
    private long duration = 0;
    private long period = 6000000000L;
    private boolean initoffset = false;

    public Statistics(int count, long period) {
      this.count = count;
      this.period = period*1000000L; 
    }
    
    public void push(long value) {
      duration += value;
      elapsed += value;
      if (elapsed>period) {
        elapsed = 0;
        long now = System.nanoTime();
        if (!initoffset || (now - start < 0)) {
          start = now;
          duration = 0;
          initoffset = true;
        }
        value -= (now - start) - duration;
        //Log.d(TAG, "sum1: "+duration/1000000+" sum2: "+(now-start)/1000000+" drift: "+((now-start)-duration)/1000000+" v: "+value/1000000);
      }
      if (c<40) {
        // We ignore the first 40 measured values because they may not be accurate
        c++;
        m = value;
      } else {
        m = (m*q+value)/(q+1);
        if (q<count) q++;
      }
    }
    
    public long average() {
      long l = (long)m-2000000;
      return l>0 ? l : 0;
    }

  }

}




Java Source Code List

net.majorkernelpanic.http.ModAssetServer.java
net.majorkernelpanic.http.ModInternationalization.java
net.majorkernelpanic.http.ModSSL.java
net.majorkernelpanic.http.TinyHttpServer.java
net.majorkernelpanic.spydroid.SpydroidApplication.java
net.majorkernelpanic.spydroid.Utilities.java
net.majorkernelpanic.spydroid.api.CustomHttpServer.java
net.majorkernelpanic.spydroid.api.CustomRtspServer.java
net.majorkernelpanic.spydroid.api.RequestHandler.java
net.majorkernelpanic.spydroid.ui.AboutFragment.java
net.majorkernelpanic.spydroid.ui.HandsetFragment.java
net.majorkernelpanic.spydroid.ui.OptionsActivity.java
net.majorkernelpanic.spydroid.ui.PreviewFragment.java
net.majorkernelpanic.spydroid.ui.SpydroidActivity.java
net.majorkernelpanic.spydroid.ui.TabletFragment.java
net.majorkernelpanic.streaming.MediaStream.java
net.majorkernelpanic.streaming.SessionBuilder.java
net.majorkernelpanic.streaming.Session.java
net.majorkernelpanic.streaming.Stream.java
net.majorkernelpanic.streaming.audio.AACStream.java
net.majorkernelpanic.streaming.audio.AMRNBStream.java
net.majorkernelpanic.streaming.audio.AudioQuality.java
net.majorkernelpanic.streaming.audio.AudioStream.java
net.majorkernelpanic.streaming.exceptions.CameraInUseException.java
net.majorkernelpanic.streaming.exceptions.ConfNotSupportedException.java
net.majorkernelpanic.streaming.exceptions.InvalidSurfaceException.java
net.majorkernelpanic.streaming.exceptions.StorageUnavailableException.java
net.majorkernelpanic.streaming.gl.SurfaceManager.java
net.majorkernelpanic.streaming.gl.SurfaceView.java
net.majorkernelpanic.streaming.gl.TextureManager.java
net.majorkernelpanic.streaming.hw.CodecManager.java
net.majorkernelpanic.streaming.hw.EncoderDebugger.java
net.majorkernelpanic.streaming.hw.NV21Convertor.java
net.majorkernelpanic.streaming.mp4.MP4Config.java
net.majorkernelpanic.streaming.mp4.MP4Parser.java
net.majorkernelpanic.streaming.rtcp.SenderReport.java
net.majorkernelpanic.streaming.rtp.AACADTSPacketizer.java
net.majorkernelpanic.streaming.rtp.AACLATMPacketizer.java
net.majorkernelpanic.streaming.rtp.AMRNBPacketizer.java
net.majorkernelpanic.streaming.rtp.AbstractPacketizer.java
net.majorkernelpanic.streaming.rtp.H263Packetizer.java
net.majorkernelpanic.streaming.rtp.H264Packetizer.java
net.majorkernelpanic.streaming.rtp.MediaCodecInputStream.java
net.majorkernelpanic.streaming.rtp.RtpSocket.java
net.majorkernelpanic.streaming.rtsp.RtspClient.java
net.majorkernelpanic.streaming.rtsp.RtspServer.java
net.majorkernelpanic.streaming.rtsp.UriParser.java
net.majorkernelpanic.streaming.video.CodecManager.java
net.majorkernelpanic.streaming.video.H263Stream.java
net.majorkernelpanic.streaming.video.H264Stream.java
net.majorkernelpanic.streaming.video.VideoQuality.java
net.majorkernelpanic.streaming.video.VideoStream.java