Back to project page streamvid.
The source code is released under:
GNU General Public License
If you think the Android project streamvid listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.
/* * Copyright (C) 2011-2014 GUIGUI Simon, fyhertz@gmail.com * /*ww w. j ava 2s .co m*/ * 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; import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.concurrent.CountDownLatch; import net.majorkernelpanic.streaming.audio.AudioQuality; import net.majorkernelpanic.streaming.audio.AudioStream; import net.majorkernelpanic.streaming.exceptions.CameraInUseException; import net.majorkernelpanic.streaming.exceptions.ConfNotSupportedException; import net.majorkernelpanic.streaming.exceptions.InvalidSurfaceException; import net.majorkernelpanic.streaming.exceptions.StorageUnavailableException; import net.majorkernelpanic.streaming.gl.SurfaceView; import net.majorkernelpanic.streaming.rtsp.RtspClient; import net.majorkernelpanic.streaming.video.VideoQuality; import net.majorkernelpanic.streaming.video.VideoStream; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; /** * You should instantiate this class with the {@link SessionBuilder}.<br /> * This is the class you will want to use to stream audio and or video to some peer using RTP.<br /> * * It holds a {@link VideoStream} and a {@link AudioStream} together and provides * syncronous and asyncrounous functions to start and stop those steams. * You should implement a callback interface {@link Callback} to receive notifications and error reports.<br /> * * If you want to stream to a RTSP server, you will need an instance of this class and hand it to a {@link RtspClient}. * * If you don't use the RTSP protocol, you will still need to send a session description to the receiver * for him to be able to decode your audio/video streams. You can obtain this session description by calling * {@link #configure()} or {@link #syncConfigure()} to configure the session with its parameters * (audio samplingrate, video resolution) and then {@link Session#getSessionDescription()}.<br /> * * See the example 2 here: https://github.com/fyhertz/libstreaming-examples to * see an example of how to get a SDP.<br /> * * See the example 3 here: https://github.com/fyhertz/libstreaming-examples to * see an example of how to stream to a RTSP server.<br /> * */ public class Session { public final static String TAG = "Session"; public final static int STREAM_VIDEO = 0x01; public final static int STREAM_AUDIO = 0x00; /** Some app is already using a camera (Camera.open() has failed). */ public final static int ERROR_CAMERA_ALREADY_IN_USE = 0x00; /** The phone may not support some streaming parameters that you are using (bit rate, frame rate...s). */ public final static int ERROR_CONFIGURATION_NOT_SUPPORTED = 0x01; /** * The internal storage of the phone is not ready. * Libstreaming tried to store a test file on the sdcard but couldn't. * See H264Stream and AACStream to find out why libstreaming would want to something like that. */ public final static int ERROR_STORAGE_NOT_READY = 0x02; /** The phone has no flash. */ public final static int ERROR_CAMERA_HAS_NO_FLASH = 0x03; /** The supplied SurfaceView is not a valid surface, or has not been created yet. */ public final static int ERROR_INVALID_SURFACE = 0x04; /** * The destination set with {@link Session#setDestination(String)} could not be resolved. * May mean that the phone has no access to the internet, or that the DNS server could not * resolved the host name. */ public final static int ERROR_UNKNOWN_HOST = 0x05; /** * Some other error occured ! */ public final static int ERROR_OTHER = 0x06; private String mOrigin; private String mDestination; private int mTimeToLive = 64; private long mTimestamp; private AudioStream mAudioStream = null; private VideoStream mVideoStream = null; private Callback mCallback; private Handler mMainHandler; private static CountDownLatch sSignal; private static Handler sHandler; static { // Creates the Thread that will be used when asynchronous methods of a Session are called sSignal = new CountDownLatch(1); new HandlerThread("net.majorkernelpanic.streaming.Session"){ @Override protected void onLooperPrepared() { sHandler = new Handler(); sSignal.countDown(); } }.start(); } /** * Creates a streaming session that can be customized by adding tracks. */ public Session() { long uptime = System.currentTimeMillis(); mMainHandler = new Handler(Looper.getMainLooper()); mTimestamp = (uptime/1000)<<32 & (((uptime-((uptime/1000)*1000))>>32)/1000); // NTP timestamp mOrigin = "127.0.0.1"; // Me make sure that we won't send Runnables to a non existing thread try { sSignal.await(); } catch (InterruptedException e) {} } /** * The callback interface you need to implement to get some feedback * Those will be called from the UI thread. */ public interface Callback { /** * Called periodically to inform you on the bandwidth * consumption of the streams when streaming. */ public void onBitrareUpdate(long bitrate); /** Called when some error occurs. */ public void onSessionError(int reason, int streamType, Exception e); /** * Called when the previw of the {@link VideoStream} * has correctly been started. * If an error occurs while starting the preview, * {@link Callback#onSessionError(int, int, Exception)} will be * called instead of {@link Callback#onPreviewStarted()}. */ public void onPreviewStarted(); /** * Called when the session has correctly been configured * after calling {@link Session#configure()}. * If an error occurs while configuring the {@link Session}, * {@link Callback#onSessionError(int, int, Exception)} will be * called instead of {@link Callback#onSessionConfigured()}. */ public void onSessionConfigured(); /** * Called when the streams of the session have correctly been started. * If an error occurs while starting the {@link Session}, * {@link Callback#onSessionError(int, int, Exception)} will be * called instead of {@link Callback#onSessionStarted()}. */ public void onSessionStarted(); /** Called when the stream of the session have been stopped. */ public void onSessionStopped(); } /** You probably don't need to use that directly, use the {@link SessionBuilder}. */ void addAudioTrack(AudioStream track) { removeAudioTrack(); mAudioStream = track; } /** You probably don't need to use that directly, use the {@link SessionBuilder}. */ void addVideoTrack(VideoStream track) { removeVideoTrack(); mVideoStream = track; } /** You probably don't need to use that directly, use the {@link SessionBuilder}. */ void removeAudioTrack() { if (mAudioStream != null) { mAudioStream.stop(); mAudioStream = null; } } /** You probably don't need to use that directly, use the {@link SessionBuilder}. */ void removeVideoTrack() { if (mVideoStream != null) { mVideoStream.stopPreview(); mVideoStream = null; } } /** Returns the underlying {@link AudioStream} used by the {@link Session}. */ public AudioStream getAudioTrack() { return mAudioStream; } /** Returns the underlying {@link VideoStream} used by the {@link Session}. */ public VideoStream getVideoTrack() { return mVideoStream; } /** * Sets the callback interface that will be called by the {@link Session}. * @param callback The implementation of the {@link Callback} interface */ public void setCallback(Callback callback) { mCallback = callback; } /** * The origin address of the session. * It appears in the sessionn description. * @param origin The origin address */ public void setOrigin(String origin) { mOrigin = origin; } /** * The destination address for all the streams of the session. * Changes will be taken into account the next time you start the session. * @param destination The destination address */ public void setDestination(String destination) { mDestination = destination; } /** * Set the TTL of all packets sent during the session. * Changes will be taken into account the next time you start the session. * @param ttl The Time To Live */ public void setTimeToLive(int ttl) { mTimeToLive = ttl; } /** * Sets the configuration of the stream. You can call this method at any time * and changes will take effect next time you call {@link #configure()}. * @param quality Quality of the stream */ public void setVideoQuality(VideoQuality quality) { if (mVideoStream != null) { mVideoStream.setVideoQuality(quality); } } /** * Sets a Surface to show a preview of recorded media (video). * You can call this method at any time and changes will take effect next time you call {@link #start()} or {@link #startPreview()}. */ public void setSurfaceView(final SurfaceView view) { sHandler.post(new Runnable() { @Override public void run() { if (mVideoStream != null) { mVideoStream.setSurfaceView(view); } } }); } /** * Sets the orientation of the preview. You can call this method at any time * and changes will take effect next time you call {@link #configure()}. * @param orientation The orientation of the preview */ public void setPreviewOrientation(int orientation) { if (mVideoStream != null) { mVideoStream.setPreviewOrientation(orientation); } } /** * Sets the configuration of the stream. You can call this method at any time * and changes will take effect next time you call {@link #configure()}. * @param quality Quality of the stream */ public void setAudioQuality(AudioQuality quality) { if (mAudioStream != null) { mAudioStream.setAudioQuality(quality); } } /** * Returns the {@link Callback} interface that was set with * {@link #setCallback(Callback)} or null if none was set. */ public Callback getCallback() { return mCallback; } /** * Returns a Session Description that can be stored in a file or sent to a client with RTSP. * @return The Session Description. * @throws IllegalStateException Thrown when {@link #setDestination(String)} has never been called. */ public String getSessionDescription() { StringBuilder sessionDescription = new StringBuilder(); if (mDestination==null) { throw new IllegalStateException("setDestination() has not been called !"); } sessionDescription.append("v=0\r\n"); // TODO: Add IPV6 support sessionDescription.append("o=- "+mTimestamp+" "+mTimestamp+" IN IP4 "+mOrigin+"\r\n"); sessionDescription.append("s=Unnamed\r\n"); sessionDescription.append("i=N/A\r\n"); sessionDescription.append("c=IN IP4 "+mDestination+"\r\n"); // t=0 0 means the session is permanent (we don't know when it will stop) sessionDescription.append("t=0 0\r\n"); sessionDescription.append("a=recvonly\r\n"); // Prevents two different sessions from using the same peripheral at the same time if (mAudioStream != null) { sessionDescription.append(mAudioStream.getSessionDescription()); sessionDescription.append("a=control:trackID="+0+"\r\n"); } if (mVideoStream != null) { sessionDescription.append(mVideoStream.getSessionDescription()); sessionDescription.append("a=control:trackID="+1+"\r\n"); } return sessionDescription.toString(); } /** Returns the destination set with {@link #setDestination(String)}. */ public String getDestination() { return mDestination; } /** Returns an approximation of the bandwidth consumed by the session in bit per seconde. */ public long getBitrate() { long sum = 0; if (mAudioStream != null) sum += mAudioStream.getBitrate(); if (mVideoStream != null) sum += mVideoStream.getBitrate(); return sum; } /** Indicates if a track is currently running. */ public boolean isStreaming() { if ( (mAudioStream!=null && mAudioStream.isStreaming()) || (mVideoStream!=null && mVideoStream.isStreaming()) ) return true; else return false; } /** * Configures all streams of the session. **/ public void configure() { sHandler.post(new Runnable() { @Override public void run() { try { syncConfigure(); } catch (Exception e) {}; } }); } /** * Does the same thing as {@link #configure()}, but in a syncronous manner. * Throws exceptions in addition to calling a callback * {@link Callback#onSessionError(int, int, Exception)} when * an error occurs. **/ public void syncConfigure() throws CameraInUseException, StorageUnavailableException, ConfNotSupportedException, InvalidSurfaceException, RuntimeException, IOException { for (int id=0;id<2;id++) { Stream stream = id==0 ? mAudioStream : mVideoStream; if (stream!=null && !stream.isStreaming()) { try { stream.configure(); } catch (CameraInUseException e) { postError(ERROR_CAMERA_ALREADY_IN_USE , id, e); throw e; } catch (StorageUnavailableException e) { postError(ERROR_STORAGE_NOT_READY , id, e); throw e; } catch (ConfNotSupportedException e) { postError(ERROR_CONFIGURATION_NOT_SUPPORTED , id, e); throw e; } catch (InvalidSurfaceException e) { postError(ERROR_INVALID_SURFACE , id, e); throw e; } catch (IOException e) { postError(ERROR_OTHER, id, e); throw e; } catch (RuntimeException e) { postError(ERROR_OTHER, id, e); throw e; } } } postSessionConfigured(); } /** * Asyncronously starts all streams of the session. **/ public void start() { sHandler.post(new Runnable() { @Override public void run() { try { syncStart(); } catch (Exception e) {} } }); } /** * Starts a stream in a syncronous manner. * Throws exceptions in addition to calling a callback. * @param id The id of the stream to start **/ public void syncStart(int id) throws CameraInUseException, StorageUnavailableException, ConfNotSupportedException, InvalidSurfaceException, UnknownHostException, IOException { Stream stream = id==0 ? mAudioStream : mVideoStream; if (stream!=null && !stream.isStreaming()) { try { InetAddress destination = InetAddress.getByName(mDestination); stream.setTimeToLive(mTimeToLive); stream.setDestinationAddress(destination); stream.start(); if (getTrack(1-id) == null || getTrack(1-id).isStreaming()) { postSessionStarted(); } if (getTrack(1-id) == null || !getTrack(1-id).isStreaming()) { sHandler.post(mUpdateBitrate); } } catch (UnknownHostException e) { postError(ERROR_UNKNOWN_HOST, id, e); throw e; } catch (CameraInUseException e) { postError(ERROR_CAMERA_ALREADY_IN_USE , id, e); throw e; } catch (StorageUnavailableException e) { postError(ERROR_STORAGE_NOT_READY , id, e); throw e; } catch (ConfNotSupportedException e) { postError(ERROR_CONFIGURATION_NOT_SUPPORTED , id, e); throw e; } catch (InvalidSurfaceException e) { postError(ERROR_INVALID_SURFACE , id, e); throw e; } catch (IOException e) { postError(ERROR_OTHER, id, e); throw e; } catch (RuntimeException e) { postError(ERROR_OTHER, id, e); throw e; } } } /** * Does the same thing as {@link #start()}, but in a syncronous manner. * Throws exceptions in addition to calling a callback. **/ public void syncStart() throws CameraInUseException, StorageUnavailableException, ConfNotSupportedException, InvalidSurfaceException, UnknownHostException, IOException { syncStart(1); try { syncStart(0); } catch (RuntimeException e) { syncStop(1); throw e; } catch (IOException e) { syncStop(1); throw e; } } /** Stops all existing streams. */ public void stop() { sHandler.post(new Runnable() { @Override public void run() { syncStop(); } }); } /** * Stops one stream in a syncronous manner. * @param id The id of the stream to stop **/ private void syncStop(final int id) { Stream stream = id==0 ? mAudioStream : mVideoStream; if (stream!=null) { stream.stop(); } } /** Stops all existing streams in a syncronous manner. */ public void syncStop() { syncStop(0); syncStop(1); postSessionStopped(); } public void startPreview() { sHandler.post(new Runnable() { @Override public void run() { if (mVideoStream != null) { try { mVideoStream.configure(); mVideoStream.startPreview(); postPreviewStarted(); } catch (CameraInUseException e) { postError(ERROR_CAMERA_ALREADY_IN_USE , STREAM_VIDEO, e); } catch (ConfNotSupportedException e) { postError(ERROR_CONFIGURATION_NOT_SUPPORTED , STREAM_VIDEO, e); } catch (InvalidSurfaceException e) { postError(ERROR_INVALID_SURFACE , STREAM_VIDEO, e); } catch (RuntimeException e) { postError(ERROR_OTHER, STREAM_VIDEO, e); } catch (StorageUnavailableException e) { postError(ERROR_STORAGE_NOT_READY, STREAM_VIDEO, e); } catch (IOException e) { postError(ERROR_OTHER, STREAM_VIDEO, e); } } } }); } public void stopPreview() { sHandler.post(new Runnable() { @Override public void run() { if (mVideoStream != null) { mVideoStream.stopPreview(); } } }); } public void switchCamera() { sHandler.post(new Runnable() { @Override public void run() { if (mVideoStream != null) { try { mVideoStream.switchCamera(); postPreviewStarted(); } catch (CameraInUseException e) { postError(ERROR_CAMERA_ALREADY_IN_USE , STREAM_VIDEO, e); } catch (ConfNotSupportedException e) { postError(ERROR_CONFIGURATION_NOT_SUPPORTED , STREAM_VIDEO, e); } catch (InvalidSurfaceException e) { postError(ERROR_INVALID_SURFACE , STREAM_VIDEO, e); } catch (IOException e) { postError(ERROR_OTHER, STREAM_VIDEO, e); } catch (RuntimeException e) { postError(ERROR_OTHER, STREAM_VIDEO, e); } } } }); } public int getCamera() { return mVideoStream != null ? mVideoStream.getCamera() : 0; } public void toggleFlash() { sHandler.post(new Runnable() { @Override public void run() { if (mVideoStream != null) { try { mVideoStream.toggleFlash(); } catch (RuntimeException e) { postError(ERROR_CAMERA_HAS_NO_FLASH, STREAM_VIDEO, e); } } } }); } /** Deletes all existing tracks & release associated resources. */ public void release() { removeAudioTrack(); removeVideoTrack(); sHandler.getLooper().quit(); } private void postPreviewStarted() { mMainHandler.post(new Runnable() { @Override public void run() { if (mCallback != null) { mCallback.onPreviewStarted(); } } }); } private void postSessionConfigured() { mMainHandler.post(new Runnable() { @Override public void run() { if (mCallback != null) { mCallback.onSessionConfigured(); } } }); } private void postSessionStarted() { mMainHandler.post(new Runnable() { @Override public void run() { if (mCallback != null) { mCallback.onSessionStarted(); } } }); } private void postSessionStopped() { mMainHandler.post(new Runnable() { @Override public void run() { if (mCallback != null) { mCallback.onSessionStopped(); } } }); } private void postError(final int reason, final int streamType,final Exception e) { mMainHandler.post(new Runnable() { @Override public void run() { if (mCallback != null) { mCallback.onSessionError(reason, streamType, e); } } }); } private void postBitRate(final long bitrate) { mMainHandler.post(new Runnable() { @Override public void run() { if (mCallback != null) { mCallback.onBitrareUpdate(bitrate); } } }); } private Runnable mUpdateBitrate = new Runnable() { @Override public void run() { if (isStreaming()) { postBitRate(getBitrate()); sHandler.postDelayed(mUpdateBitrate, 500); } else { postBitRate(0); } } }; public boolean trackExists(int id) { if (id==0) return mAudioStream!=null; else return mVideoStream!=null; } public Stream getTrack(int id) { if (id==0) return mAudioStream; else return mVideoStream; } }