rtsp-client-android

repository·master·Indexed 19 days ago

https://github.com/alexeyvasilyev/rtsp-client-android

A lightweight RTSP client library for Android designed for lag-critical applications such as drone surveillance or car rear-view cameras. It achieves near-zero lag by avoiding video buffering and utilizing Android's Low-Latency MediaCodec. The library provides UI components like RtspSurfaceView and RtspImageView for playback, as well as the RtspClient API for raw frame access without decoding.

Tokens
3.8K
Snippets
9
Records
15
Agent score
66%

What's inside rtsp-client-android

  1. How to achieve lowest possible latency

    master

    Latency in this library is split into two categories: Network and Video Decoder latency.

    Reducing Network Latency

    1. Use Ethernet: Connect both the Android device and the RTSP camera via Ethernet rather than WiFi.
    2. Lower Bitrate: Decrease the stream bitrate on the RTSP camera. Smaller frames transfer faster.

    Reducing Video Decoder Latency

    Decoder latency varies by device and stream (ranging from 20ms to 1200ms). To minimize it:

    1. Use Baseline Profile: Use the lowest possible H.264 video stream profile and level. The Baseline profile typically results in the lowest latency. You can check profile_idc and level_idc in the logs by enabling debug mode in the library.
    2. Minimize Reorder Frames: Ensure the max_num_reorder_frames parameter in the stream is set to 0.
    3. Experimental Feature: Use the experimentalUpdateSpsFrameWithLowLatencyParams feature to rewrite the configuration frame at runtime with low-latency parameters.
  2. Display video using RtspSurfaceView or RtspImageView

    master

    The easiest way to show a video stream is to use the provided UI components.

    • RtspSurfaceView (Recommended): Best performance and lowest battery usage. Use this if you just need to display the video. To capture bitmaps from this view, use PixelCopy.request.
    • RtspImageView: Use this if you need to frequently extract bitmaps for further processing (e.g., for AI/Computer Vision), as it offers better performance for bitmap retrieval than PixelCopy on a SurfaceView.

    XML Layout Setup

    Add either component to your layout file:

    <com.alexvas.rtsp.widget.RtspSurfaceView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/svVideo" />
    
    <com.alexvas.rtsp.widget.RtspImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/ivVideo" />

    Kotlin Implementation

    Initialize and start the stream using the URI, username, and password:

    val uri = Uri.parse("rtsps://10.0.1.3/test.sdp")
    val username = "admin"
    val password = "secret"
    
    // For RtspSurfaceView
    svVideo.init(uri, username, password)
    svVideo.start(
        requestVideo = true,
        requestAudio = true,
        requestApplication = false
    )
    
    // ... later
    svVideo.stop()
    val uri = Uri.parse("rtsps://10.0.1.3/test.sdp")
    val username = "admin"
    val password = "secret"
    svVideo.init(uri, username, password)
    svVideo.start(
        requestVideo = true,
        requestAudio = true,
        requestApplication = false)
    // ...
    svVideo.stop()
  3. Install rtsp-client-android via JitPack

    master

    To integrate the library into your Android project, add the JitPack repository to your allprojects block and include the dependency in your dependencies block in build.gradle.

    Note: Replace x.x.x with the desired version number.

    allprojects {
      repositories {
        maven { url 'https://jitpack.io' }
      }
    }
    dependencies {
      implementation 'com.github.alexeyvasilyev:rtsp-client-android:x.x.x'
    }
  4. Use RtspImageView for low-latency video playback

    master

    RtspImageView is a custom ImageView designed for low-latency RTSP stream playback using bitmaps. It manages the underlying RtspProcessor and video decoding lifecycle automatically.

    To use it, follow these steps:

    1. Initialize the view with an RTSP URI and optional credentials using init().
    2. Start the stream using start() by specifying which tracks (video, audio, application) to request.
    3. Use stop() to terminate the stream.

    You can also listen for individual bitmap frames via onRtspImageBitmapListener or monitor stream status and data via setStatusListener and setDataListener.

    // 1. Initialize the view (e.g., in an Activity or Fragment)
    val rtspImageView = findViewById<RtspImageView>(R.id.rtsp_image_view)
    rtspImageView.init(
        uri = Uri.parse("rtsp://your_stream_url"),
        username = "user",
        password = "pass"
    )
    
    // 2. Optional: Listen for raw bitmaps
    rtspImageView.onRtspImageBitmapListener = object : RtspImageView.RtspImageBitmapListener {
        override fun onRtspImageBitmapObtained(bitmap: Bitmap) {
            // Handle the raw bitmap frame
        }
    }
    
    // 3. Start playback
    rtspImageView.start(requestVideo = true, requestAudio = false, requestApplication = false)
    
    // 4. Stop playback when done
    // rtspImageView.stop()
  5. Use RtspClient for raw frame access (No Decoding)

    master

    If you need to obtain raw frames (e.g., for writing to an MP4 via a muxer) without using the built-in UI components, use the RtspClient API. You must implement RtspClient.RtspClientListener to handle incoming NAL units, audio samples, or application data.

    Implementation Example

    val rtspClientListener = object: RtspClient.RtspClientListener {
        override fun onRtspConnecting() {}
        override fun onRtspConnected(sdpInfo: SdpInfo) {}
        override fun onRtspVideoNalUnitReceived(data: ByteArray, offset: Int, length: Int, timestamp: Long) {
            // Send raw H264/H265 NAL unit to decoder
        }
        override fun onRtspAudioSampleReceived(data: ByteArray, offset: Int, length: Int, timestamp: Long) {
            // Send raw audio to decoder
        }
        override fun onRtspApplicationDataReceived(data: ByteArray, offset: Int, length: Int, timestamp: Long) {
            // Send raw application data to app specific parser
        }
        override fun onRtspDisconnected() {}
        override fun onRtspFailedUnauthorized() {
            Log.e(TAG, "RTSP failed unauthorized")
        }
        override fun onRtspFailed(message: String?) {
            Log.e(TAG, "RTSP failed with message '$message'")
        }
    }
    
    val uri = Uri.parse("rtsps://10.0.1.3/test.sdp")
    val username = "admin"
    val password = "secret"
    val stopped = new AtomicBoolean(false)
    
    // Create a socket (e.g., via NetUtils for SSL/TLS)
    val sslSocket = NetUtils.createSslSocketAndConnect(uri.getHost(), uri.getPort(), 5000)
    
    val rtspClient = RtspClient.Builder(sslSocket, uri.toString(), stopped, rtspClientListener)
        .requestVideo(true)
        .requestAudio(true)
        .withDebug(false)
        .withUserAgent("RTSP client")
        .withCredentials(username, password)
        .build()
    
    // Blocking call until 'stopped' becomes true or connection fails
    rtspClient.execute()
    
    NetUtils.closeSocket(sslSocket)
    val rtspClientListener = object: RtspClient.RtspClientListener {
        override fun onRtspConnecting() {}
        override fun onRtspConnected(sdpInfo: SdpInfo) {}
        override fun onRtspVideoNalUnitReceived(data: ByteArray, offset: Int, length: Int, timestamp: Long) {
            // Send raw H264/H265 NAL unit to decoder
        }
        override fun onRtspAudioSampleReceived(data: ByteArray, offset: Int, length: Int, timestamp: Long) {
            // Send raw audio to decoder
        }
        override fun onRtspApplicationDataReceived(data: ByteArray, offset: Int, length: Int, timestamp: Long) {
            // Send raw application data to app specific parser
        }
        override fun onRtspDisconnected() {}
        override fun onRtspFailedUnauthorized() {
            Log.e(TAG, "RTSP failed unauthorized");
        }
        override fun onRtspFailed(message: String?) {
            Log.e(TAG, "RTSP failed with message '$message'")
        }
    }
    
    val uri = Uri.parse("rtsps://10.0.1.3/test.sdp")
    val username = "admin"
    val password = "secret"
    val stopped = new AtomicBoolean(false)
    val sslSocket = NetUtils.createSslSocketAndConnect(uri.getHost(), uri.getPort(), 5000)
    
    val rtspClient = RtspClient.Builder(sslSocket, uri.toString(), stopped, rtspClientListener)
        .requestVideo(true)
        .requestAudio(true)
        .withDebug(false)
        .withUserAgent("RTSP client")
        .withCredentials(username, password)
        .build()
    // Blocking call until stopped variable is true or connection failed
    rtspClient.execute()
    
    NetUtils.closeSocket(sslSocket)
  6. Configure RtspProcessor for low latency

    master

    You can tune RtspProcessor to achieve the lowest possible latency by adjusting several properties:

    • videoDecoderType: Set to DecoderType.HARDWARE (default) or software.
    • experimentalUpdateSpsFrameWithLowLatencyParams: If set to true, the processor attempts to modify the SPS (Sequence Parameter Set) frame. It sets maxDecFrameBuffering to 1 and numReorderFrames to 0, which can reduce decoding latency by up to 2x on some hardware decoders.
    • videoRotation: Set the video rotation in degrees (0, 90, 180, 270). Note that not all hardware decoders support rotation.
    • videoFrameRateStabilization: Enables playback smoothing logic inside the video decoder to prevent jitter.
  7. Control stream playback with start() and stop()

    master

    Methods to manage the active connection and media request.

    start(requestVideo: Boolean, requestAudio: Boolean, requestApplication: Boolean) Starts the RTSP client. Use these flags to request specific tracks as defined in RFC 4566.

    stop() Stops the RTSP client and terminates the stream.

    // Request only video
    rtspImageView.start(requestVideo = true, requestAudio = false, requestApplication = false)
    
    // Stop the stream
    rtspImageView.stop()
  8. Handle RTSP events and raw data

    master

    Use the following listeners to react to stream lifecycle changes and access raw media data:

    RtspStatusListener

    Implement this to receive status updates on the UI thread:

    • onRtspStatusConnecting()
    • onRtspStatusConnected()
    • onRtspStatusDisconnecting()
    • onRtspStatusDisconnected()
    • onRtspStatusFailed(message: String?)
    • onRtspStatusFailedUnauthorized()
    • onRtspFrameSizeChanged(width: Int, height: Int)
    • onRtspFirstFrameRendered()

    RtspDataListener

    Implement this to intercept raw NAL units or audio samples (e.g., for recording):

    • onRtspDataVideoNalUnitReceived(data: ByteArray, offset: Int, length: Int, timestamp: Long)
    • onRtspDataAudioSampleReceived(data: ByteArray, offset: Int, length: Int, timestamp: Long)
    • onRtspDataApplicationDataReceived(data: ByteArray, offset: Int, length: Int, timestamp: Long)
  9. Configure RtspImageView properties

    master

    Exposed properties to adjust the behavior and metadata of the stream playback.

    • videoRotation: Int: Gets or sets the video rotation.
    • videoDecoderType: VideoDecodeThread.DecoderType: Gets or sets the type of video decoder to use.
    • debug: Boolean: Gets or sets whether debug logging is enabled.
    • statistics: Statistics: Provides real-time stream statistics (read-only).
  10. Initialize RtspImageView with init()

    master

    Configures the RTSP stream parameters. This must be called before start().

    Parameters:

    • uri: Uri: The RTSP stream URI.
    • username: String?: Optional username for authentication.
    • password: String?: Optional password for authentication.
    • userAgent: String?: Optional user agent string.
    • socketTimeout: Int?: Optional socket timeout in milliseconds. Defaults to RtspProcessor.DEFAULT_SOCKET_TIMEOUT if null.
    rtspImageView.init(
        uri = uri,
        username = "user",
        password = "pass",
        userAgent = "my-app",
        socketTimeout = 5000
    )