### Quick Usage Example: Reading RTP Packets with RTSPReader Source: https://github.com/marss/aiortsp/blob/master/README.rst Demonstrates the basic usage of aiortsp to connect to an RTSP stream and iterate over received RTP packets. It utilizes `asyncio` and the `RTSPReader` class to establish a connection and process packet data. ```python import asyncio from aiortsp.rtsp.reader import RTSPReader async def main(): # Open a reader (which means RTSP connection, then media session) async with RTSPReader('rtsp://cam/video.sdp') as reader: # Iterate on RTP packets async for pkt in reader.iter_packets(): print('PKT', pkt.seq, pkt.pt, len(pkt)) asyncio.run(main()) ``` -------------------------------- ### RTSP Session Setup and Playback Control with Python Source: https://context7.com/marss/aiortsp/llms.txt Demonstrates the complete lifecycle management of an RTSP media session using Python's aiortsp library. It covers establishing a connection, selecting the appropriate transport, setting up the media session, parsing SDP information, controlling playback (play, pause, resume with seek), and maintaining the session with keep-alives. Dependencies include asyncio, urllib.parse, and aiortsp components. ```python import asyncio from urllib.parse import urlparse from aiortsp.rtsp.connection import RTSPConnection from aiortsp.rtsp.session import RTSPMediaSession from aiortsp.transport import transport_for_scheme async def full_session_control(): url = 'rtsp://camera.example.com:554/live.sdp' p_url = urlparse(url) async with RTSPConnection( p_url.hostname, p_url.port or 554, p_url.username, p_url.password ) as conn: # Select transport based on URL scheme (UDP for rtsp://, TCP for rtspt://) transport_class = transport_for_scheme(p_url.scheme) async with transport_class(conn, timeout=10) as transport: async with RTSPMediaSession( conn, url, transport, media_type='video' # or 'audio' ) as session: # Access parsed SDP information if session.sdp: print(f'Session name: {session.sdp.get("sessionName")}') print(f'Clock rate: {session.sdp.media_clock_rate()}') print(f'Payload type: {session.sdp.media_payload_type()}') # Get H.264 codec parameters if available h264_props = session.sdp.guess_h264_props() if h264_props: print(f'H.264 SPS/PPS: {h264_props}') # Start playback await session.play() print(f'Playing... Keep-alive interval: {session.session_keepalive}s') # Keep stream alive for _ in range(5): await asyncio.sleep(session.session_keepalive) await session.keep_alive() print(f'Stats - Jitter: {session.stats.jitter}, Lost: {session.stats.lost}') # Pause playback (keeps session alive) await session.pause() print('Stream paused') # Resume with seek to specific timestamp import time seek_time = time.time() - 3600 # 1 hour ago (for recorded streams) await session.play(seek=seek_time, speed=2) # 2x playback speed # Session automatically tears down when exiting context asyncio.run(full_session_control()) ``` -------------------------------- ### Low-Level RTSP Communication with RTSPConnection (Python) Source: https://context7.com/marss/aiortsp/llms.txt Illustrates direct interaction with the RTSP protocol using the RTSPConnection class. This example shows how to send RTSP requests like OPTIONS and DESCRIBE, handle authentication (Basic and Digest), and manage potential connection or response errors. It's suitable for fine-grained control over RTSP communication. ```Python import asyncio from aiortsp.rtsp.connection import RTSPConnection from aiortsp.rtsp.errors import RTSPResponseError, RTSPConnectionError, RTSPTimeoutError async def low_level_rtsp_communication(): try: # Create connection with authentication async with RTSPConnection( host='192.168.1.100', port=554, username='admin', password='secret', accept_auth=['digest', 'basic'], # Preferred auth methods timeout=10 ) as conn: # Send OPTIONS request to discover supported methods options_resp = await conn.send_request( 'OPTIONS', 'rtsp://192.168.1.100:554/stream' ) print(f'Server supports: {options_resp.headers.get("public")}') # Send DESCRIBE to get SDP (Session Description Protocol) describe_resp = await conn.send_request( 'DESCRIBE', 'rtsp://192.168.1.100:554/stream', headers={'Accept': 'application/sdp'} ) print(f'SDP Content:\n{describe_resp.content}') # Check if connection is still active if conn.running: print('Connection is active') except RTSPConnectionError as e: print(f'Connection failed: {e}') except RTSPResponseError as e: print(f'Server returned error: {e.response.status} {e.response.status_msg}') except RTSPTimeoutError as e: print(f'Request timed out: {e}') asyncio.run(low_level_rtsp_communication()) ``` -------------------------------- ### Custom RTP Packet Handling with RTPTransportClient in Python Source: https://context7.com/marss/aiortsp/llms.txt Demonstrates how to implement a custom RTPTransportClient to process incoming RTP and RTCP packets. This example shows how to detect packet loss, analyze packet data, and handle connection closures. It requires the 'aiortsp' and 'dpkt' libraries. ```python import asyncio from urllib.parse import urlparse from dpkt.rtp import RTP from aiortsp.rtcp.parser import RTCP from aiortsp.transport import RTPTransportClient, transport_for_scheme from aiortsp.rtsp.connection import RTSPConnection from aiortsp.rtsp.session import RTSPMediaSession class VideoAnalyzer(RTPTransportClient): """Custom handler that analyzes incoming video packets.""" def __init__(self): self.packet_count = 0 self.total_bytes = 0 self.last_seq = None self.gaps = 0 def handle_rtp(self, rtp: RTP): """Called for each incoming RTP packet.""" self.packet_count += 1 self.total_bytes += len(rtp.data) # Detect sequence gaps (packet loss) if self.last_seq is not None: expected = (self.last_seq + 1) % 65536 if rtp.seq != expected: self.gaps += 1 print(f'Gap detected: expected {expected}, got {rtp.seq}') self.last_seq = rtp.seq # Analyze packet properties if self.packet_count % 100 == 0: print(f'Received {self.packet_count} packets, ' f'{self.total_bytes} bytes, {self.gaps} gaps') def handle_rtcp(self, rtcp: RTCP): """Called for each incoming RTCP report.""" for packet in rtcp.packets: print(f'RTCP: {packet}') def handle_closed(self, error): """Called when connection is closed.""" print(f'Stream closed. Total: {self.packet_count} packets, ' f'{self.total_bytes} bytes, {self.gaps} gaps') if error: print(f'Error: {error}') async def analyze_stream(): url = 'rtsp://camera.example.com/stream' p_url = urlparse(url) analyzer = VideoAnalyzer() async with RTSPConnection(p_url.hostname, p_url.port or 554) as conn: transport_class = transport_for_scheme(p_url.scheme) async with transport_class(conn, timeout=30) as transport: # Subscribe our custom handler to receive packets transport.subscribe(analyzer) async with RTSPMediaSession(conn, url, transport) as session: await session.play() try: while conn.running and transport.running: await asyncio.sleep(session.session_keepalive) await session.keep_alive() except asyncio.CancelledError: pass # Unsubscribe when done transport.unsubscribe(analyzer) asyncio.run(analyze_stream()) ``` -------------------------------- ### Get H.264 Codec Parameters Source: https://context7.com/marss/aiortsp/llms.txt Retrieves H.264 codec parameters (SPS/PPS) for decoder initialization using sdp.guess_h264_props(). Also demonstrates helper methods for getting video clock rate and payload type. ```python # Get H.264 codec parameters for decoder initialization h264_params = sdp.guess_h264_props() if h264_params: print(f'H.264 SPS/PPS: {h264_params}') # Helper methods print(f'\nVideo clock rate: {sdp.media_clock_rate("video")}') print(f'Video payload type: {sdp.media_payload_type("video")}') ``` -------------------------------- ### RTSPConnection - Low-Level RTSP Protocol Client Source: https://context7.com/marss/aiortsp/llms.txt RTSPConnection provides direct access to the RTSP protocol, enabling developers to send individual RTSP requests (OPTIONS, DESCRIBE, SETUP, PLAY, PAUSE, TEARDOWN) and manage responses. This is ideal for scenarios requiring fine-grained control over RTSP communication. ```APIDOC ## RTSPConnection - Low-Level RTSP Protocol Client ### Description Provides direct access to the RTSP protocol, allowing you to send individual RTSP requests (OPTIONS, DESCRIBE, SETUP, PLAY, PAUSE, TEARDOWN) and receive responses. This is useful when you need fine-grained control over the RTSP communication or want to implement custom session handling. ### Method `async with RTSPConnection(host, port, username=None, password=None, accept_auth=None, timeout=None)` ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Request Example ```python import asyncio from aiortsp.rtsp.connection import RTSPConnection from aiortsp.rtsp.errors import RTSPResponseError, RTSPConnectionError, RTSPTimeoutError async def low_level_rtsp_communication(): try: async with RTSPConnection( host='192.168.1.100', port=554, username='admin', password='secret', accept_auth=['digest', 'basic'], timeout=10 ) as conn: options_resp = await conn.send_request('OPTIONS', 'rtsp://192.168.1.100:554/stream') print(f'Server supports: {options_resp.headers.get("public")}') describe_resp = await conn.send_request('DESCRIBE', 'rtsp://192.168.1.100:554/stream', headers={'Accept': 'application/sdp'}) print(f'SDP Content:\n{describe_resp.content}') if conn.running: print('Connection is active') except RTSPConnectionError as e: print(f'Connection failed: {e}') except RTSPResponseError as e: print(f'Server returned error: {e.response.status} {e.response.status_msg}') except RTSPTimeoutError as e: print(f'Request timed out: {e}') asyncio.run(low_level_rtsp_communication()) ``` ### Response #### Success Response (200) An `RTSPResponse` object containing the server's response, including status code, headers, and content. #### Response Example ```json { "example": "RTSPResponse object with status, headers, and content attributes" } ``` ``` -------------------------------- ### RTSPReader - Simple RTP Packet Iterator Source: https://context7.com/marss/aiortsp/llms.txt The RTSPReader class offers a high-level interface for establishing RTSP connections and iterating over incoming RTP packets. It automates connection setup, session management, and keep-alive messages. ```APIDOC ## RTSPReader - Simple RTP Packet Iterator ### Description Provides a high-level wrapper for quickly connecting to an RTSP stream and iterating over incoming RTP packets. It handles connection setup, session management, and keep-alive messages automatically. ### Method `async with RTSPReader(url, timeout=None, ssl=None, run_loop=False)` ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Request Example ```python import asyncio from aiortsp.rtsp.reader import RTSPReader async def receive_video_stream(): async with RTSPReader('rtsp://camera.example.com/live.sdp') as reader: async for pkt in reader.iter_packets(): print(f'Received packet: seq={pkt.seq}, timestamp={pkt.ts}, payload_type={pkt.pt}, data_length={len(pkt.data)}') if pkt.seq > 100: break asyncio.run(receive_video_stream()) ``` ### Response #### Success Response (200) An asynchronous iterator yielding `dpkt.rtp.RTP` objects representing incoming RTP packets. #### Response Example ```json { "example": "pkt is a dpkt.rtp.RTP object with attributes like seq, ts, pt, data" } ``` ``` -------------------------------- ### SDP Session Description Protocol Parsing in Python Source: https://context7.com/marss/aiortsp/llms.txt Shows how to parse Session Description Protocol (SDP) content using the SDP class from aiortsp. This enables extraction of media stream details like codecs, clock rates, and control URLs. It's useful for configuring RTSP clients. ```python from aiortsp.rtsp.sdp import SDP # Example SDP content from a camera sdp_content = """v=0 o=- 1234567890 1 IN IP4 192.168.1.100 s=Live Stream t=0 0 a=control:* m=video 0 RTP/AVP 96 a=rtpmap:96 H264/90000 a=fmtp:96 packetization-mode=1;sprop-parameter-sets=Z0IAH5WoFAFuQA==,aM48gA== a=control:trackID=0 a=framerate:30.0 a=framesize:96 1920-1080 m=audio 0 RTP/AVP 97 a=rtpmap:97 MPEG4-GENERIC/48000 a=control:trackID=1 """ # Parse the SDP sdp = SDP(sdp_content) # Access session-level information print(f'Session name: {sdp.get("sessionName")}') print(f'Origin: {sdp.get("origin")}') print(f'Version: {sdp.get("version")}') # Get video media information video_media = sdp.get_media(media_type='video', media_idx=0) if video_media: print(f'\nVideo Media:') print(f' Type: {video_media["type"]}') print(f' Protocol: {video_media["protocol"]}') print(f' Format: {video_media["format"]}') attrs = video_media.get('attributes', {}) if 'rtpmap' in attrs: print(f' Encoding: {attrs["rtpmap"]["encoding"]}') print(f' Clock Rate: {attrs["rtpmap"]["clockRate"]}') print(f' Payload Type: {attrs["rtpmap"]["pt"]}') if 'framerate' in attrs: print(f' Frame Rate: {attrs["framerate"]}') if 'framesize' in attrs: fs = attrs['framesize'] print(f' Resolution: {fs["width"]}x{fs["height"]}') # Get audio media audio_media = sdp.get_media(media_type='audio', media_idx=0) if audio_media: print(f'\nAudio Media:') attrs = audio_media.get('attributes', {}) if 'rtpmap' in attrs: print(f' Encoding: {attrs["rtpmap"]["encoding"]}') print(f' Sample Rate: {attrs["rtpmap"]["clockRate"]}') # Build setup URL for video track base_url = 'rtsp://192.168.1.100:554/live' setup_url = sdp.setup_url(base_url, media_type='video') print(f'\nVideo setup URL: {setup_url}') ``` -------------------------------- ### Stream with UDPTransport in aiortsp Source: https://context7.com/marss/aiortsp/llms.txt Demonstrates streaming media using UDPTransport for local networks. It shows explicit UDP transport configuration with custom buffer sizes and subscription to packet handling. ```python import asyncio from urllib.parse import urlparse from aiortsp.rtsp.connection import RTSPConnection from aiortsp.rtsp.session import RTSPMediaSession from aiortsp.transport import UDPTransport from aiortsp.transport.base import RTPTransportClient class PacketCounter(RTPTransportClient): def __init__(self): self.count = 0 def handle_rtp(self, rtp): self.count += 1 async def stream_with_udp_transport(): """UDP transport - best for local networks with reliable connectivity.""" url = 'rtsp://camera.local/stream' # rtsp:// uses UDP p_url = urlparse(url) counter = PacketCounter() async with RTSPConnection(p_url.hostname, p_url.port or 554) as conn: # Explicit UDP transport with custom buffer sizes async with UDPTransport( conn, timeout=30, receive_buffer=8 * 1024 * 1024, # 8MB receive buffer send_buffer=1 * 1024 * 1024 # 1MB send buffer ) as transport: transport.subscribe(counter) async with RTSPMediaSession(conn, url, transport) as session: await session.play() # Run for 30 seconds await asyncio.sleep(30) print(f'UDP: Received {counter.count} packets') ``` -------------------------------- ### SDP - Session Description Protocol Parser Source: https://context7.com/marss/aiortsp/llms.txt The SDP class parses Session Description Protocol content returned by RTSP DESCRIBE requests. It provides access to media stream information including codecs, clock rates, and control URLs. ```APIDOC ## SDP - Session Description Protocol Parser ### Description Parses Session Description Protocol (SDP) content returned by RTSP DESCRIBE requests. Provides access to media stream information including codecs, clock rates, and control URLs. ### Class SDP ### Methods - **get(attribute_name)**: Retrieves a session-level attribute. - **get_media(media_type, media_idx)**: Retrieves information for a specific media stream. - **setup_url(base_url, media_type)**: Builds a setup URL for a given media type. ### Example Usage ```python from aiortsp.rtsp.sdp import SDP # Example SDP content from a camera sdp_content = """v=0 o=- 1234567890 1 IN IP4 192.168.1.100 s=Live Stream t=0 0 a=control:* m=video 0 RTP/AVP 96 a=rtpmap:96 H264/90000 a=fmtp:96 packetization-mode=1;sprop-parameter-sets=Z0IAH5WoFAFuQA==,aM48gA== a=control:trackID=0 a=framerate:30.0 a=framesize:96 1920-1080 m=audio 0 RTP/AVP 97 a=rtpmap:97 MPEG4-GENERIC/48000 a=control:trackID=1 """ # Parse the SDP sdp = SDP(sdp_content) # Access session-level information print(f'Session name: {sdp.get("sessionName")}') print(f'Origin: {sdp.get("origin")}') print(f'Version: {sdp.get("version")}') # Get video media information video_media = sdp.get_media(media_type='video', media_idx=0) if video_media: print(f'\nVideo Media:') print(f' Type: {video_media["type"]}') print(f' Protocol: {video_media["protocol"]}') print(f' Format: {video_media["format"]}') attrs = video_media.get('attributes', {}) if 'rtpmap' in attrs: print(f' Encoding: {attrs["rtpmap"]["encoding"]}') print(f' Clock Rate: {attrs["rtpmap"]["clockRate"]}') print(f' Payload Type: {attrs["rtpmap"]["pt"]}') if 'framerate' in attrs: print(f' Frame Rate: {attrs["framerate"]}') if 'framesize' in attrs: fs = attrs['framesize'] print(f' Resolution: {fs["width"]}x{fs["height"]}') # Get audio media audio_media = sdp.get_media(media_type='audio', media_idx=0) if audio_media: print(f'\nAudio Media:') attrs = audio_media.get('attributes', {}) if 'rtpmap' in attrs: print(f' Encoding: {attrs["rtpmap"]["encoding"]}') print(f' Sample Rate: {attrs["rtpmap"]["clockRate"]}') # Build setup URL for video track base_url = 'rtsp://192.168.1.100:554/live' setup_url = sdp.setup_url(base_url, media_type='video') print(f'\nVideo setup URL: {setup_url}') ``` ``` -------------------------------- ### Stream with TCPTransport in aiortsp Source: https://context7.com/marss/aiortsp/llms.txt Illustrates streaming media using TCPTransport, suitable for traversing firewalls and NAT. This method uses interleaved RTP over a standard RTSP TCP connection. ```python import asyncio from urllib.parse import urlparse from aiortsp.rtsp.connection import RTSPConnection from aiortsp.rtsp.session import RTSPMediaSession from aiortsp.transport import TCPTransport from aiortsp.transport.base import RTPTransportClient class PacketCounter(RTPTransportClient): def __init__(self): self.count = 0 def handle_rtp(self, rtp): self.count += 1 async def stream_with_tcp_transport(): """TCP interleaved transport - works through firewalls/NAT.""" url = 'rtspt://camera.remote.com/stream' # rtspt:// uses TCP p_url = urlparse(url) counter = PacketCounter() async with RTSPConnection(p_url.hostname, p_url.port or 554) as conn: # TCP transport - RTP data interleaved in RTSP connection async with TCPTransport(conn, timeout=30) as transport: transport.subscribe(counter) async with RTSPMediaSession(conn, url, transport) as session: await session.play() await asyncio.sleep(30) print(f'TCP: Received {counter.count} packets') ``` -------------------------------- ### Monitor RTSP Stream Quality with RTCP Statistics Source: https://context7.com/marss/aiortsp/llms.txt This snippet demonstrates how to monitor the quality of an RTSP stream using the RTCPStats class. It connects to an RTSP URL, plays the stream, and periodically prints statistics such as packet loss, jitter, and received packet counts. It also shows how to manually build RTCP reports, though this is typically handled automatically by the library. ```python import asyncio from urllib.parse import urlparse from aiortsp.rtsp.connection import RTSPConnection from aiortsp.rtsp.session import RTSPMediaSession from aiortsp.transport import transport_for_scheme from aiortsp.rtcp.stats import RTCPStats async def monitor_stream_quality(): url = 'rtsp://camera.example.com/stream' p_url = urlparse(url) async with RTSPConnection(p_url.hostname, p_url.port or 554) as conn: transport_class = transport_for_scheme(p_url.scheme) async with transport_class(conn, timeout=30) as transport: async with RTSPMediaSession(conn, url, transport) as session: await session.play() # Access RTCP statistics through transport or session stats: RTCPStats = transport.stats for i in range(10): await asyncio.sleep(5) # Print stream statistics print(f'\n--- Stats Report #{i+1} ---') print(f'Packets received: {stats.received}') print(f'Packets lost: {stats.lost}') print(f'Loss fraction: {stats.fraction}/256 ({stats.fraction*100/256:.1f}%)') print(f'Jitter: {stats.jitter:.2f}') print(f'Extended sequence: {stats.extended_seq}') print(f'Cycles (wraparounds): {stats.cycles}') print(f'Last received: {stats.last_received}') # Build RTCP report manually (normally done automatically) rtcp_report = stats.build_rtcp() if rtcp_report: print(f'RTCP Report: {rtcp_report}') asyncio.run(monitor_stream_quality()) ``` -------------------------------- ### Auto-select Transport Scheme in aiortsp Source: https://context7.com/marss/aiortsp/llms.txt Demonstrates how aiortsp automatically selects the appropriate transport layer (UDPTransport or TCPTransport) based on the URL scheme (rtsp://, rtspt://, rtsps://). ```python import asyncio from urllib.parse import urlparse from aiortsp.transport import transport_for_scheme async def auto_select_transport(): """Let the library choose transport based on URL scheme.""" urls = [ 'rtsp://192.168.1.100/stream', # -> UDPTransport 'rtspt://192.168.1.100/stream', # -> TCPTransport 'rtsps://192.168.1.100/stream', # -> TCPTransport (over TLS) ] for url in urls: p_url = urlparse(url) transport_class = transport_for_scheme(p_url.scheme) print(f'{url} -> {transport_class.__name__}') asyncio.run(auto_select_transport()) ``` -------------------------------- ### Implement Robust RTSP Error Handling Source: https://context7.com/marss/aiortsp/llms.txt This code illustrates how to handle various exceptions that can occur during RTSP communication. It uses a retry mechanism for connection errors and timeouts, and provides specific handling for different RTSP response errors, including checking status codes for authentication or resource not found issues. It utilizes custom exception types provided by the aiortsp library for precise error management. ```python import asyncio from aiortsp.rtsp.reader import RTSPReader from aiortsp.rtsp.connection import RTSPConnection from aiortsp.rtsp.errors import ( RTSPError, RTSPConnectionError, RTSPResponseError, RTSPTimeoutError ) async def robust_stream_connection(): url = 'rtsp://unreliable-camera.example.com/stream' max_retries = 3 retry_delay = 5 for attempt in range(max_retries): try: async with RTSPReader(url, timeout=10) as reader: packet_count = 0 async for pkt in reader.iter_packets(): packet_count += 1 if packet_count % 1000 == 0: print(f'Processed {packet_count} packets') except RTSPConnectionError as e: print(f'Attempt {attempt+1}: Connection failed - {e}') print(f' Error type: {e.type}') # 'connection' if attempt < max_retries - 1: await asyncio.sleep(retry_delay) continue raise except RTSPResponseError as e: print(f'Attempt {attempt+1}: Server error - {e}') print(f' Status: {e.response.status}') print(f' Message: {e.response.status_msg}') reason = e.reason() print(f' Reason: {reason}') # {'type': 'response', 'status': 401, ...} # Handle specific status codes if e.response.status == 401: print('Authentication failed - check credentials') raise elif e.response.status == 404: print('Stream not found - check URL') raise except RTSPTimeoutError as e: print(f'Attempt {attempt+1}: Request timed out - {e}') if attempt < max_retries - 1: await asyncio.sleep(retry_delay) continue raise except RTSPError as e: # Catch-all for other RTSP errors print(f'Attempt {attempt+1}: RTSP error - {e}') print(f' Error type: {e.type}') raise else: # Success - no exception print('Stream completed successfully') break asyncio.run(robust_stream_connection()) ``` -------------------------------- ### RTPTransportClient - Custom Packet Handler Source: https://context7.com/marss/aiortsp/llms.txt RTPTransportClient is an interface for subscribing to RTP and RTCP packets from a transport layer. Implement this class to create custom packet processors that receive raw streaming data. ```APIDOC ## RTPTransportClient - Custom Packet Handler ### Description An interface for subscribing to RTP and RTCP packets from a transport layer. Implement this class to create custom packet processors that receive raw streaming data. ### Class RTPTransportClient ### Methods - **handle_rtp(rtp: RTP)**: Called for each incoming RTP packet. - **handle_rtcp(rtcp: RTCP)**: Called for each incoming RTCP report. - **handle_closed(error)**: Called when connection is closed. ### Example Usage ```python import asyncio from urllib.parse import urlparse from dpkt.rtp import RTP from aiortsp.rtcp.parser import RTCP from aiortsp.transport import RTPTransportClient, transport_for_scheme from aiortsp.rtsp.connection import RTSPConnection from aiortsp.rtsp.session import RTSPMediaSession class VideoAnalyzer(RTPTransportClient): """Custom handler that analyzes incoming video packets.""" def __init__(self): self.packet_count = 0 self.total_bytes = 0 self.last_seq = None self.gaps = 0 def handle_rtp(self, rtp: RTP): """Called for each incoming RTP packet.""" self.packet_count += 1 self.total_bytes += len(rtp.data) # Detect sequence gaps (packet loss) if self.last_seq is not None: expected = (self.last_seq + 1) % 65536 if rtp.seq != expected: self.gaps += 1 print(f'Gap detected: expected {expected}, got {rtp.seq}') self.last_seq = rtp.seq # Analyze packet properties if self.packet_count % 100 == 0: print(f'Received {self.packet_count} packets, ' f'{self.total_bytes} bytes, {self.gaps} gaps') def handle_rtcp(self, rtcp: RTCP): """Called for each incoming RTCP report.""" for packet in rtcp.packets: print(f'RTCP: {packet}') def handle_closed(self, error): """Called when connection is closed.""" print(f'Stream closed. Total: {self.packet_count} packets, ' f'{self.total_bytes} bytes, {self.gaps} gaps') if error: print(f'Error: {error}') async def analyze_stream(): url = 'rtsp://camera.example.com/stream' p_url = urlparse(url) analyzer = VideoAnalyzer() async with RTSPConnection(p_url.hostname, p_url.port or 554) as conn: transport_class = transport_for_scheme(p_url.scheme) async with transport_class(conn, timeout=30) as transport: # Subscribe our custom handler to receive packets transport.subscribe(analyzer) async with RTSPMediaSession(conn, url, transport) as session: await session.play() try: while conn.running and transport.running: await asyncio.sleep(session.session_keepalive) await session.keep_alive() except asyncio.CancelledError: pass # Unsubscribe when done transport.unsubscribe(analyzer) asyncio.run(analyze_stream()) ``` ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.