1+ import struct
2+ import time
3+
4+ def read_wav (filename ):
5+ try :
6+ with open (filename , 'rb' ) as f :
7+ def read (fmt ):
8+ return struct .unpack (fmt , f .read (struct .calcsize (fmt )))
9+
10+ # === Chunk Header ===
11+ chunk_id , = read ('<I' )
12+ if chunk_id != 0x46464952 : # "RIFF"
13+ print (f"[ERROR] chunk_id != 'RIFF': { hex (chunk_id )} " )
14+ return [], - 1 , - 1 , False
15+
16+ _chunk_size , = read ('<I' )
17+ format_tag , = read ('<I' )
18+ if format_tag != 0x45564157 : # "WAVE"
19+ print (f"[ERROR] format != 'WAVE': { hex (format_tag )} " )
20+ return [], - 1 , - 1 , False
21+
22+ # === Subchunk1: fmt ===
23+ subchunk1_id , = read ('<I' )
24+ subchunk1_size , = read ('<I' )
25+
26+ if subchunk1_id == 0x4B4E554A : # JUNK
27+ f .seek (subchunk1_size , 1 )
28+ subchunk1_id , = read ('<I' )
29+ subchunk1_size , = read ('<I' )
30+
31+ if subchunk1_id != 0x20746D66 : # "fmt "
32+ print (f"[ERROR] subchunk1_id != 'fmt ': { hex (subchunk1_id )} " )
33+ return [], - 1 , - 1 , False
34+
35+ if subchunk1_size not in [16 , 18 ]:
36+ print (f"[ERROR] subchunk1_size != 16 or 18: { subchunk1_size } " )
37+ return [], - 1 , - 1 , False
38+
39+ audio_format , = read ('<H' )
40+ if audio_format != 1 :
41+ print (f"[ERROR] audio_format != PCM (1): { audio_format } " )
42+ return [], - 1 , - 1 , False
43+
44+ num_channels , = read ('<H' )
45+ sample_rate , = read ('<I' )
46+ byte_rate , = read ('<I' )
47+ block_align , = read ('<H' )
48+ bits_per_sample , = read ('<H' )
49+
50+ expected_byte_rate = sample_rate * num_channels * bits_per_sample // 8
51+ if byte_rate != expected_byte_rate :
52+ print (f"[ERROR] byte_rate mismatch: got { byte_rate } , expected { expected_byte_rate } " )
53+ return [], - 1 , - 1 , False
54+
55+ expected_align = num_channels * bits_per_sample // 8
56+ if block_align != expected_align :
57+ print (f"[ERROR] block_align mismatch: got { block_align } , expected { expected_align } " )
58+ return [], - 1 , - 1 , False
59+
60+ if bits_per_sample != 16 :
61+ print (f"[ERROR] Only 16-bit samples supported, got { bits_per_sample } " )
62+ return [], - 1 , - 1 , False
63+
64+ if subchunk1_size == 18 :
65+ extra_size , = read ('<H' )
66+ if extra_size != 0 :
67+ print (f"[ERROR] extra_size != 0: { extra_size } " )
68+ return [], - 1 , - 1 , False
69+
70+ # === Subchunk2: data ===
71+ while True :
72+ subchunk2_id , subchunk2_size = read ('<II' )
73+ if subchunk2_id == 0x61746164 : # "data"
74+ break
75+ f .seek (subchunk2_size , 1 )
76+
77+ raw_pcm = f .read (subchunk2_size )
78+ if len (raw_pcm ) != subchunk2_size :
79+ print ("[ERROR] Failed to read full PCM data" )
80+ return [], - 1 , - 1 , False
81+
82+ return list (raw_pcm ), sample_rate , num_channels , True
83+
84+ except Exception as e :
85+ print (f"[ERROR] read_wave() failed: { e } " )
86+ return [], - 1 , - 1 , False
87+
88+
89+ def write_wave (filename , sample_rate , samples , num_channels = 1 ):
90+ try :
91+ import array
92+ if isinstance (samples [0 ], int ):
93+ samples = array .array ('h' , samples )
94+
95+ subchunk2_size = len (samples ) * 2
96+ chunk_size = 36 + subchunk2_size
97+
98+ with open (filename , 'wb' ) as f :
99+ # RIFF chunk
100+ f .write (struct .pack ('<I' , 0x46464952 )) # "RIFF"
101+ f .write (struct .pack ('<I' , chunk_size ))
102+ f .write (struct .pack ('<I' , 0x45564157 )) # "WAVE"
103+
104+ # fmt subchunk
105+ f .write (struct .pack ('<I' , 0x20746D66 )) # "fmt "
106+ f .write (struct .pack ('<I' , 16 )) # PCM
107+ f .write (struct .pack ('<H' , 1 )) # PCM format
108+ f .write (struct .pack ('<H' , num_channels ))
109+ f .write (struct .pack ('<I' , sample_rate ))
110+ f .write (struct .pack ('<I' , sample_rate * num_channels * 2 )) # byte_rate
111+ f .write (struct .pack ('<H' , num_channels * 2 )) # block_align
112+ f .write (struct .pack ('<H' , 16 )) # bits per sample
113+
114+ # data subchunk
115+ f .write (struct .pack ('<I' , 0x61746164 )) # "data"
116+ f .write (struct .pack ('<I' , subchunk2_size ))
117+ f .write (samples .tobytes ())
118+
119+ return True
120+ except Exception as e :
121+ print (f"[ERROR] write_wave() failed: { e } " )
122+ return False
123+
124+
125+ def play_pcm_stream (client , pcm_list , stream_name = "example" , chunk_size = 96000 , sleep_time = 1.0 , verbose = False ):
126+ """
127+ Play PCM audio stream (16-bit little-endian format), sending data in chunks.
128+
129+ Parameters:
130+ client: An object with a PlayStream method
131+ pcm_list: list[int], PCM audio data in int16 format
132+ stream_name: Stream name, default is "example"
133+ chunk_size: Number of bytes to send per chunk, default is 96000 (3 seconds at 16kHz)
134+ sleep_time: Delay between chunks in seconds
135+ """
136+ pcm_data = bytes (pcm_list )
137+ stream_id = str (int (time .time () * 1000 )) # Unique stream ID based on current timestamp
138+ offset = 0
139+ chunk_index = 0
140+ total_size = len (pcm_data )
141+
142+ while offset < total_size :
143+ remaining = total_size - offset
144+ current_chunk_size = min (chunk_size , remaining )
145+ chunk = pcm_data [offset :offset + current_chunk_size ]
146+
147+ if verbose :
148+ # Print info about the current chunk
149+ print (f"[CHUNK { chunk_index } ] offset = { offset } , size = { current_chunk_size } bytes" )
150+ print (" First 10 samples (int16): " , end = "" )
151+ for i in range (0 , min (20 , len (chunk ) - 1 ), 2 ):
152+ sample = struct .unpack_from ('<h' , chunk , i )[0 ]
153+ print (sample , end = " " )
154+ print ()
155+
156+ # Send the chunk
157+ ret_code , _ = client .PlayStream (stream_name , stream_id , chunk )
158+ if ret_code != 0 :
159+ print (f"[ERROR] Failed to send chunk { chunk_index } , return code: { ret_code } " )
160+ break
161+ else :
162+ print (f"[INFO] Chunk { chunk_index } sent successfully" )
163+
164+ offset += current_chunk_size
165+ chunk_index += 1
166+ time .sleep (sleep_time )
0 commit comments