Background thread worker that reads audio data from the microphone. This method runs in a separate thread to avoid blocking the async event loop. It continuously reads audio chunks from the default input device and places them in the queue for consumption by the asy
(self, eventloop: asyncio.AbstractEventLoop)
| 77 | self.queue.task_done() |
| 78 | |
| 79 | def _reader_worker(self, eventloop: asyncio.AbstractEventLoop): |
| 80 | """ |
| 81 | Background thread worker that reads audio data from the microphone. |
| 82 | |
| 83 | This method runs in a separate thread to avoid blocking the async event loop. |
| 84 | It continuously reads audio chunks from the default input device and places |
| 85 | them in the queue for consumption by the async iterator. |
| 86 | |
| 87 | Args: |
| 88 | eventloop: asyncio event loop to use for thread-safe queue operations |
| 89 | """ |
| 90 | pa = pyaudio.PyAudio() |
| 91 | frames_per_chunk = 1600 # 100ms of audio at 16kHz |
| 92 | default_device = pa.get_default_input_device_info() |
| 93 | |
| 94 | # Verify the device supports the required audio format |
| 95 | if not pa.is_format_supported( |
| 96 | 16000, # Sample rate |
| 97 | input_device=default_device["index"], |
| 98 | input_channels=default_device["maxInputChannels"], |
| 99 | input_format=pyaudio.paInt16, |
| 100 | ): |
| 101 | raise RuntimeError( |
| 102 | f"Requested audio format not supported by device '{default_device['name']}'" |
| 103 | ) |
| 104 | |
| 105 | # Open the audio stream with the required configuration |
| 106 | self.stream = pa.open( |
| 107 | format=pyaudio.paInt16, |
| 108 | channels=1, |
| 109 | rate=16000, |
| 110 | input=True, |
| 111 | frames_per_buffer=frames_per_chunk, |
| 112 | start=True, |
| 113 | input_device_index=default_device["index"], |
| 114 | ) |
| 115 | |
| 116 | logger.info( |
| 117 | f"Reading audio data from default input device: '{default_device['name']}'" |
| 118 | ) |
| 119 | |
| 120 | # Read audio chunks until close is signaled |
| 121 | while self.stream.is_active() and not self.close_event.is_set(): |
| 122 | chunk = self.stream.read( |
| 123 | frames_per_chunk, exception_on_overflow=True |
| 124 | ) |
| 125 | if not eventloop.is_running(): |
| 126 | break |
| 127 | |
| 128 | # Thread-safe queue operation using the event loop |
| 129 | eventloop.call_soon_threadsafe(self.queue.put_nowait, chunk) |
| 130 | |
| 131 | # Clean up PyAudio resources |
| 132 | self.stream.stop_stream() |
| 133 | self.stream.close() |
| 134 | pa.terminate() |