@@ -222,6 +222,12 @@ def pw_record_supports_raw(help_text: str | None = None) -> bool:
222222 return "--raw" in (help_text if help_text is not None else pw_record_help ())
223223
224224
225+ def pw_record_capture_features (help_text : str ) -> tuple [bool , bool ]:
226+ include_sample_count = pw_record_supports_sample_count (help_text )
227+ raw_output = include_sample_count and pw_record_supports_raw (help_text )
228+ return include_sample_count , raw_output
229+
230+
225231def build_pw_record_capture_command (
226232 path : Path ,
227233 * ,
@@ -259,6 +265,33 @@ def build_pw_record_capture_command(
259265 return command
260266
261267
268+ def decode_process_output (payload : bytes | None ) -> str :
269+ if not payload :
270+ return ""
271+ return payload .decode ("utf-8" , errors = "replace" ).strip ()
272+
273+
274+ def signal_capture_output_text (
275+ stdout : bytes | None ,
276+ stderr : bytes | None ,
277+ * ,
278+ raw_stdout : bool ,
279+ ) -> str :
280+ parts : list [str ] = []
281+ if not raw_stdout :
282+ parts .append (decode_process_output (stdout ))
283+ parts .append (decode_process_output (stderr ))
284+ return "\n " .join (part for part in parts if part )
285+
286+
287+ def materialize_raw_stdout_capture (capture_path : Path , stdout : bytes | None ) -> None :
288+ if not stdout :
289+ return
290+ if capture_path .exists () and capture_path .stat ().st_size > 0 :
291+ return
292+ capture_path .write_bytes (stdout )
293+
294+
262295def capture_sink_monitor_rms (
263296 sink_name : str ,
264297 path : Path ,
@@ -271,8 +304,7 @@ def capture_sink_monitor_rms(
271304 sample_width_bytes = 2
272305 expected_capture_bytes = sample_count * channels * sample_width_bytes
273306 help_text = pw_record_help ()
274- include_sample_count = pw_record_supports_sample_count (help_text )
275- raw_output = pw_record_supports_raw (help_text )
307+ include_sample_count , raw_output = pw_record_capture_features (help_text )
276308 capture_path = path if raw_output else path .with_suffix (".wav" )
277309 capture_path .unlink (missing_ok = True )
278310 command = build_pw_record_capture_command (
@@ -282,12 +314,15 @@ def capture_sink_monitor_rms(
282314 raw_output = raw_output ,
283315 )
284316 print (f"$ { format_command (command )} " , flush = True )
285- recorder = subprocess .Popen (command , text = True , stdout = subprocess .PIPE , stderr = subprocess .STDOUT )
317+ recorder = subprocess .Popen (command , stdout = subprocess .PIPE , stderr = subprocess .PIPE )
286318 try :
287319
288320 def capture_node () -> Any :
289321 if recorder .poll () is not None :
290- output = recorder .stdout .read ().strip () if recorder .stdout is not None else ""
322+ stdout , stderr = recorder .communicate ()
323+ if raw_output :
324+ materialize_raw_stdout_capture (capture_path , stdout )
325+ output = signal_capture_output_text (stdout , stderr , raw_stdout = raw_output )
291326 detail = f": { output } " if output else ""
292327 raise RuntimeError (f"signal capture exited before stream appeared{ detail } " )
293328 return live .node_by_name (CAPTURE_NODE_NAME )
@@ -302,18 +337,32 @@ def capture_node() -> Any:
302337
303338 if include_sample_count :
304339 try :
305- output , _stderr = recorder .communicate (timeout = timeout_seconds )
340+ stdout , stderr = recorder .communicate (timeout = timeout_seconds )
306341 except subprocess .TimeoutExpired as exc :
307342 recorder .kill ()
308- output , _stderr = recorder .communicate (timeout = 2.0 )
343+ stdout , stderr = recorder .communicate (timeout = 2.0 )
344+ if raw_output :
345+ materialize_raw_stdout_capture (capture_path , stdout )
346+ output = signal_capture_output_text (stdout , stderr , raw_stdout = raw_output )
309347 raise RuntimeError (f"timed out waiting for signal capture to finish: { output .strip ()} " ) from exc
348+ if raw_output :
349+ materialize_raw_stdout_capture (capture_path , stdout )
350+ output = signal_capture_output_text (stdout , stderr , raw_stdout = raw_output )
310351 else :
311352 dispatch_until (
312353 "Mini EQ signal capture data" ,
313354 lambda : capture_path .exists () and capture_path .stat ().st_size >= expected_capture_bytes ,
314355 timeout_seconds ,
315356 )
316- output = live .terminate_process (recorder , "pw-record signal capture" )
357+ recorder .terminate ()
358+ try :
359+ stdout , stderr = recorder .communicate (timeout = 5.0 )
360+ except subprocess .TimeoutExpired :
361+ recorder .kill ()
362+ stdout , stderr = recorder .communicate (timeout = 5.0 )
363+ if raw_output :
364+ materialize_raw_stdout_capture (capture_path , stdout )
365+ output = signal_capture_output_text (stdout , stderr , raw_stdout = raw_output )
317366
318367 if output :
319368 print (f"Mini EQ signal capture output:\n { output .rstrip ()} " , flush = True )
@@ -330,7 +379,12 @@ def capture_node() -> Any:
330379 return wav_s16le_rms (capture_path , skip_frames = sample_count // 4 , channels = channels )
331380 finally :
332381 if recorder .poll () is None :
333- live .terminate_process (recorder , "pw-record signal capture" )
382+ recorder .terminate ()
383+ try :
384+ recorder .communicate (timeout = 5.0 )
385+ except subprocess .TimeoutExpired :
386+ recorder .kill ()
387+ recorder .communicate (timeout = 5.0 )
334388
335389
336390def assert_signal_is_attenuated (label : str , * , baseline_rms : float , measured_rms : float ) -> None :
0 commit comments