Preview IP Cam on Android App with ExoPlayer

The Standard Implmentation via ExoPlayer

// Create a player instance.
ExoPlayer player = new ExoPlayer.Builder(context).build();
// Set the media item to be played.
player.setMediaItem(MediaItem.fromUri(rtspUri));
// Prepare the player.
player.prepare();

The More Customized Implementation via ExoPlayer

// Create an RTSP media source pointing to an RTSP uri.
MediaSource mediaSource =
    new RtspMediaSource.Factory()
        .createMediaSource(MediaItem.fromUri(rtspUri));
// Create a player instance.
ExoPlayer player = new ExoPlayer.Builder(context).build();
// Set the media source to be played.
player.setMediaSource(mediaSource);
// Prepare the player.
player.prepare();

Both of these implmentations work for no username/password RTSP link but not for RTSP with username/password.

Is the App crashing because of

  • No exception and retry authentication method inside Exo RTSP implmentation
  • No authentication method at all ?

And with unprotected RTSP then only there are minutes in latency.

  • Frame is not properlly decode/encode
  • Sound capturing seems to be fine also with some delay.

Why The App is Crashsing

By default, RtspMediaSource will use Java's standard socket factory (SocketFactory.getDefault()) to create connections to the remote endpoints. This behavior can be overridden using RtspMediaSource.Factory.setSocketFactory().

// Create an RTSP media source pointing to an RTSP uri and override the socket
// factory.
MediaSource mediaSource =
    new RtspMediaSource.Factory()
        .setSocketFactory(...)
        .createMediaSource(MediaItem.fromUri(rtspUri));

Did the UDP connection establish successfully ? Where Android app be able to fetch info from the camera ?

The errors seem to come from the RTSP response header of one of the camera

"OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, GET_PARAMETER, SET_PARAMETER,USER_CMD_SET"

RTSPResponse header seems to give users an extra method USER_CMD_SET

It seems like the bug is in this method RtspMessageUtil Method

  /**
   * Parses the RTSP PUBLIC header into a list of RTSP methods.
   *
   * @param publicHeader The PUBLIC header content, null if not available.
   * @return The list of supported RTSP methods, encoded in {@link RtspRequest.Method}, or an empty
   *     list if the PUBLIC header is null.
   */
  public static ImmutableList<Integer> parsePublicHeader(@Nullable String publicHeader) {
    if (publicHeader == null) {
      return ImmutableList.of();
    }

    ImmutableList.Builder<Integer> methodListBuilder = new ImmutableList.Builder<>();
    for (String method : Util.split(publicHeader, ",\\s?")) {
      methodListBuilder.add(parseMethodString(method));
    }
    return methodListBuilder.build();
  }

The Regular expression ",\s?" failed to split RTSP headers without space into an array of string.

Lets try override the RtspMessageUtil.parsePublicHeader to remove the bug. In order to modify our ExoPlayer2 library, we can add following line of code to settings.gradle file.

gradle.ext.exoplayerModulePrefix = 'exoplayer-'
apply from: file("/path_to_local_copy_of_exoplayer2/core_settings.gradle")

After that, any modifications to the local copy will be use to build our Android app.

The String Splitting does not seem to be the problems. The header string splitting works fine in isolation and split the Header String to an array of Methods.

However parseMethodString can not handle USER_CMD_SET and throw new IllegalArgumentException() and crash the application.

  private static @RtspRequest.Method int parseMethodString(String method) {
    switch (method) {
      case "ANNOUNCE":
        return METHOD_ANNOUNCE;
      case "DESCRIBE":
        return METHOD_DESCRIBE;
      case "GET_PARAMETER":
        return METHOD_GET_PARAMETER;
      case "OPTIONS":
        return METHOD_OPTIONS;
      case "PAUSE":
        return METHOD_PAUSE;
      case "PLAY":
        return METHOD_PLAY;
      case "PLAY_NOTIFY":
        return METHOD_PLAY_NOTIFY;
      case "RECORD":
        return METHOD_RECORD;
      case "REDIRECT":
        return METHOD_REDIRECT;
      case "SETUP":
        return METHOD_SETUP;
      case "SET_PARAMETER":
        return METHOD_SET_PARAMETER;
      case "TEARDOWN":
        return METHOD_TEARDOWN;
      default:
        throw new IllegalArgumentException();
    }
  }

Audio Sink Errors are causing high latency in our player for RTSP Link rtsp://192.168.0.101/EDFBE769E500A5CC5B5079DBED80BFFF&0 with no Authentication.

E/MediaCodecAudioRenderer: Audio sink error
      com.google.android.exoplayer2.audio.AudioSink$UnexpectedDiscontinuityException: Unexpected audio track timestamp discontinuity: expected 1000000220000, got 1000000019750
        at com.google.android.exoplayer2.audio.DefaultAudioSink.handleBuffer(DefaultAudioSink.java:990)
        at com.google.android.exoplayer2.audio.MediaCodecAudioRenderer.processOutputBuffer(MediaCodecAudioRenderer.java:703)
        at com.google.android.exoplayer2.mediacodec.MediaCodecRenderer.drainOutputBuffer(MediaCodecRenderer.java:1894)
        at com.google.android.exoplayer2.mediacodec.MediaCodecRenderer.render(MediaCodecRenderer.java:792)
        at com.google.android.exoplayer2.ExoPlayerImplInternal.doSomeWork(ExoPlayerImplInternal.java:989)
        at com.google.android.exoplayer2.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:490)
        at android.os.Handler.dispatchMessage(Handler.java:103)
        at android.os.Looper.loop(Looper.java:214)
        at android.os.HandlerThread.run(HandlerThread.java:67)

Playing the RTSP Stream rtsp://admin:admin@192.168.0.102:1935 directly from the other RTSP Android app with hardware configuration is good. However, still need to be able to control the Audio to stop echoing from happening.

When attempting to play the password protected RTSP rtsp://admin:123@192.168.0.100:554/onvif1 then we are running into this error

E/AndroidRuntime: FATAL EXCEPTION: ExoPlayer:Playback
    Process: com.mikedoconsulting.fog, PID: 18574
    java.lang.IllegalArgumentException
        at com.google.android.exoplayer2.source.rtsp.RtspMessageUtil.parseMethodString(RtspMessageUtil.java:284)
        at com.google.android.exoplayer2.source.rtsp.RtspMessageUtil.parsePublicHeader(RtspMessageUtil.java:387)
        at com.google.android.exoplayer2.source.rtsp.RtspClient$MessageListener.handleRtspResponse(RtspClient.java:594)
        at com.google.android.exoplayer2.source.rtsp.RtspClient$MessageListener.handleRtspMessage(RtspClient.java:507)
        at com.google.android.exoplayer2.source.rtsp.RtspClient$MessageListener.lambda$onRtspMessageReceived$0$com-google-android-exoplayer2-source-rtsp-RtspClient$MessageListener(RtspClient.java:500)
        at com.google.android.exoplayer2.source.rtsp.RtspClient$MessageListener$$ExternalSyntheticLambda0.run(Unknown Source:4)
        at android.os.Handler.handleCallback(Handler.java:883)
        at android.os.Handler.dispatchMessage(Handler.java:100)
        at android.os.Looper.loop(Looper.java:214)
        at android.os.HandlerThread.run(HandlerThread.java:67)

References

Open Positions
Lets have a brainstorming
session about your business
How about a 15 mins Conference Call
Submit
More About Dystill Vision around the Internet.