1+ # Copyright 2026 LiveKit, Inc.
2+ #
3+ # Licensed under the Apache License, Version 2.0 (the "License");
4+ # you may not use this file except in compliance with the License.
5+ # You may obtain a copy of the License at
6+ #
7+ # http://www.apache.org/licenses/LICENSE-2.0
8+ #
9+ # Unless required by applicable law or agreed to in writing, software
10+ # distributed under the License is distributed on an "AS IS" BASIS,
11+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ # See the License for the specific language governing permissions and
13+ # limitations under the License.
14+
115"""End-to-end Test for simulcast / SVC video quality layers.
216
317The test publishes a 1280x720 video track (rolling colored bars) with four
5468
5569QUALITY_SEQUENCE = [
5670 (VideoQuality .VIDEO_QUALITY_HIGH , "f" ),
57- (VideoQuality .VIDEO_QUALITY_MEDIUM , "h" ),
5871 (VideoQuality .VIDEO_QUALITY_LOW , "q" ),
72+ (VideoQuality .VIDEO_QUALITY_MEDIUM , "h" ),
5973]
6074
6175
@@ -93,7 +107,7 @@ async def _wait_until(
93107 interval : float = WAIT_INTERVAL ,
94108 message : str = "condition not met" ,
95109) -> None :
96- loop = asyncio .get_event_loop ()
110+ loop = asyncio .get_running_loop ()
97111 deadline = loop .time () + timeout
98112 while loop .time () < deadline :
99113 if predicate ():
@@ -132,7 +146,7 @@ def _expect_event(
132146 event : EventTypes ,
133147 predicate : Optional [Callable [..., bool ]] = None ,
134148) -> asyncio .Future :
135- loop = asyncio .get_event_loop ()
149+ loop = asyncio .get_running_loop ()
136150 fut : asyncio .Future = loop .create_future ()
137151
138152 def _on_event (* args , ** kwargs ) -> None :
@@ -200,11 +214,11 @@ async def _wait_for_layer(
200214) -> Tuple [int , int ]:
201215 """Drain frames until we observe `samples` consecutive frames whose
202216 dimensions match the expected layer (within `tolerance`)."""
203- deadline = asyncio .get_event_loop ().time () + timeout
217+ deadline = asyncio .get_running_loop ().time () + timeout
204218 matches = 0
205219 last : Optional [Tuple [int , int ]] = None
206220 iterator = stream .__aiter__ ()
207- while asyncio .get_event_loop ().time () < deadline :
221+ while asyncio .get_running_loop ().time () < deadline :
208222 try :
209223 ev = await asyncio .wait_for (iterator .__anext__ (), timeout = 2.0 )
210224 except asyncio .TimeoutError :
@@ -278,7 +292,7 @@ async def test_simulcast_quality_layers(
278292 source = rtc .TrackSource .SOURCE_CAMERA ,
279293 simulcast = True ,
280294 video_codec = video_codec ,
281- video_encoding = rtc .VideoEncoding (max_bitrate = 3_000_000 , max_framerate = PUBLISH_FPS ),
295+ video_encoding = rtc .VideoEncoding (max_bitrate = 6_000_000 , max_framerate = PUBLISH_FPS ),
282296 )
283297 # For SVC codecs, ask libwebrtc to emit three spatial × three temporal
284298 # layers in a single bitstream. Without an explicit scalability_mode AV1
@@ -309,12 +323,6 @@ async def test_simulcast_quality_layers(
309323 )
310324 remote_pub = await _ensure_track_subscribed (receiver , local_pub .sid )
311325 assert remote_pub .track is not None
312-
313- # Give the SFU a moment to propagate layer metadata and let the
314- # encoder/bandwidth estimator ramp up to all layers before we start
315- # switching qualities. SVC codecs (VP9/AV1) typically need more
316- # time than VP8/H264 simulcast to produce all spatial layers.
317- await asyncio .sleep (10.0 if mode == "svc" else 5.0 )
318326 print (
319327 f"[{ codec_name } ] remote_pub: sid={ remote_pub .sid } "
320328 f"simulcasted={ remote_pub .simulcasted } "
@@ -333,8 +341,8 @@ async def test_simulcast_quality_layers(
333341 stop .set ()
334342 try :
335343 await pub_task
336- except Exception :
337- pass
344+ except Exception as e :
345+ raise AssertionError ( f"pub_task failed: { e } " ) from e
338346 if stream is not None :
339347 await stream .aclose ()
340348 await sender .disconnect ()
0 commit comments