diff --git a/.classpath b/.classpath index 5b1771a..2aceb57 100644 --- a/.classpath +++ b/.classpath @@ -5,5 +5,6 @@ + diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml index c4c5eb8..59df3f4 100644 --- a/res/xml/preferences.xml +++ b/res/xml/preferences.xml @@ -52,23 +52,23 @@ android:key="http_server_enabled" android:summary="@string/settings23" android:title="@string/settings22" /> - + android:title="@string/settings24" /> - + android:title="@string/settings26" /> diff --git a/src/net/majorkernelpanic/http/TinyHttpServer.java b/src/net/majorkernelpanic/http/TinyHttpServer.java index e98dab7..d0e3c23 100644 --- a/src/net/majorkernelpanic/http/TinyHttpServer.java +++ b/src/net/majorkernelpanic/http/TinyHttpServer.java @@ -665,9 +665,13 @@ public void run() { } catch (HttpException e) { Log.e(TAG,"Unrecoverable HTTP protocol violation: " + e.getMessage()); } finally { - /*try { + try { + OutputStream sockOutOStream = socket.getOutputStream(); + sockOutOStream.write(new byte[0]); + sockOutOStream.flush(); socket.close(); - } catch (IOException e) {}*/ + } catch (IOException e) { + } try { this.conn.shutdown(); } catch (Exception ignore) {} diff --git a/src/net/majorkernelpanic/spydroid/SpydroidApplication.java b/src/net/majorkernelpanic/spydroid/SpydroidApplication.java index 8d82cef..bb07e34 100644 --- a/src/net/majorkernelpanic/spydroid/SpydroidApplication.java +++ b/src/net/majorkernelpanic/spydroid/SpydroidApplication.java @@ -34,7 +34,6 @@ import net.majorkernelpanic.streaming.SessionBuilder; import net.majorkernelpanic.streaming.video.VideoQuality; -import org.acra.ACRA; import org.acra.annotation.ReportsCrashes; import android.content.BroadcastReceiver; @@ -88,7 +87,7 @@ public void onCreate() { SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this); notificationEnabled = settings.getBoolean("notification_enabled", true); - + // On android 3.* AAC ADTS is not supported so we set the default encoder to AMR-NB, on android 4.* AAC is the default encoder audioEncoder = (Integer.parseInt(android.os.Build.VERSION.SDK)<14) ? SessionBuilder.AUDIO_AMRNB : SessionBuilder.AUDIO_AAC; audioEncoder = Integer.parseInt(settings.getString("audio_encoder", String.valueOf(audioEncoder))); @@ -137,14 +136,14 @@ else if (key.equals("video_bitrate")) { } else if (key.equals("audio_encoder") || key.equals("stream_audio")) { - audioEncoder = Integer.parseInt(sharedPreferences.getString("audio_encoder", "0")); + audioEncoder = Integer.parseInt(sharedPreferences.getString("audio_encoder", String.valueOf(audioEncoder))); SessionBuilder.getInstance().setAudioEncoder( audioEncoder ); if (!sharedPreferences.getBoolean("stream_audio", false)) SessionBuilder.getInstance().setAudioEncoder(0); } else if (key.equals("stream_video") || key.equals("video_encoder")) { - videoEncoder = Integer.parseInt(sharedPreferences.getString("video_encoder", "0")); + videoEncoder = Integer.parseInt(sharedPreferences.getString("video_encoder", String.valueOf(videoEncoder))); SessionBuilder.getInstance().setVideoEncoder( videoEncoder ); if (!sharedPreferences.getBoolean("stream_video", true)) SessionBuilder.getInstance().setVideoEncoder(0); diff --git a/src/net/majorkernelpanic/spydroid/ui/OptionsActivity.java b/src/net/majorkernelpanic/spydroid/ui/OptionsActivity.java index 42c4a0a..8c053b8 100644 --- a/src/net/majorkernelpanic/spydroid/ui/OptionsActivity.java +++ b/src/net/majorkernelpanic/spydroid/ui/OptionsActivity.java @@ -21,6 +21,7 @@ package net.majorkernelpanic.spydroid.ui; import static net.majorkernelpanic.http.TinyHttpServer.KEY_HTTPS_ENABLED; +import static net.majorkernelpanic.http.TinyHttpServer.KEY_HTTPS_PORT; import static net.majorkernelpanic.http.TinyHttpServer.KEY_HTTP_ENABLED; import static net.majorkernelpanic.http.TinyHttpServer.KEY_HTTP_PORT; @@ -37,6 +38,7 @@ import android.preference.Preference; import android.preference.PreferenceActivity; import android.preference.PreferenceManager; +import android.util.Log; @SuppressWarnings("deprecation") public class OptionsActivity extends PreferenceActivity { @@ -59,16 +61,15 @@ protected void onCreate(Bundle savedInstanceState){ final ListPreference videoBitrate = (ListPreference) findPreference("video_bitrate"); final ListPreference videoFramerate = (ListPreference) findPreference("video_framerate"); final CheckBoxPreference httpEnabled = (CheckBoxPreference) findPreference("http_server_enabled"); - //final CheckBoxPreference httpsEnabled = (CheckBoxPreference) findPreference("use_https"); + final CheckBoxPreference httpsEnabled = (CheckBoxPreference) findPreference("use_https"); final Preference httpPort = findPreference(KEY_HTTP_PORT); - //final Preference httpsPort = findPreference(KEY_HTTPS_PORT); + final Preference httpsPort = findPreference(KEY_HTTPS_PORT); boolean videoState = settings.getBoolean("stream_video", true); videoEncoder.setEnabled(videoState); videoResolution.setEnabled(videoState); videoBitrate.setEnabled(videoState); videoFramerate.setEnabled(videoState); - audioEncoder.setEnabled(settings.getBoolean("stream_audio", true)); videoEncoder.setValue(String.valueOf(mApplication.videoEncoder)); audioEncoder.setValue(String.valueOf(mApplication.audioEncoder)); @@ -80,6 +81,8 @@ protected void onCreate(Bundle savedInstanceState){ videoFramerate.setSummary(getString(R.string.settings1)+" "+videoFramerate.getValue()+"fps"); videoBitrate.setSummary(getString(R.string.settings2)+" "+videoBitrate.getValue()+"kbps"); + audioEncoder.setEnabled(settings.getBoolean("stream_audio", false)); + httpEnabled.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { public boolean onPreferenceChange(Preference preference, Object newValue) { boolean state = (Boolean)newValue; @@ -93,20 +96,20 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { editor.putBoolean(KEY_HTTPS_ENABLED, false); } else { // HTTP/HTTPS, it's one or the other - /**if (httpsEnabled.isChecked()) { + if (httpsEnabled.isChecked()) { editor.putBoolean(KEY_HTTPS_ENABLED, true); editor.putBoolean(KEY_HTTP_ENABLED, false); - } else {*/ + } else { editor.putBoolean(KEY_HTTPS_ENABLED, false); editor.putBoolean(KEY_HTTP_ENABLED, true); - //} + } } editor.commit(); return true; } }); - /*httpsEnabled.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + httpsEnabled.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { public boolean onPreferenceChange(Preference preference, Object newValue) { boolean state = (Boolean)newValue; Editor editor = settings.edit(); @@ -127,7 +130,7 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { editor.commit(); return true; } - });*/ + }); videoResolution.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { public boolean onPreferenceChange(Preference preference, Object newValue) { diff --git a/src/net/majorkernelpanic/spydroid/ui/SpydroidActivity.java b/src/net/majorkernelpanic/spydroid/ui/SpydroidActivity.java index ff9f4f1..59c957c 100644 --- a/src/net/majorkernelpanic/spydroid/ui/SpydroidActivity.java +++ b/src/net/majorkernelpanic/spydroid/ui/SpydroidActivity.java @@ -118,7 +118,7 @@ public void onCreate(Bundle savedInstanceState) { ((LinearLayout)findViewById(R.id.adcontainer)).removeAllViews(); } - // Prevents the phone to go to sleep mode + // Prevents the phone from going to sleep mode PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); mWakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, "net.majorkernelpanic.spydroid.wakelock"); @@ -233,20 +233,6 @@ private void quitSpydroid() { // Returns to home menu finish(); } - - private SurfaceHolder.Callback mHolderCallback = new SurfaceHolder.Callback() { - - @Override - public void surfaceDestroyed(SurfaceHolder holder) { - - } - - @Override - public void surfaceCreated(SurfaceHolder holder) {} - @Override - public void surfaceChanged(SurfaceHolder holder, int format, int width,int height) {} - - }; private ServiceConnection mRtspServiceConnection = new ServiceConnection() { diff --git a/src/net/majorkernelpanic/streaming/SessionBuilder.java b/src/net/majorkernelpanic/streaming/SessionBuilder.java index 2f88995..8df5c32 100644 --- a/src/net/majorkernelpanic/streaming/SessionBuilder.java +++ b/src/net/majorkernelpanic/streaming/SessionBuilder.java @@ -43,22 +43,22 @@ public class SessionBuilder { public final static String TAG = "SessionBuilder"; /** Can be used with {@link #setVideoEncoder}. */ - public final static int VIDEO_NONE = 0x00; + public final static int VIDEO_NONE = 0; /** Can be used with {@link #setVideoEncoder}. */ - public final static int VIDEO_H264 = 0x01; + public final static int VIDEO_H264 = 1; /** Can be used with {@link #setVideoEncoder}. */ - public final static int VIDEO_H263 = 0x02; + public final static int VIDEO_H263 = 2; /** Can be used with {@link #setAudioEncoder}. */ - public final static int AUDIO_NONE = 0x00; + public final static int AUDIO_NONE = 0; /** Can be used with {@link #setAudioEncoder}. */ - public final static int AUDIO_AMRNB = 0x03; + public final static int AUDIO_AMRNB = 3; /** Can be used with {@link #setAudioEncoder}. */ - public final static int AUDIO_AAC = 0x05; + public final static int AUDIO_AAC = 5; // Default configuration private VideoQuality mVideoQuality = VideoQuality.defaultVideoQualiy.clone(); diff --git a/src/net/majorkernelpanic/streaming/audio/AACStream.java b/src/net/majorkernelpanic/streaming/audio/AACStream.java index 2c78331..bb2340a 100644 --- a/src/net/majorkernelpanic/streaming/audio/AACStream.java +++ b/src/net/majorkernelpanic/streaming/audio/AACStream.java @@ -27,6 +27,7 @@ import net.majorkernelpanic.streaming.exceptions.AACNotSupportedException; import net.majorkernelpanic.streaming.rtp.AACADTSPacketizer; +import android.annotation.SuppressLint; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.media.MediaRecorder; @@ -76,6 +77,7 @@ public class AACStream extends AudioStream { private int mProfile, mSamplingRateIndex, mChannel, mConfig; private SharedPreferences mSettings = null; + @SuppressLint("InlinedApi") public AACStream() throws IOException { super(); @@ -83,20 +85,32 @@ public AACStream() throws IOException { setAudioSource(MediaRecorder.AudioSource.CAMCORDER); + if (!AACStreamingSupported()) { + Log.e(TAG,"AAC ADTS not supported on this phone"); + throw new AACNotSupportedException(); + } + try { Field name = MediaRecorder.OutputFormat.class.getField("AAC_ADTS"); - Log.d(TAG,"AAC ADTS seems to be supported: AAC_ADTS="+name.getInt(null)); setOutputFormat(name.getInt(null)); - } catch (Exception e) { - Log.e(TAG,"AAC ADTS not supported on this phone"); - throw new AACNotSupportedException(); } + catch (Exception ignore) {} setAudioEncoder(MediaRecorder.AudioEncoder.AAC); setAudioSamplingRate(16000); } + private static boolean AACStreamingSupported() { + if (Integer.parseInt(android.os.Build.VERSION.SDK)<14) return false; + try { + MediaRecorder.OutputFormat.class.getField("AAC_ADTS"); + return true; + } catch (Exception e) { + return false; + } + } + /** * Some data (the actual sampling rate) needs to be stored once {@link #generateSessionDescription()} is called. * @param prefs The SharedPreferences that will be used to store the sampling rate diff --git a/src/net/majorkernelpanic/streaming/rtcp/SenderReport.java b/src/net/majorkernelpanic/streaming/rtcp/SenderReport.java index 3b0c906..b453d69 100644 --- a/src/net/majorkernelpanic/streaming/rtcp/SenderReport.java +++ b/src/net/majorkernelpanic/streaming/rtcp/SenderReport.java @@ -38,7 +38,6 @@ public class SenderReport { private byte[] buffer = new byte[MTU]; private int ssrc, port = -1; private int octetCount = 0, packetCount = 0; - private long ntp = 0; public SenderReport() throws IOException { @@ -79,6 +78,17 @@ public void send() throws IOException { usock.send(upack); } + /** Sends the RTCP packet over the network. */ + public void send(long ntpts, long rtpts) throws IOException { + long hb = ntpts/1000000000; + long lb = ( ( ntpts - hb*1000000000 ) * 4294967296L )/1000000000; + setLong(hb, 8, 12); + setLong(lb, 12, 16); + setLong(rtpts, 16, 20); + upack.setLength(28); + usock.send(upack); + } + /** * Updates the number of packets sent, and the total amount of data sent. * @param length The length of the packet @@ -97,18 +107,8 @@ public void setRtpTimestamp(long ts) { /** Sets the NTP timestamp of the sender report. */ public void setNtpTimestamp(long ts) { - ntp = ts; - long hb = ntp/1000000000; - long lb = ( ( ntp - hb*1000000000 ) * 4294967296L )/1000000000; - setLong(hb, 8, 12); - setLong(lb, 12, 16); - } - - /** Updates the NTP timestamp of the sender report. */ - public void updateNtpTimestamp(long delta) { - ntp += delta; - long hb = ntp/1000000000; - long lb = ( ( ntp - hb*1000000000 ) * 4294967296L )/1000000000; + long hb = ts/1000000000; + long lb = ( ( ts - hb*1000000000 ) * 4294967296L )/1000000000; setLong(hb, 8, 12); setLong(lb, 12, 16); } diff --git a/src/net/majorkernelpanic/streaming/rtp/AACADTSPacketizer.java b/src/net/majorkernelpanic/streaming/rtp/AACADTSPacketizer.java index 8a6f145..79a3170 100644 --- a/src/net/majorkernelpanic/streaming/rtp/AACADTSPacketizer.java +++ b/src/net/majorkernelpanic/streaming/rtp/AACADTSPacketizer.java @@ -43,7 +43,6 @@ public class AACADTSPacketizer extends AbstractPacketizer implements Runnable { private Thread t; private int samplingRate = 8000; - private Statistics stats = new Statistics(); public AACADTSPacketizer() throws IOException { super(); @@ -79,10 +78,8 @@ public void run() { // Adts header fields that we need to parse boolean protection; int frameLength, sum, length, nbau, nbpk; - long oldtime = System.nanoTime(), now = oldtime, delta = 5000, measured, lastmeasured = 5000, expected, interval = 0; + long oldtime = System.nanoTime(), now = oldtime, measured = 0, lastmeasured = 5000, expected = 0; - stats.init(1024*1000000000/samplingRate); - try { while (!Thread.interrupted()) { @@ -118,11 +115,11 @@ public void run() { socket.updateTimestamp(ts); // We send one RTCP Sender Report every 5 secs - if (delta>5000) { - delta = 0; - report.setNtpTimestamp(now); - report.setRtpTimestamp(ts); - report.send(); + if (intervalBetweenReports>0) { + if (delta>=intervalBetweenReports) { + delta = 0; + report.send(now,ts); + } } sum = 0; @@ -161,9 +158,7 @@ public void run() { // We wait a little to avoid sending to many packets too quickly now = System.nanoTime(); - interval = now-oldtime; - measured = interval/1000000; - stats.push(interval); + measured = (now-oldtime)/1000000; delta += measured; oldtime = now; expected = nbau*1024*1000 / (nbpk*samplingRate); diff --git a/src/net/majorkernelpanic/streaming/rtp/AMRNBPacketizer.java b/src/net/majorkernelpanic/streaming/rtp/AMRNBPacketizer.java index 7e68abc..5a71923 100644 --- a/src/net/majorkernelpanic/streaming/rtp/AMRNBPacketizer.java +++ b/src/net/majorkernelpanic/streaming/rtp/AMRNBPacketizer.java @@ -66,7 +66,7 @@ public void stop() { public void run() { - int frameLength, frameType, delta = 10000; + int frameLength, frameType; long now = System.nanoTime(), oldtime = now, measured; long expected = 20, lastmeasured = 10000, intervalNs = 0; @@ -115,11 +115,11 @@ public void run() { Thread.sleep( 2*expected/3-measured ); } - if (delta>5000) { - delta = 0; - report.setNtpTimestamp(now); - report.setRtpTimestamp(ts); - report.send(); + if (intervalBetweenReports>0) { + if (delta>=intervalBetweenReports) { + delta = 0; + report.send(now,ts); + } } send(rtphl+1+AMR_FRAME_HEADER_LENGTH+frameLength); diff --git a/src/net/majorkernelpanic/streaming/rtp/AbstractPacketizer.java b/src/net/majorkernelpanic/streaming/rtp/AbstractPacketizer.java index 1264213..4e1962c 100644 --- a/src/net/majorkernelpanic/streaming/rtp/AbstractPacketizer.java +++ b/src/net/majorkernelpanic/streaming/rtp/AbstractPacketizer.java @@ -40,7 +40,8 @@ abstract public class AbstractPacketizer { protected SenderReport report = null; protected InputStream is = null; protected byte[] buffer; - protected long ts = 0; + + protected long ts = 0, intervalBetweenReports = 5000, delta = 0; public AbstractPacketizer() throws IOException { int ssrc = new Random().nextInt(); @@ -89,6 +90,16 @@ public void setDestination(InetAddress dest, int rtpPort, int rtcpPort) { report.setDestination(dest, rtcpPort); } + /** + * Sets the temporal interval between two RTCP Sender Reports. + * Default interval is set to 5 secondes. + * Set 0 to disable RTCP. + * @param interval The interval in milliseconds + */ + public void setSenderReportsInterval(long interval) { + intervalBetweenReports = interval; + } + public abstract void start() throws IOException; public abstract void stop(); diff --git a/src/net/majorkernelpanic/streaming/rtp/H263Packetizer.java b/src/net/majorkernelpanic/streaming/rtp/H263Packetizer.java index acadbbc..5abbb2c 100644 --- a/src/net/majorkernelpanic/streaming/rtp/H263Packetizer.java +++ b/src/net/majorkernelpanic/streaming/rtp/H263Packetizer.java @@ -64,7 +64,7 @@ public void stop() { public void run() { long time, duration = 0; - int i = 0, j = 0, tr, delta = 10000; + int i = 0, j = 0, tr; boolean firstFragment = true; // This will skip the MPEG4 header if this step fails we can't stream anything :( @@ -106,11 +106,11 @@ public void run() { if (j>0) { // We send one RTCP Sender Report every 5 secs delta += duration/1000000; - if (delta>5000 && duration/1000000>10) { - delta = 0; - report.setRtpTimestamp(ts); - report.setNtpTimestamp(System.nanoTime()); - report.send(); + if (intervalBetweenReports>0) { + if (delta>=intervalBetweenReports && duration/1000000>10) { + delta = 0; + report.send(System.nanoTime(),ts); + } } // We have found the end of the frame stats.push(duration); diff --git a/src/net/majorkernelpanic/streaming/rtp/H264Packetizer.java b/src/net/majorkernelpanic/streaming/rtp/H264Packetizer.java index 3235416..58095e0 100644 --- a/src/net/majorkernelpanic/streaming/rtp/H264Packetizer.java +++ b/src/net/majorkernelpanic/streaming/rtp/H264Packetizer.java @@ -66,7 +66,8 @@ public void stop() { public void run() { - long duration = 0, oldtime = 0, delta = 10000, sum1 = 0, sum2 = 0; + long duration = 0, oldtime = 0, sum = 0, start = 0, drift = 0, now, count = 0; + boolean initoffset = false; // This will skip the MPEG4 header if this step fails we can't stream anything :( try { @@ -82,6 +83,8 @@ public void run() { return; } + start = System.nanoTime(); + // We read a NAL units from the input stream and we send them try { while (!Thread.interrupted()) { @@ -90,22 +93,38 @@ public void run() { oldtime = System.nanoTime(); send(); duration = System.nanoTime() - oldtime; - sum2 += duration; - // We send one RTCP Sender Report every 5 secs delta += duration/1000000; - if (delta>5000) { - delta = 0; - report.setRtpTimestamp(ts); - report.setNtpTimestamp(System.nanoTime()); - report.send(); - //Log.d(TAG, "sum1: "+sum1/1000000+" sum2: "+sum2/1000000); + if (intervalBetweenReports>0) { + if (delta>=intervalBetweenReports) { + // We send a Sender Report + report.send(oldtime+duration,ts); + } } - // Calculates the average duration of a NAL unit - stats.push(duration); + count += duration/1000000; + if (count>=7000) { + // We estimate the drift of the steam + now = System.nanoTime(); + if (!initoffset || (now - start < 0)) { + sum = (now - start); + initoffset = true; + } + drift = (now - start) - sum; + count = 0; + //Log.d(TAG, "sum1: "+sum/1000000+" sum2: "+(now-start)/1000000+" drift: "+drift/1000000); + } + + + if (drift!=0) { + // Compensates if the stream is drifting + stats.push(duration+drift); + drift = 0; + } else stats.push(duration); + // Computes the average duration of a NAL unit delay = stats.average(); - sum1 += delay; + sum += delay; + //Log.d(TAG,"duration: "+duration+" delay: "+delay); } } catch (IOException e) { diff --git a/src/net/majorkernelpanic/streaming/rtsp/UriParser.java b/src/net/majorkernelpanic/streaming/rtsp/UriParser.java index a5aa2be..a0cddd4 100644 --- a/src/net/majorkernelpanic/streaming/rtsp/UriParser.java +++ b/src/net/majorkernelpanic/streaming/rtsp/UriParser.java @@ -44,8 +44,7 @@ import android.hardware.Camera.CameraInfo; /** - * This class parses URIs and configures Sessions accordingly. - * It is used by the HttpServer and the Rtsp server of this package to configure Sessions + * This class parses URIs received by the RTSP server and configures a Session accordingly. */ public class UriParser { @@ -68,7 +67,7 @@ public static Session parse(String uri) throws IllegalStateException, IOExceptio if (params.size()>0) { builder.setAudioEncoder(AUDIO_NONE).setVideoEncoder(VIDEO_NONE); - + // Those parameters must be parsed first or else they won't necessarily be taken into account for (Iterator it = params.iterator();it.hasNext();) { NameValuePair param = it.next(); @@ -90,7 +89,7 @@ else if (param.getValue().equalsIgnoreCase("front")) } // MULTICAST -> the stream will be sent to a multicast group - // The default mutlicast address is 228.5.6.7, but the client can specify one + // The default mutlicast address is 228.5.6.7, but the client can specify another else if (param.getName().equalsIgnoreCase("multicast")) { if (param.getValue()!=null) { try { @@ -109,7 +108,7 @@ else if (param.getName().equalsIgnoreCase("multicast")) { } } - // UNICAST -> the client can use this so specify where he wants the stream to be sent + // UNICAST -> the client can use this to specify where he wants the stream to be sent else if (param.getName().equalsIgnoreCase("unicast")) { if (param.getValue()!=null) { try { @@ -156,7 +155,7 @@ else if (param.getName().equalsIgnoreCase("amrnb") || param.getName().equalsIgno else if (param.getName().equalsIgnoreCase("aac")) { builder.setAudioEncoder(AUDIO_AAC); } - + } } @@ -166,9 +165,9 @@ else if (param.getName().equalsIgnoreCase("aac")) { builder.setVideoEncoder(b.getVideoEncoder()); builder.setAudioEncoder(b.getAudioEncoder()); } - + return builder.build(); - + } } diff --git a/src/net/majorkernelpanic/streaming/video/VideoStream.java b/src/net/majorkernelpanic/streaming/video/VideoStream.java index 8baaf4e..2dc9328 100644 --- a/src/net/majorkernelpanic/streaming/video/VideoStream.java +++ b/src/net/majorkernelpanic/streaming/video/VideoStream.java @@ -204,7 +204,7 @@ public void prepare() throws IllegalStateException, IOException { // Resets the recorder in case it is in a bad state mMediaRecorder.reset(); - if (mSurfaceHolder == null) + if (mSurfaceHolder == null || mSurfaceHolder.getSurface() == null || !mSurfaceHolder.getSurface().isValid()) throw new IllegalStateException("Invalid surface holder !"); if (mCamera == null) {