deepgram.audio.microphone.microphone

  1# Copyright 2023-2024 Deepgram SDK contributors. All Rights Reserved.
  2# Use of this source code is governed by a MIT license that can be found in the LICENSE file.
  3# SPDX-License-Identifier: MIT
  4
  5import inspect
  6import asyncio
  7import threading
  8from typing import Optional, Callable, Union, TYPE_CHECKING
  9import logging
 10
 11from ...utils import verboselogs
 12
 13from .constants import LOGGING, CHANNELS, RATE, CHUNK
 14
 15if TYPE_CHECKING:
 16    import pyaudio
 17
 18
 19class Microphone:  # pylint: disable=too-many-instance-attributes
 20    """
 21    This implements a microphone for local audio input. This uses PyAudio under the hood.
 22    """
 23
 24    _logger: verboselogs.VerboseLogger
 25
 26    _audio: Optional["pyaudio.PyAudio"] = None
 27    _stream: Optional["pyaudio.Stream"] = None
 28
 29    _chunk: int
 30    _rate: int
 31    _format: int
 32    _channels: int
 33    _input_device_index: Optional[int]
 34    _is_muted: bool
 35
 36    _asyncio_loop: asyncio.AbstractEventLoop
 37    _asyncio_thread: Optional[threading.Thread] = None
 38    _exit: threading.Event
 39
 40    _push_callback_org: Optional[Callable] = None
 41    _push_callback: Optional[Callable] = None
 42
 43    def __init__(
 44        self,
 45        push_callback: Optional[Callable] = None,
 46        verbose: int = LOGGING,
 47        rate: int = RATE,
 48        chunk: int = CHUNK,
 49        channels: int = CHANNELS,
 50        input_device_index: Optional[int] = None,
 51    ):  # pylint: disable=too-many-positional-arguments
 52        # dynamic import of pyaudio as not to force the requirements on the SDK (and users)
 53        import pyaudio  # pylint: disable=import-outside-toplevel
 54
 55        self._logger = verboselogs.VerboseLogger(__name__)
 56        self._logger.addHandler(logging.StreamHandler())
 57        self._logger.setLevel(verbose)
 58
 59        self._exit = threading.Event()
 60
 61        self._audio = pyaudio.PyAudio()
 62        self._chunk = chunk
 63        self._rate = rate
 64        self._format = pyaudio.paInt16
 65        self._channels = channels
 66        self._is_muted = False
 67
 68        self._input_device_index = input_device_index
 69        self._push_callback_org = push_callback
 70
 71    def _start_asyncio_loop(self) -> None:
 72        self._asyncio_loop = asyncio.new_event_loop()
 73        self._asyncio_loop.run_forever()
 74
 75    def is_active(self) -> bool:
 76        """
 77        is_active - returns the state of the stream
 78
 79        Args:
 80            None
 81
 82        Returns:
 83            True if the stream is active, False otherwise
 84        """
 85        self._logger.debug("Microphone.is_active ENTER")
 86
 87        if self._stream is None:
 88            self._logger.error("stream is None")
 89            self._logger.debug("Microphone.is_active LEAVE")
 90            return False
 91
 92        val = self._stream.is_active()
 93        self._logger.info("is_active: %s", val)
 94        self._logger.info("is_exiting: %s", self._exit.is_set())
 95        self._logger.debug("Microphone.is_active LEAVE")
 96        return val
 97
 98    def set_callback(self, push_callback: Callable) -> None:
 99        """
100        set_callback - sets the callback function to be called when data is received.
101
102        Args:
103            push_callback (Callable): The callback function to be called when data is received.
104                                      This should be the websocket send function.
105
106        Returns:
107            None
108        """
109        self._push_callback_org = push_callback
110
111    def start(self) -> bool:
112        """
113        starts - starts the microphone stream
114
115        Returns:
116            bool: True if the stream was started, False otherwise
117        """
118        self._logger.debug("Microphone.start ENTER")
119
120        self._logger.info("format: %s", self._format)
121        self._logger.info("channels: %d", self._channels)
122        self._logger.info("rate: %d", self._rate)
123        self._logger.info("chunk: %d", self._chunk)
124        # self._logger.info("input_device_id: %d", self._input_device_index)
125
126        if self._push_callback_org is None:
127            self._logger.error("start failed. No callback set.")
128            self._logger.debug("Microphone.start LEAVE")
129            return False
130
131        if inspect.iscoroutinefunction(self._push_callback_org):
132            self._logger.verbose("async/await callback - wrapping")
133            # Run our own asyncio loop.
134            self._asyncio_thread = threading.Thread(target=self._start_asyncio_loop)
135            self._asyncio_thread.start()
136
137            self._push_callback = lambda data: (
138                asyncio.run_coroutine_threadsafe(
139                    self._push_callback_org(data), self._asyncio_loop
140                ).result()
141                if self._push_callback_org
142                else None
143            )
144        else:
145            self._logger.verbose("regular threaded callback")
146            self._asyncio_thread = None
147            self._push_callback = self._push_callback_org
148
149        if self._audio is not None:
150            self._stream = self._audio.open(
151                format=self._format,
152                channels=self._channels,
153                rate=self._rate,
154                input=True,
155                output=False,
156                frames_per_buffer=self._chunk,
157                input_device_index=self._input_device_index,
158                stream_callback=self._callback,
159            )
160
161        if self._stream is None:
162            self._logger.error("start failed. No stream created.")
163            self._logger.debug("Microphone.start LEAVE")
164            return False
165
166        self._exit.clear()
167        if self._stream is not None:
168            self._stream.start_stream()
169
170        self._logger.notice("start succeeded")
171        self._logger.debug("Microphone.start LEAVE")
172        return True
173
174    def mute(self) -> bool:
175        """
176        mute - mutes the microphone stream
177
178        Returns:
179            bool: True if the stream was muted, False otherwise
180        """
181        self._logger.verbose("Microphone.mute ENTER")
182
183        if self._stream is None:
184            self._logger.error("mute failed. Library not initialized.")
185            self._logger.verbose("Microphone.mute LEAVE")
186            return False
187
188        self._is_muted = True
189
190        self._logger.notice("mute succeeded")
191        self._logger.verbose("Microphone.mute LEAVE")
192        return True
193
194    def unmute(self) -> bool:
195        """
196        unmute - unmutes the microphone stream
197
198        Returns:
199            bool: True if the stream was unmuted, False otherwise
200        """
201        self._logger.verbose("Microphone.unmute ENTER")
202
203        if self._stream is None:
204            self._logger.error("unmute failed. Library not initialized.")
205            self._logger.verbose("Microphone.unmute LEAVE")
206            return False
207
208        self._is_muted = False
209
210        self._logger.notice("unmute succeeded")
211        self._logger.verbose("Microphone.unmute LEAVE")
212        return True
213
214    def is_muted(self) -> bool:
215        """
216        is_muted - returns the state of the stream
217
218        Args:
219            None
220
221        Returns:
222            True if the stream is muted, False otherwise
223        """
224        self._logger.spam("Microphone.is_muted ENTER")
225
226        if self._stream is None:
227            self._logger.spam("is_muted: stream is None")
228            self._logger.spam("Microphone.is_muted LEAVE")
229            return False
230
231        val = self._is_muted
232
233        self._logger.spam("is_muted: %s", val)
234        self._logger.spam("Microphone.is_muted LEAVE")
235        return val
236
237    def finish(self) -> bool:
238        """
239        finish - stops the microphone stream
240
241        Returns:
242            bool: True if the stream was stopped, False otherwise
243        """
244        self._logger.debug("Microphone.finish ENTER")
245
246        self._logger.notice("signal exit")
247        self._exit.set()
248
249        # Stop the stream.
250        if self._stream is not None:
251            self._logger.notice("stopping stream...")
252            self._stream.stop_stream()
253            self._stream.close()
254            self._logger.notice("stream stopped")
255
256        # clean up the thread
257        if (
258            # inspect.iscoroutinefunction(self._push_callback_org)
259            # and
260            self._asyncio_thread
261            is not None
262        ):
263            self._logger.notice("stopping _asyncio_loop...")
264            self._asyncio_loop.call_soon_threadsafe(self._asyncio_loop.stop)
265            self._asyncio_thread.join()
266            self._logger.notice("_asyncio_thread joined")
267        self._stream = None
268        self._asyncio_thread = None
269
270        self._logger.notice("finish succeeded")
271        self._logger.debug("Microphone.finish LEAVE")
272
273        return True
274
275    def _callback(
276        self, input_data, frame_count, time_info, status_flags
277    ):  # pylint: disable=unused-argument
278        """
279        The callback used to process data in callback mode.
280        """
281        # dynamic import of pyaudio as not to force the requirements on the SDK (and users)
282        import pyaudio  # pylint: disable=import-outside-toplevel
283
284        if self._exit.is_set():
285            self._logger.notice("_callback exit is Set. stopping...")
286            return None, pyaudio.paAbort
287
288        if input_data is None:
289            self._logger.warning("input_data is None")
290            return None, pyaudio.paContinue
291
292        try:
293            if self._is_muted:
294                size = len(input_data)
295                input_data = b"\x00" * size
296
297            self._push_callback(input_data)
298        except Exception as e:
299            self._logger.error("Error while sending: %s", str(e))
300            raise
301
302        return input_data, pyaudio.paContinue
class Microphone:
 20class Microphone:  # pylint: disable=too-many-instance-attributes
 21    """
 22    This implements a microphone for local audio input. This uses PyAudio under the hood.
 23    """
 24
 25    _logger: verboselogs.VerboseLogger
 26
 27    _audio: Optional["pyaudio.PyAudio"] = None
 28    _stream: Optional["pyaudio.Stream"] = None
 29
 30    _chunk: int
 31    _rate: int
 32    _format: int
 33    _channels: int
 34    _input_device_index: Optional[int]
 35    _is_muted: bool
 36
 37    _asyncio_loop: asyncio.AbstractEventLoop
 38    _asyncio_thread: Optional[threading.Thread] = None
 39    _exit: threading.Event
 40
 41    _push_callback_org: Optional[Callable] = None
 42    _push_callback: Optional[Callable] = None
 43
 44    def __init__(
 45        self,
 46        push_callback: Optional[Callable] = None,
 47        verbose: int = LOGGING,
 48        rate: int = RATE,
 49        chunk: int = CHUNK,
 50        channels: int = CHANNELS,
 51        input_device_index: Optional[int] = None,
 52    ):  # pylint: disable=too-many-positional-arguments
 53        # dynamic import of pyaudio as not to force the requirements on the SDK (and users)
 54        import pyaudio  # pylint: disable=import-outside-toplevel
 55
 56        self._logger = verboselogs.VerboseLogger(__name__)
 57        self._logger.addHandler(logging.StreamHandler())
 58        self._logger.setLevel(verbose)
 59
 60        self._exit = threading.Event()
 61
 62        self._audio = pyaudio.PyAudio()
 63        self._chunk = chunk
 64        self._rate = rate
 65        self._format = pyaudio.paInt16
 66        self._channels = channels
 67        self._is_muted = False
 68
 69        self._input_device_index = input_device_index
 70        self._push_callback_org = push_callback
 71
 72    def _start_asyncio_loop(self) -> None:
 73        self._asyncio_loop = asyncio.new_event_loop()
 74        self._asyncio_loop.run_forever()
 75
 76    def is_active(self) -> bool:
 77        """
 78        is_active - returns the state of the stream
 79
 80        Args:
 81            None
 82
 83        Returns:
 84            True if the stream is active, False otherwise
 85        """
 86        self._logger.debug("Microphone.is_active ENTER")
 87
 88        if self._stream is None:
 89            self._logger.error("stream is None")
 90            self._logger.debug("Microphone.is_active LEAVE")
 91            return False
 92
 93        val = self._stream.is_active()
 94        self._logger.info("is_active: %s", val)
 95        self._logger.info("is_exiting: %s", self._exit.is_set())
 96        self._logger.debug("Microphone.is_active LEAVE")
 97        return val
 98
 99    def set_callback(self, push_callback: Callable) -> None:
100        """
101        set_callback - sets the callback function to be called when data is received.
102
103        Args:
104            push_callback (Callable): The callback function to be called when data is received.
105                                      This should be the websocket send function.
106
107        Returns:
108            None
109        """
110        self._push_callback_org = push_callback
111
112    def start(self) -> bool:
113        """
114        starts - starts the microphone stream
115
116        Returns:
117            bool: True if the stream was started, False otherwise
118        """
119        self._logger.debug("Microphone.start ENTER")
120
121        self._logger.info("format: %s", self._format)
122        self._logger.info("channels: %d", self._channels)
123        self._logger.info("rate: %d", self._rate)
124        self._logger.info("chunk: %d", self._chunk)
125        # self._logger.info("input_device_id: %d", self._input_device_index)
126
127        if self._push_callback_org is None:
128            self._logger.error("start failed. No callback set.")
129            self._logger.debug("Microphone.start LEAVE")
130            return False
131
132        if inspect.iscoroutinefunction(self._push_callback_org):
133            self._logger.verbose("async/await callback - wrapping")
134            # Run our own asyncio loop.
135            self._asyncio_thread = threading.Thread(target=self._start_asyncio_loop)
136            self._asyncio_thread.start()
137
138            self._push_callback = lambda data: (
139                asyncio.run_coroutine_threadsafe(
140                    self._push_callback_org(data), self._asyncio_loop
141                ).result()
142                if self._push_callback_org
143                else None
144            )
145        else:
146            self._logger.verbose("regular threaded callback")
147            self._asyncio_thread = None
148            self._push_callback = self._push_callback_org
149
150        if self._audio is not None:
151            self._stream = self._audio.open(
152                format=self._format,
153                channels=self._channels,
154                rate=self._rate,
155                input=True,
156                output=False,
157                frames_per_buffer=self._chunk,
158                input_device_index=self._input_device_index,
159                stream_callback=self._callback,
160            )
161
162        if self._stream is None:
163            self._logger.error("start failed. No stream created.")
164            self._logger.debug("Microphone.start LEAVE")
165            return False
166
167        self._exit.clear()
168        if self._stream is not None:
169            self._stream.start_stream()
170
171        self._logger.notice("start succeeded")
172        self._logger.debug("Microphone.start LEAVE")
173        return True
174
175    def mute(self) -> bool:
176        """
177        mute - mutes the microphone stream
178
179        Returns:
180            bool: True if the stream was muted, False otherwise
181        """
182        self._logger.verbose("Microphone.mute ENTER")
183
184        if self._stream is None:
185            self._logger.error("mute failed. Library not initialized.")
186            self._logger.verbose("Microphone.mute LEAVE")
187            return False
188
189        self._is_muted = True
190
191        self._logger.notice("mute succeeded")
192        self._logger.verbose("Microphone.mute LEAVE")
193        return True
194
195    def unmute(self) -> bool:
196        """
197        unmute - unmutes the microphone stream
198
199        Returns:
200            bool: True if the stream was unmuted, False otherwise
201        """
202        self._logger.verbose("Microphone.unmute ENTER")
203
204        if self._stream is None:
205            self._logger.error("unmute failed. Library not initialized.")
206            self._logger.verbose("Microphone.unmute LEAVE")
207            return False
208
209        self._is_muted = False
210
211        self._logger.notice("unmute succeeded")
212        self._logger.verbose("Microphone.unmute LEAVE")
213        return True
214
215    def is_muted(self) -> bool:
216        """
217        is_muted - returns the state of the stream
218
219        Args:
220            None
221
222        Returns:
223            True if the stream is muted, False otherwise
224        """
225        self._logger.spam("Microphone.is_muted ENTER")
226
227        if self._stream is None:
228            self._logger.spam("is_muted: stream is None")
229            self._logger.spam("Microphone.is_muted LEAVE")
230            return False
231
232        val = self._is_muted
233
234        self._logger.spam("is_muted: %s", val)
235        self._logger.spam("Microphone.is_muted LEAVE")
236        return val
237
238    def finish(self) -> bool:
239        """
240        finish - stops the microphone stream
241
242        Returns:
243            bool: True if the stream was stopped, False otherwise
244        """
245        self._logger.debug("Microphone.finish ENTER")
246
247        self._logger.notice("signal exit")
248        self._exit.set()
249
250        # Stop the stream.
251        if self._stream is not None:
252            self._logger.notice("stopping stream...")
253            self._stream.stop_stream()
254            self._stream.close()
255            self._logger.notice("stream stopped")
256
257        # clean up the thread
258        if (
259            # inspect.iscoroutinefunction(self._push_callback_org)
260            # and
261            self._asyncio_thread
262            is not None
263        ):
264            self._logger.notice("stopping _asyncio_loop...")
265            self._asyncio_loop.call_soon_threadsafe(self._asyncio_loop.stop)
266            self._asyncio_thread.join()
267            self._logger.notice("_asyncio_thread joined")
268        self._stream = None
269        self._asyncio_thread = None
270
271        self._logger.notice("finish succeeded")
272        self._logger.debug("Microphone.finish LEAVE")
273
274        return True
275
276    def _callback(
277        self, input_data, frame_count, time_info, status_flags
278    ):  # pylint: disable=unused-argument
279        """
280        The callback used to process data in callback mode.
281        """
282        # dynamic import of pyaudio as not to force the requirements on the SDK (and users)
283        import pyaudio  # pylint: disable=import-outside-toplevel
284
285        if self._exit.is_set():
286            self._logger.notice("_callback exit is Set. stopping...")
287            return None, pyaudio.paAbort
288
289        if input_data is None:
290            self._logger.warning("input_data is None")
291            return None, pyaudio.paContinue
292
293        try:
294            if self._is_muted:
295                size = len(input_data)
296                input_data = b"\x00" * size
297
298            self._push_callback(input_data)
299        except Exception as e:
300            self._logger.error("Error while sending: %s", str(e))
301            raise
302
303        return input_data, pyaudio.paContinue

This implements a microphone for local audio input. This uses PyAudio under the hood.

Microphone( push_callback: Optional[Callable] = None, verbose: int = 30, rate: int = 16000, chunk: int = 8194, channels: int = 1, input_device_index: Optional[int] = None)
44    def __init__(
45        self,
46        push_callback: Optional[Callable] = None,
47        verbose: int = LOGGING,
48        rate: int = RATE,
49        chunk: int = CHUNK,
50        channels: int = CHANNELS,
51        input_device_index: Optional[int] = None,
52    ):  # pylint: disable=too-many-positional-arguments
53        # dynamic import of pyaudio as not to force the requirements on the SDK (and users)
54        import pyaudio  # pylint: disable=import-outside-toplevel
55
56        self._logger = verboselogs.VerboseLogger(__name__)
57        self._logger.addHandler(logging.StreamHandler())
58        self._logger.setLevel(verbose)
59
60        self._exit = threading.Event()
61
62        self._audio = pyaudio.PyAudio()
63        self._chunk = chunk
64        self._rate = rate
65        self._format = pyaudio.paInt16
66        self._channels = channels
67        self._is_muted = False
68
69        self._input_device_index = input_device_index
70        self._push_callback_org = push_callback
def is_active(self) -> bool:
76    def is_active(self) -> bool:
77        """
78        is_active - returns the state of the stream
79
80        Args:
81            None
82
83        Returns:
84            True if the stream is active, False otherwise
85        """
86        self._logger.debug("Microphone.is_active ENTER")
87
88        if self._stream is None:
89            self._logger.error("stream is None")
90            self._logger.debug("Microphone.is_active LEAVE")
91            return False
92
93        val = self._stream.is_active()
94        self._logger.info("is_active: %s", val)
95        self._logger.info("is_exiting: %s", self._exit.is_set())
96        self._logger.debug("Microphone.is_active LEAVE")
97        return val

is_active - returns the state of the stream

Args: None

Returns: True if the stream is active, False otherwise

def set_callback(self, push_callback: Callable) -> None:
 99    def set_callback(self, push_callback: Callable) -> None:
100        """
101        set_callback - sets the callback function to be called when data is received.
102
103        Args:
104            push_callback (Callable): The callback function to be called when data is received.
105                                      This should be the websocket send function.
106
107        Returns:
108            None
109        """
110        self._push_callback_org = push_callback

set_callback - sets the callback function to be called when data is received.

Args: push_callback (Callable): The callback function to be called when data is received. This should be the websocket send function.

Returns: None

def start(self) -> bool:
112    def start(self) -> bool:
113        """
114        starts - starts the microphone stream
115
116        Returns:
117            bool: True if the stream was started, False otherwise
118        """
119        self._logger.debug("Microphone.start ENTER")
120
121        self._logger.info("format: %s", self._format)
122        self._logger.info("channels: %d", self._channels)
123        self._logger.info("rate: %d", self._rate)
124        self._logger.info("chunk: %d", self._chunk)
125        # self._logger.info("input_device_id: %d", self._input_device_index)
126
127        if self._push_callback_org is None:
128            self._logger.error("start failed. No callback set.")
129            self._logger.debug("Microphone.start LEAVE")
130            return False
131
132        if inspect.iscoroutinefunction(self._push_callback_org):
133            self._logger.verbose("async/await callback - wrapping")
134            # Run our own asyncio loop.
135            self._asyncio_thread = threading.Thread(target=self._start_asyncio_loop)
136            self._asyncio_thread.start()
137
138            self._push_callback = lambda data: (
139                asyncio.run_coroutine_threadsafe(
140                    self._push_callback_org(data), self._asyncio_loop
141                ).result()
142                if self._push_callback_org
143                else None
144            )
145        else:
146            self._logger.verbose("regular threaded callback")
147            self._asyncio_thread = None
148            self._push_callback = self._push_callback_org
149
150        if self._audio is not None:
151            self._stream = self._audio.open(
152                format=self._format,
153                channels=self._channels,
154                rate=self._rate,
155                input=True,
156                output=False,
157                frames_per_buffer=self._chunk,
158                input_device_index=self._input_device_index,
159                stream_callback=self._callback,
160            )
161
162        if self._stream is None:
163            self._logger.error("start failed. No stream created.")
164            self._logger.debug("Microphone.start LEAVE")
165            return False
166
167        self._exit.clear()
168        if self._stream is not None:
169            self._stream.start_stream()
170
171        self._logger.notice("start succeeded")
172        self._logger.debug("Microphone.start LEAVE")
173        return True

starts - starts the microphone stream

Returns: bool: True if the stream was started, False otherwise

def mute(self) -> bool:
175    def mute(self) -> bool:
176        """
177        mute - mutes the microphone stream
178
179        Returns:
180            bool: True if the stream was muted, False otherwise
181        """
182        self._logger.verbose("Microphone.mute ENTER")
183
184        if self._stream is None:
185            self._logger.error("mute failed. Library not initialized.")
186            self._logger.verbose("Microphone.mute LEAVE")
187            return False
188
189        self._is_muted = True
190
191        self._logger.notice("mute succeeded")
192        self._logger.verbose("Microphone.mute LEAVE")
193        return True

mute - mutes the microphone stream

Returns: bool: True if the stream was muted, False otherwise

def unmute(self) -> bool:
195    def unmute(self) -> bool:
196        """
197        unmute - unmutes the microphone stream
198
199        Returns:
200            bool: True if the stream was unmuted, False otherwise
201        """
202        self._logger.verbose("Microphone.unmute ENTER")
203
204        if self._stream is None:
205            self._logger.error("unmute failed. Library not initialized.")
206            self._logger.verbose("Microphone.unmute LEAVE")
207            return False
208
209        self._is_muted = False
210
211        self._logger.notice("unmute succeeded")
212        self._logger.verbose("Microphone.unmute LEAVE")
213        return True

unmute - unmutes the microphone stream

Returns: bool: True if the stream was unmuted, False otherwise

def is_muted(self) -> bool:
215    def is_muted(self) -> bool:
216        """
217        is_muted - returns the state of the stream
218
219        Args:
220            None
221
222        Returns:
223            True if the stream is muted, False otherwise
224        """
225        self._logger.spam("Microphone.is_muted ENTER")
226
227        if self._stream is None:
228            self._logger.spam("is_muted: stream is None")
229            self._logger.spam("Microphone.is_muted LEAVE")
230            return False
231
232        val = self._is_muted
233
234        self._logger.spam("is_muted: %s", val)
235        self._logger.spam("Microphone.is_muted LEAVE")
236        return val

is_muted - returns the state of the stream

Args: None

Returns: True if the stream is muted, False otherwise

def finish(self) -> bool:
238    def finish(self) -> bool:
239        """
240        finish - stops the microphone stream
241
242        Returns:
243            bool: True if the stream was stopped, False otherwise
244        """
245        self._logger.debug("Microphone.finish ENTER")
246
247        self._logger.notice("signal exit")
248        self._exit.set()
249
250        # Stop the stream.
251        if self._stream is not None:
252            self._logger.notice("stopping stream...")
253            self._stream.stop_stream()
254            self._stream.close()
255            self._logger.notice("stream stopped")
256
257        # clean up the thread
258        if (
259            # inspect.iscoroutinefunction(self._push_callback_org)
260            # and
261            self._asyncio_thread
262            is not None
263        ):
264            self._logger.notice("stopping _asyncio_loop...")
265            self._asyncio_loop.call_soon_threadsafe(self._asyncio_loop.stop)
266            self._asyncio_thread.join()
267            self._logger.notice("_asyncio_thread joined")
268        self._stream = None
269        self._asyncio_thread = None
270
271        self._logger.notice("finish succeeded")
272        self._logger.debug("Microphone.finish LEAVE")
273
274        return True

finish - stops the microphone stream

Returns: bool: True if the stream was stopped, False otherwise