@@ -717,6 +717,8 @@ private void decodeFrame(Frame buffer) {
717717 }
718718
719719 private void createDecoder () {
720+ if (mDecoder != null ) return ; // already running — avoid double-init and native leak
721+
720722 Surface mVideo = mSurface .getHolder ().getSurface ();
721723 if (!mVideo .isValid ()) {
722724 return ;
@@ -854,6 +856,29 @@ private void parseSdpAudioRate(String sdp) {
854856 }
855857 }
856858
859+ /**
860+ * Detects the video codec from the SDP "m=video" line and updates {@link #codecH265}.
861+ * Knowing the codec before the first RTP packet allows pre-warming the decoder immediately
862+ * after PLAY, hiding the 200–500 ms MediaCodec startup cost behind network latency.
863+ */
864+ private void parseSdpVideoCodec (String sdp ) {
865+ for (String line : sdp .split ("[\r \n ]+" )) {
866+ if (line .startsWith ("m=video" )) {
867+ // "m=video <port> RTP/AVP <pt> [...]" — first payload type determines codec
868+ String [] parts = line .split ("\\ s+" );
869+ if (parts .length >= 4 ) {
870+ try {
871+ int pt = Integer .parseInt (parts [3 ]);
872+ codecH265 = (pt == RTP_PT_H265 );
873+ Log .d (TAG , "SDP video codec: " + (codecH265 ? "H.265" : "H.264" )
874+ + " (PT=" + pt + ")" );
875+ } catch (NumberFormatException ignored ) {}
876+ }
877+ break ;
878+ }
879+ }
880+ }
881+
857882 /**
858883 * Parses per-track Control URLs from an SDP body (RFC 2326 §C.1.1).
859884 * Returns a 2-element array: [videoControlUrl, audioControlUrl].
@@ -930,6 +955,7 @@ private void rtspConnect() throws Exception {
930955 sdpBodyLen -= n ;
931956 }
932957 parseSdpAudioRate (sdp .toString ());
958+ parseSdpVideoCodec (sdp .toString ()); // detect codec early to pre-warm the decoder
933959 // parse per-track Control URLs; used for SETUP requests below
934960 String [] trackUrls = parseSdpControls (sdp .toString (), rtspUrl );
935961
@@ -974,6 +1000,9 @@ private void rtspConnect() throws Exception {
9741000 // not from the epoch (lastFrame == 0 would trigger the watchdog immediately)
9751001 lastFrame = SystemClock .elapsedRealtime ();
9761002 activeStream = true ;
1003+ // pre-warm the decoder now, while first packets are still in transit;
1004+ // without this, createDecoder() runs on the first decoded frame (~200–500 ms later)
1005+ createDecoder ();
9771006 try {
9781007 if (mType ) {
9791008 try (DatagramSocket d = new DatagramSocket (5000 )) {
0 commit comments