From bd1f00664a321e151226ab62f64b17a932166206 Mon Sep 17 00:00:00 2001 From: fyhertz Date: Tue, 20 Aug 2013 10:39:28 -0400 Subject: [PATCH] Important bug fixes, for RtpSocket and AAC streaming --- .../streaming/SessionBuilder.java | 20 ++++- .../streaming/audio/AACStream.java | 21 ++--- .../streaming/audio/AMRNBStream.java | 2 +- .../streaming/audio/AudioQuality.java | 78 +++++++++++++++++++ .../streaming/audio/AudioStream.java | 33 ++++++-- .../streaming/rtp/AACADTSPacketizer.java | 45 +++++++---- .../streaming/rtp/AMRNBPacketizer.java | 9 +-- .../streaming/rtp/H263Packetizer.java | 2 +- .../streaming/rtp/H264Packetizer.java | 2 +- .../streaming/rtp/RtpSocket.java | 25 +++--- .../streaming/rtsp/UriParser.java | 7 +- .../streaming/video/VideoQuality.java | 18 +++-- .../streaming/video/VideoStream.java | 9 ++- 13 files changed, 206 insertions(+), 65 deletions(-) create mode 100644 src/net/majorkernelpanic/streaming/audio/AudioQuality.java diff --git a/src/net/majorkernelpanic/streaming/SessionBuilder.java b/src/net/majorkernelpanic/streaming/SessionBuilder.java index 8df5c32..f8749f2 100644 --- a/src/net/majorkernelpanic/streaming/SessionBuilder.java +++ b/src/net/majorkernelpanic/streaming/SessionBuilder.java @@ -25,6 +25,7 @@ import net.majorkernelpanic.streaming.audio.AACStream; import net.majorkernelpanic.streaming.audio.AMRNBStream; +import net.majorkernelpanic.streaming.audio.AudioQuality; import net.majorkernelpanic.streaming.audio.AudioStream; import net.majorkernelpanic.streaming.video.H263Stream; import net.majorkernelpanic.streaming.video.H264Stream; @@ -33,6 +34,7 @@ import android.content.Context; import android.hardware.Camera.CameraInfo; import android.preference.PreferenceManager; +import android.util.Log; import android.view.SurfaceHolder; /** @@ -61,7 +63,8 @@ public class SessionBuilder { public final static int AUDIO_AAC = 5; // Default configuration - private VideoQuality mVideoQuality = VideoQuality.defaultVideoQualiy.clone(); + private VideoQuality mVideoQuality = new VideoQuality(); + private AudioQuality mAudioQuality = new AudioQuality(); private Context mContext; private int mVideoEncoder = VIDEO_H263; private int mAudioEncoder = AUDIO_AMRNB; @@ -134,13 +137,14 @@ public Session build() throws IOException { if (session.getVideoTrack()!=null) { VideoStream video = session.getVideoTrack(); video.setFlashState(mFlash); - video.setVideoQuality(mVideoQuality); + video.setVideoQuality(VideoQuality.merge(mVideoQuality,video.getVideoQuality())); video.setPreviewDisplay(mSurfaceHolder); video.setDestinationPorts(5006); } if (session.getAudioTrack()!=null) { AudioStream audio = session.getAudioTrack(); + audio.setAudioQuality(AudioQuality.merge(mAudioQuality,audio.getAudioQuality())); audio.setDestinationPorts(5004); } @@ -180,6 +184,12 @@ public SessionBuilder setAudioEncoder(int encoder) { mAudioEncoder = encoder; return this; } + + /** Sets the audio quality. */ + public SessionBuilder setAudioQuality(AudioQuality quality) { + mAudioQuality = AudioQuality.merge(quality, mAudioQuality); + return this; + } /** Sets the default video encoder. */ public SessionBuilder setVideoEncoder(int encoder) { @@ -245,6 +255,11 @@ public int getVideoEncoder() { public VideoQuality getVideoQuality() { return mVideoQuality; } + + /** Returns the AudioQuality set with {@link #setAudioQuality(AudioQuality)}. */ + public AudioQuality getAudioQuality() { + return mAudioQuality; + } /** Returns the flash state set with {@link #setFlashEnabled(boolean)}. */ public boolean getFlashState() { @@ -273,6 +288,7 @@ public SessionBuilder clone() { .setCamera(mCamera) .setTimeToLive(mTimeToLive) .setAudioEncoder(mAudioEncoder) + .setAudioQuality(mAudioQuality) .setContext(mContext); } diff --git a/src/net/majorkernelpanic/streaming/audio/AACStream.java b/src/net/majorkernelpanic/streaming/audio/AACStream.java index a6bad34..83fcffa 100644 --- a/src/net/majorkernelpanic/streaming/audio/AACStream.java +++ b/src/net/majorkernelpanic/streaming/audio/AACStream.java @@ -41,9 +41,11 @@ public class AACStream extends AudioStream { public final static String TAG = "AACStream"; + + protected AudioQuality mQuality = new AudioQuality(32000,32000); /** MPEG-4 Audio Object Types supported by ADTS. **/ - private static final String[] sAudioObjectTypes = { + private static final String[] AUDIO_OBJECT_TYPES = { "NULL", // 0 "AAC Main", // 1 "AAC LC (Low Complexity)", // 2 @@ -52,7 +54,7 @@ public class AACStream extends AudioStream { }; /** There are 13 supported frequencies by ADTS. **/ - private static final int[] sADTSSamplingRates = { + public static final int[] ADTS_SAMPLING_RATES = { 96000, // 0 88200, // 1 64000, // 2 @@ -95,7 +97,7 @@ public AACStream() throws IOException { catch (Exception ignore) {} setAudioEncoder(MediaRecorder.AudioEncoder.AAC); - setAudioSamplingRate(16000); + setAudioSamplingRate(mQuality.samplingRate); } @@ -152,8 +154,8 @@ public String generateSessionDescription() throws IllegalStateException, IOExcep private void testADTS() throws IllegalStateException, IOException { if (mSettings!=null) { - if (mSettings.contains("aac-"+mSamplingRate)) { - String[] s = mSettings.getString("aac-"+mSamplingRate, "").split(","); + if (mSettings.contains("aac-"+mQuality.samplingRate)) { + String[] s = mSettings.getString("aac-"+mQuality.samplingRate, "").split(","); mActualSamplingRate = Integer.valueOf(s[0]); mConfig = Integer.valueOf(s[1]); mChannel = Integer.valueOf(s[2]); @@ -177,7 +179,8 @@ private void testADTS() throws IllegalStateException, IOException { mMediaRecorder.setOutputFormat(mOutputFormat); mMediaRecorder.setAudioEncoder(mAudioEncoder); mMediaRecorder.setAudioChannels(1); - mMediaRecorder.setAudioSamplingRate(mSamplingRate); + mMediaRecorder.setAudioSamplingRate(mQuality.samplingRate); + mMediaRecorder.setAudioEncodingBitRate(mQuality.bitRate); mMediaRecorder.setOutputFile(TESTFILE); mMediaRecorder.prepare(); mMediaRecorder.start(); @@ -208,14 +211,14 @@ private void testADTS() throws IllegalStateException, IOException { mSamplingRateIndex = (buffer[1]&0x3C) >> 2; mProfile = ( (buffer[1]&0xC0) >> 6 ) + 1 ; mChannel = (buffer[1]&0x01) << 2 | (buffer[2]&0xC0) >> 6 ; - mActualSamplingRate = sADTSSamplingRates[mSamplingRateIndex]; + mActualSamplingRate = ADTS_SAMPLING_RATES[mSamplingRateIndex]; // 5 bits for the object type / 4 bits for the sampling rate / 4 bits for the channel / padding mConfig = mProfile<<11 | mSamplingRateIndex<<7 | mChannel<<3; Log.i(TAG,"MPEG VERSION: " + ( (buffer[0]&0x08) >> 3 ) ); Log.i(TAG,"PROTECTION: " + (buffer[0]&0x01) ); - Log.i(TAG,"PROFILE: " + sAudioObjectTypes[ mProfile ] ); + Log.i(TAG,"PROFILE: " + AUDIO_OBJECT_TYPES[ mProfile ] ); Log.i(TAG,"SAMPLING FREQUENCY: " + mActualSamplingRate ); Log.i(TAG,"CHANNEL: " + mChannel ); @@ -223,7 +226,7 @@ private void testADTS() throws IllegalStateException, IOException { if (mSettings!=null) { Editor editor = mSettings.edit(); - editor.putString("aac-"+mSamplingRate, mActualSamplingRate+","+mConfig+","+mChannel); + editor.putString("aac-"+mQuality.samplingRate, mActualSamplingRate+","+mConfig+","+mChannel); editor.commit(); } diff --git a/src/net/majorkernelpanic/streaming/audio/AMRNBStream.java b/src/net/majorkernelpanic/streaming/audio/AMRNBStream.java index 9192e55..f293f65 100644 --- a/src/net/majorkernelpanic/streaming/audio/AMRNBStream.java +++ b/src/net/majorkernelpanic/streaming/audio/AMRNBStream.java @@ -49,7 +49,7 @@ public AMRNBStream() throws IOException { } setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); - setAudioSamplingRate(8000); + setAudioSamplingRate(mQuality.samplingRate); } diff --git a/src/net/majorkernelpanic/streaming/audio/AudioQuality.java b/src/net/majorkernelpanic/streaming/audio/AudioQuality.java new file mode 100644 index 0000000..13aa9af --- /dev/null +++ b/src/net/majorkernelpanic/streaming/audio/AudioQuality.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2011-2013 GUIGUI Simon, fyhertz@gmail.com + * + * This file is part of Spydroid (http://code.google.com/p/spydroid-ipcamera/) + * + * 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.audio; + +/** + * A class that represents the quality of an audio stream. + */ +public class AudioQuality { + + /** Default audio stream quality. */ + public final static AudioQuality DEFAULT_AUDIO_QUALITY = new AudioQuality(8000,32000); + + /** Represents a quality for a video stream. */ + public AudioQuality() {} + + /** + * Represents a quality for an audio stream. + * @param samplingRate The sampling rate + * @param bitRate The bitrate in bit per seconds + */ + public AudioQuality(int samplingRate, int bitRate) { + this.samplingRate = samplingRate; + this.bitRate = bitRate; + } + + public int samplingRate = 0; + public int bitRate = 0; + + public boolean equals(AudioQuality quality) { + if (quality==null) return false; + return (quality.samplingRate == this.samplingRate & + quality.bitRate == this.bitRate); + } + + public AudioQuality clone() { + return new AudioQuality(samplingRate, bitRate); + } + + public static AudioQuality parseQuality(String str) { + AudioQuality quality = new AudioQuality(0,0); + if (str != null) { + String[] config = str.split("-"); + try { + quality.bitRate = Integer.parseInt(config[0])*1000; // conversion to bit/s + quality.samplingRate = Integer.parseInt(config[1]); + } + catch (IndexOutOfBoundsException ignore) {} + } + return quality; + } + + public static AudioQuality merge(AudioQuality audioQuality, AudioQuality withAudioQuality) { + if (withAudioQuality != null && audioQuality != null) { + if (audioQuality.samplingRate==0) audioQuality.samplingRate = withAudioQuality.samplingRate; + if (audioQuality.bitRate==0) audioQuality.bitRate = withAudioQuality.bitRate; + } + return audioQuality; + } + +} diff --git a/src/net/majorkernelpanic/streaming/audio/AudioStream.java b/src/net/majorkernelpanic/streaming/audio/AudioStream.java index e49d484..4977bdd 100644 --- a/src/net/majorkernelpanic/streaming/audio/AudioStream.java +++ b/src/net/majorkernelpanic/streaming/audio/AudioStream.java @@ -2,9 +2,10 @@ import java.io.IOException; -import android.media.MediaRecorder; - import net.majorkernelpanic.streaming.MediaStream; +import net.majorkernelpanic.streaming.video.VideoQuality; +import android.media.MediaRecorder; +import android.util.Log; /** * Don't use this class directly. @@ -14,7 +15,7 @@ public abstract class AudioStream extends MediaStream { protected int mAudioSource; protected int mOutputFormat; protected int mAudioEncoder; - protected int mSamplingRate; + protected AudioQuality mQuality = AudioQuality.DEFAULT_AUDIO_QUALITY.clone(); public void setAudioSource(int audioSource) { mAudioSource = audioSource; @@ -29,21 +30,43 @@ public void setAudioEncoder(int audioEncoder) { } public void setAudioSamplingRate(int samplingRate) { - mSamplingRate = samplingRate; + mQuality.samplingRate = samplingRate; } + public void setAudioQuality(AudioQuality quality) { + mQuality = quality; + } + + /** + * Returns the quality of the stream. + */ + public AudioQuality getAudioQuality() { + return mQuality; + } + + /** + * Sets the encoding bit rate for the stream. + * @param bitRate bit rate in bit per second + */ + public void setAudioEncodingBitRate(int bitRate) { + mQuality.bitRate = bitRate; + } + @Override protected void encodeWithMediaRecorder() throws IOException { // We need a local socket to forward data output by the camera to the packetizer createSockets(); + Log.v(TAG,"Requested audio with "+mQuality.bitRate/1000+"kbps"+" at "+mQuality.samplingRate/1000+"kHz"); + mMediaRecorder = new MediaRecorder(); mMediaRecorder.setAudioSource(mAudioSource); mMediaRecorder.setOutputFormat(mOutputFormat); mMediaRecorder.setAudioEncoder(mAudioEncoder); mMediaRecorder.setAudioChannels(1); - mMediaRecorder.setAudioSamplingRate(mSamplingRate); + mMediaRecorder.setAudioSamplingRate(mQuality.samplingRate); + mMediaRecorder.setAudioEncodingBitRate(mQuality.bitRate); // We write the ouput of the camera in a local socket instead of a file ! // This one little trick makes streaming feasible quiet simply: data from the camera diff --git a/src/net/majorkernelpanic/streaming/rtp/AACADTSPacketizer.java b/src/net/majorkernelpanic/streaming/rtp/AACADTSPacketizer.java index 687599e..e7263ea 100644 --- a/src/net/majorkernelpanic/streaming/rtp/AACADTSPacketizer.java +++ b/src/net/majorkernelpanic/streaming/rtp/AACADTSPacketizer.java @@ -22,6 +22,8 @@ import java.io.IOException; +import net.majorkernelpanic.streaming.audio.AACStream; +import android.os.SystemClock; import android.util.Log; /** @@ -83,8 +85,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; + int frameLength, sum, length, nbau, nbpk, samplingRateIndex, profile; + long oldtime = SystemClock.elapsedRealtime(), now = oldtime; byte[] header = new byte[8]; try { @@ -95,11 +97,13 @@ public void run() { if ( (is.read()&0xFF) == 0xFF ) { header[1] = (byte) is.read(); if ( (header[1]&0xF0) == 0xF0) break; + } else { + Log.e(TAG,"SYNC"); } } // Parse adts header (ADTS packets start with a 7 or 9 byte long header) - is.read(header,2,5); + fill(header, 2, 5); // The protection bit indicates whether or not the header contains the two extra bytes protection = (header[1]&0x01)>0 ? true : false; @@ -117,17 +121,23 @@ public void run() { // Read CRS if any if (!protection) is.read(header,0,2); + samplingRate = AACStream.ADTS_SAMPLING_RATES[(header[2]&0x3C) >> 2]; + profile = ( (header[2]&0xC0) >> 6 ) + 1 ; + // We update the RTP timestamp - ts += 1024*1000000/samplingRate; //stats.average(); + ts += 1024L*1000000000L/samplingRate; //stats.average(); // We send one RTCP Sender Report every 5 secs + now = SystemClock.elapsedRealtime(); if (intervalBetweenReports>0) { - if (delta>=intervalBetweenReports) { - delta = 0; - report.send(now,ts*samplingRate/1000000); + if (now-oldtime>=intervalBetweenReports) { + oldtime = now; + report.send(System.nanoTime(),ts*samplingRate/1000000000L); } } + //Log.d(TAG,"frameLength: "+frameLength+" protection: "+protection+" p: "+profile+" sr: "+samplingRate); + sum = 0; while (sum 13bits for AU-size and 3bits for AU-Index / AU-Index-delta @@ -159,16 +169,9 @@ public void run() { buffer[rtphl+3] &= 0xF8; buffer[rtphl+3] |= 0x00; - //Log.d(TAG,"frameLength: "+frameLength+" protection: "+protection+ " length: "+length); - send(rtphl+4+length); } - - // We wait a little to avoid sending to many packets too quickly - now = System.nanoTime(); - delta += (now-oldtime)/1000000; - oldtime = now; } } catch (IOException e) { @@ -182,4 +185,16 @@ public void run() { } + private int fill(byte[] buffer, int offset,int length) throws IOException { + int sum = 0, len; + while (sum0) { if (delta>=intervalBetweenReports) { delta = 0; - report.send(now,ts*samplingRate/1000000); + report.send(now,ts*samplingRate/1000000000L); } } @@ -129,11 +129,8 @@ public void run() { } - private int fill(byte[] buffer, int offset,int length) throws IOException { - int sum = 0, len; - while (sum 1000) { + if (mTime - mOldTime > 1500) { mBitRate = mOctetCount*8000/(mTime-mOldTime); mOctetCount = 0; mOldTime = mTime; @@ -204,7 +204,7 @@ private void updateSequence() { **/ public void updateTimestamp(long timestamp) { mTimestamps[mBufferIn] = timestamp; - setLong(mBuffers[mBufferIn], timestamp*mClock/1000000, 4, 8); + setLong(mBuffers[mBufferIn], timestamp*mClock/1000000000L, 4, 8); } /** Sets the marker in the RTP packet. */ @@ -215,8 +215,7 @@ public void markNextPacket() { /** The Thread sends the packets in the FIFO one by one at a constant rate. */ @Override public void run() { - Statistics stats = new Statistics(10,100); - long delta = 0; + Statistics stats = new Statistics(20,330); try { // Caches mCacheSize milliseconds of the stream in the FIFO. Thread.sleep(mCacheSize); @@ -224,16 +223,16 @@ public void run() { if (mOldTimestamp != 0) { // We use our knowledge of the clock rate of the stream and the difference between to timestamp to // compute the temporal length of the packet. - stats.push(mTimestamps[mBufferOut]-mOldTimestamp); + if (mTimestamps[mBufferOut]-mOldTimestamp>=0) stats.push(mTimestamps[mBufferOut]-mOldTimestamp); // We ensure that packets are sent at a constant and suitable rate no matter how the RtpSocket is used. long d = stats.average()/1000000; - //Log.d(TAG,"delay: "+d); + //Log.d(TAG,"delay: "+d+" d: "+(mTimestamps[mBufferOut]-mOldTimestamp)/1000000); Thread.sleep(d); - delta += mTimestamps[mBufferOut]-mOldTimestamp; - if (delta>500000000) { + /*delta += mTimestamps[mBufferOut]-mOldTimestamp; + if (delta>500000000 || delta<0) { Log.d(TAG,"permits: "+mBufferCommitted.availablePermits()); delta = 0; - } + }*/ } mOldTimestamp = mTimestamps[mBufferOut]; mSocket.send(mPackets[mBufferOut]); @@ -283,7 +282,7 @@ public void push(long value) { initoffset = true; } value -= (now - start) - duration; - Log.d(TAG, "sum1: "+duration/1000000+" sum2: "+(now-start)/1000000+" drift: "+((now-start)-duration)/1000000+" v: "+value/1000000); + //Log.d(TAG, "sum1: "+duration/1000000+" sum2: "+(now-start)/1000000+" drift: "+((now-start)-duration)/1000000+" v: "+value/1000000); } if (c<20) { // We ignore the first 20 measured values because they may not be accurate diff --git a/src/net/majorkernelpanic/streaming/rtsp/UriParser.java b/src/net/majorkernelpanic/streaming/rtsp/UriParser.java index a0cddd4..3afe575 100644 --- a/src/net/majorkernelpanic/streaming/rtsp/UriParser.java +++ b/src/net/majorkernelpanic/streaming/rtsp/UriParser.java @@ -36,6 +36,7 @@ import net.majorkernelpanic.streaming.Session; import net.majorkernelpanic.streaming.SessionBuilder; +import net.majorkernelpanic.streaming.audio.AudioQuality; import net.majorkernelpanic.streaming.video.VideoQuality; import org.apache.http.NameValuePair; @@ -148,12 +149,14 @@ else if (param.getName().equalsIgnoreCase("h263")) { // AMR else if (param.getName().equalsIgnoreCase("amrnb") || param.getName().equalsIgnoreCase("amr")) { - builder.setAudioEncoder(AUDIO_AMRNB); + AudioQuality quality = AudioQuality.parseQuality(param.getValue()); + builder.setAudioQuality(quality).setAudioEncoder(AUDIO_AMRNB); } // AAC else if (param.getName().equalsIgnoreCase("aac")) { - builder.setAudioEncoder(AUDIO_AAC); + AudioQuality quality = AudioQuality.parseQuality(param.getValue()); + builder.setAudioQuality(quality).setAudioEncoder(AUDIO_AAC); } } diff --git a/src/net/majorkernelpanic/streaming/video/VideoQuality.java b/src/net/majorkernelpanic/streaming/video/VideoQuality.java index c8e6292..2b1803a 100644 --- a/src/net/majorkernelpanic/streaming/video/VideoQuality.java +++ b/src/net/majorkernelpanic/streaming/video/VideoQuality.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011 GUIGUI Simon, fyhertz@gmail.com + * Copyright (C) 2011-2013 GUIGUI Simon, fyhertz@gmail.com * * This file is part of Spydroid (http://code.google.com/p/spydroid-ipcamera/) * @@ -27,9 +27,9 @@ public class VideoQuality { /** Default video stream quality. */ - public final static VideoQuality defaultVideoQualiy = new VideoQuality(640,480,15,500000); + public final static VideoQuality DEFAULT_VIDEO_QUALITY = new VideoQuality(640,480,15,500000); - /** Represents a quality for a video stream. **/ + /** Represents a quality for a video stream. */ public VideoQuality() {} /** @@ -97,11 +97,13 @@ public static VideoQuality parseQuality(String str) { } public static VideoQuality merge(VideoQuality videoQuality, VideoQuality withVideoQuality) { - if (videoQuality.resX==0) videoQuality.resX = withVideoQuality.resX; - if (videoQuality.resY==0) videoQuality.resY = withVideoQuality.resY; - if (videoQuality.framerate==0) videoQuality.framerate = withVideoQuality.framerate; - if (videoQuality.bitrate==0) videoQuality.bitrate = withVideoQuality.bitrate; - if (videoQuality.orientation==90) videoQuality.orientation = withVideoQuality.orientation; + if (withVideoQuality != null && videoQuality != null) { + if (videoQuality.resX==0) videoQuality.resX = withVideoQuality.resX; + if (videoQuality.resY==0) videoQuality.resY = withVideoQuality.resY; + if (videoQuality.framerate==0) videoQuality.framerate = withVideoQuality.framerate; + if (videoQuality.bitrate==0) videoQuality.bitrate = withVideoQuality.bitrate; + if (videoQuality.orientation==90) videoQuality.orientation = withVideoQuality.orientation; + } return videoQuality; } diff --git a/src/net/majorkernelpanic/streaming/video/VideoStream.java b/src/net/majorkernelpanic/streaming/video/VideoStream.java index 3987925..fd12939 100644 --- a/src/net/majorkernelpanic/streaming/video/VideoStream.java +++ b/src/net/majorkernelpanic/streaming/video/VideoStream.java @@ -49,7 +49,7 @@ public abstract class VideoStream extends MediaStream { protected final static String TAG = "VideoStream"; - protected VideoQuality mQuality = VideoQuality.defaultVideoQualiy.clone(); + protected VideoQuality mQuality = VideoQuality.DEFAULT_VIDEO_QUALITY.clone(); protected SurfaceHolder.Callback mSurfaceHolderCallback = null; protected SurfaceHolder mSurfaceHolder = null; protected int mVideoEncoder, mCameraId = 0; @@ -246,6 +246,13 @@ public void setVideoQuality(VideoQuality videoQuality) { mQuality = videoQuality; } } + + /** + * Returns the quality of the stream. + */ + public VideoQuality getVideoQuality() { + return mQuality; + } /** * Modifies the videoEncoder of the stream. You can call this method at any time