Module livekit.plugins.ai_coustics.plugin
Functions
def to_native_buffer(data: memoryview) ‑> tuple[numpy.ndarray, livekit.plugins.ai_coustics._ffi.NativeAudioBufferMut]-
Expand source code
def to_native_buffer(data: memoryview) -> tuple[np.ndarray, NativeAudioBufferMut]: """ Convert frame.data (int16 memoryview) to NativeAudioBufferMut (f32 pointer). Returns both the numpy array (to keep it alive) and the NativeAudioBufferMut. """ # Convert int16 to float32 in range [-1.0, 1.0] # astype() creates a copy, which is writable by default samples = ( np.frombuffer(data, dtype=np.int16).astype(np.float32, copy=True) / 32768.0 ) # Get the memory address directly from the numpy array ptr_value = samples.ctypes.data # Create NativeAudioBufferMut pointing to the numpy memory native_buffer = NativeAudioBufferMut( ptr=ptr_value, len=len(samples), # Number of f32 samples ) return samples, native_bufferConvert frame.data (int16 memoryview) to NativeAudioBufferMut (f32 pointer). Returns both the numpy array (to keep it alive) and the NativeAudioBufferMut.
Classes
class AICousticsAudioEnhancer (*,
model: livekit.plugins.ai_coustics._ffi.EnhancerModel,
vad_settings: livekit.plugins.ai_coustics._ffi.VadSettings,
model_parameters: ModelParameters | None = None,
auth: AuthBase | None = None)-
Expand source code
class AICousticsAudioEnhancer(rtc.FrameProcessor[rtc.AudioFrame]): def __init__( self, *, model: EnhancerModel, vad_settings: VadSettings, model_parameters: Optional[ModelParameters] = None, auth: Optional[AuthBase] = None, ) -> None: self._model = model self._vad_settings = vad_settings self._model_parameters = model_parameters self._auth = auth or Auth.livekit_cloud() self._last_error_msg: Optional[str] = None self._enhancer: Enhancer | None = None self._info: StreamInfo | None = None self._credentials: Credentials | None = None self._settings: EnhancerSettings | None = None self._enabled = True @property def enabled(self) -> bool: return self._enabled @enabled.setter def enabled(self, value: bool) -> None: self._enabled = value def update_model_parameters(self, model_parameters: ModelParameters): """ Updates the model parameters on the running model. The native core must already exist (i.e. at least one audio frame must have been processed) for the update to take effect; otherwise the call is a no-op and a warning is logged. The new parameters are also stored so they are reapplied if the native core is later recreated (e.g. on a sample-rate or channel change). """ if not self._enhancer: logger.warning("update_model_parameters: Native core not yet initialized, skipping. Process at least one audio frame first.") return new_uniffi = model_parameters._to_uniffi() current_uniffi = ( self._model_parameters._to_uniffi() if self._model_parameters is not None else ModelParametersUniffi(bypass=None, enhancement_level=None) ) if model_parameters_equal(new_uniffi, current_uniffi): return self._model_parameters = model_parameters self._enhancer.update_model_parameters(new_uniffi) def _on_stream_info_updated( self, *, room_name: str, participant_identity: str, publication_sid: str ): self._info = StreamInfo( room_id="", room_name=room_name, participant_identity=participant_identity, participant_id="", track_id=publication_sid, ) if self._enhancer is not None: self._enhancer.update_stream_info(self._info) def _on_credentials_updated(self, *, token: str, url: str): self._credentials = Credentials(token=token, url=url) if self._enhancer is not None: self._enhancer.update_credentials(self._credentials) def _process(self, frame: rtc.AudioFrame) -> rtc.AudioFrame: """ Processes a single audio frame. If the frame processor is disabled or processing fails, the original frame is returned unchanged. """ if not self.enabled: return frame auth_mode = self._auth._to_auth_mode(self._credentials) if auth_mode is None: self._log_process_frame_error("Missing auth mode") return frame if self._auth_mode_requires_credentials() and not self._credentials: self._log_process_frame_error("Missing credentials") return frame if self._auth_mode_requires_stream_info() and self._info is None: self._log_process_frame_error("Missing stream info") return frame ## lazily create enhancer if self._enhancer is None or ( ## implicitly recreate audio enhancer on sample rate or channel changes self._settings is not None and ( self._settings.sample_rate != frame.sample_rate or self._settings.num_channels != frame.num_channels or self._settings.samples_per_channel != frame.samples_per_channel ) ): self._settings = EnhancerSettings( sample_rate=frame.sample_rate, num_channels=frame.num_channels, samples_per_channel=frame.samples_per_channel, model=self._model, model_parameters=self._model_parameters._to_uniffi() if self._model_parameters else ModelParametersUniffi(bypass=None, enhancement_level=None), vad=self._vad_settings ) try: self._enhancer = Enhancer(auth_mode, self._settings) except EnhancerError as e: self._log_process_frame_error(f"Failed to initialize plugin core: {e} - Disabling noise cancellation for all following audio frames.") self._enhancer = None self._enabled = False return frame if self._info is not None: self._enhancer.update_stream_info(self._info) # Convert frame.data to NativeAudioBufferMut (f32) # Keep samples alive during the process call samples, native_buffer = to_native_buffer(frame.data) # Process in-place (modifies samples array) try: vad_data = self._enhancer.process_with_vad(native_buffer) except EnhancerError as e: self._log_process_frame_error(f"Processing failed: {e}") return frame # Convert back to int16 and create new frame processed_int16 = (np.clip(samples, -1.0, 1.0) * 32767.0).astype(np.int16) output_frame = rtc.AudioFrame( data=processed_int16.tobytes(), sample_rate=frame.sample_rate, num_channels=frame.num_channels, samples_per_channel=frame.samples_per_channel, userdata=frame.userdata, ) output_frame.userdata[FRAME_USERDATA_AIC_VAD_ATTRIBUTE] = vad_data return output_frame def _auth_mode_requires_stream_info(self) -> bool: """Does the given auth mode require update_stream_info be called?""" return isinstance(self._auth, LiveKitCloud) def _auth_mode_requires_credentials(self) -> bool: """ Does the given auth mode require update_credentials be called? Note that this is just here to provide helpful warnings to users, the actual auth layer is in the rust core. """ return isinstance(self._auth, LiveKitCloud) def _log_process_frame_error(self, msg: str): """ Logs a new error to the screen when processing a frame. Only shows logs which were newly introduced as compared with the last processed frame. """ if self._last_error_msg == msg: return self._last_error_msg = msg logger.error(msg) def _close(self): if self._enhancer is not None: self._enhancer = NoneAbstract base class for generic types.
On Python 3.12 and newer, generic classes implicitly inherit from Generic when they declare a parameter list after the class's name::
class Mapping[KT, VT]: def __getitem__(self, key: KT) -> VT: ... # Etc.On older versions of Python, however, generic classes have to explicitly inherit from Generic.
After a class has been declared to be generic, it can then be used as follows::
def lookup_name[KT, VT](mapping: Mapping[KT, VT], key: KT, default: VT) -> VT: try: return mapping[key] except KeyError: return defaultAncestors
- FrameProcessor
- typing.Generic
- abc.ABC
Instance variables
prop enabled : bool-
Expand source code
@property def enabled(self) -> bool: return self._enabled
Methods
def update_model_parameters(self,
model_parameters: ModelParameters)-
Expand source code
def update_model_parameters(self, model_parameters: ModelParameters): """ Updates the model parameters on the running model. The native core must already exist (i.e. at least one audio frame must have been processed) for the update to take effect; otherwise the call is a no-op and a warning is logged. The new parameters are also stored so they are reapplied if the native core is later recreated (e.g. on a sample-rate or channel change). """ if not self._enhancer: logger.warning("update_model_parameters: Native core not yet initialized, skipping. Process at least one audio frame first.") return new_uniffi = model_parameters._to_uniffi() current_uniffi = ( self._model_parameters._to_uniffi() if self._model_parameters is not None else ModelParametersUniffi(bypass=None, enhancement_level=None) ) if model_parameters_equal(new_uniffi, current_uniffi): return self._model_parameters = model_parameters self._enhancer.update_model_parameters(new_uniffi)Updates the model parameters on the running model.
The native core must already exist (i.e. at least one audio frame must have been processed) for the update to take effect; otherwise the call is a no-op and a warning is logged. The new parameters are also stored so they are reapplied if the native core is later recreated (e.g. on a sample-rate or channel change).
class ModelParameters (enhancement_level: float | None = None, bypass: float | None = None)-
Expand source code
@dataclass class ModelParameters: enhancement_level: Optional[float] = None bypass: Optional[float] = None def _to_uniffi(self): return ModelParametersUniffi( enhancement_level=self.enhancement_level, bypass=self.bypass, )ModelParameters(enhancement_level: Optional[float] = None, bypass: Optional[float] = None)
Instance variables
var bypass : float | Nonevar enhancement_level : float | None