| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import argparse |
| import os |
| import time |
|
|
| import numpy as np |
|
|
| from campplus_sdk import CampplusModel, chunk, cosine_similarity |
| from campplus_sdk.inference import load_wav |
|
|
|
|
| def parse_args(): |
| parser = argparse.ArgumentParser(description="CAMPPlus speaker embedding demo") |
| parser.add_argument("--models-dir", type=str, default="models", |
| help="Directory containing campplus.axmodel") |
| parser.add_argument("--wav1", type=str, default=None, |
| help="First wav for 1:1 speaker verification") |
| parser.add_argument("--wav2", type=str, default=None, |
| help="Second wav for 1:1 speaker verification") |
| parser.add_argument("--audio", type=str, default=None, |
| help="Audio for chunked embedding extraction / diarization") |
| parser.add_argument("--diarize", action="store_true", |
| help="Run speaker clustering (requires clustering deps)") |
| parser.add_argument("--speaker-num", type=int, default=0, |
| help="Known speaker count (0 = auto, requires clustering deps)") |
| return parser.parse_args() |
|
|
|
|
| def load_wav_16k(wav_file): |
| """Read wav, resample if needed, return mono [T] tensor.""" |
| return load_wav(wav_file, target_sr=16000) |
|
|
|
|
| def main(): |
| args = parse_args() |
| if not args.wav1 and not args.audio: |
| raise SystemExit("Specify --wav1/--wav2 or --audio (see --help)") |
|
|
| t0 = time.time() |
| model = CampplusModel(args.models_dir) |
| print(f"Model loaded in {time.time() - t0:.2f}s") |
|
|
| if args.audio: |
| speech = load_wav_16k(args.audio) |
| speech = speech.numpy() |
| if speech.ndim > 1: |
| speech = speech[0] |
| fs = 16000 |
| audio_duration = len(speech) / fs |
| print(f"Audio duration: {audio_duration:.2f}s") |
|
|
| |
| chunks = chunk(0, audio_duration) |
| t1 = time.time() |
| embeddings = model(speech, fs, chunks=chunks) |
| cost = time.time() - t1 |
| print(f"Embedding extraction: {cost:.2f}s for {len(chunks)} chunks " |
| f"({cost / max(len(chunks), 1) * 1000:.2f} ms/chunk)") |
| print(f"Embeddings shape: {embeddings.shape}") |
|
|
| if args.diarize: |
| try: |
| from campplus_sdk.clustering import do_clustering |
| except ImportError as e: |
| raise SystemExit( |
| f"clustering deps missing ({e}); run: " |
| "pip install scipy scikit-learn fastcluster umap-learn hdbscan") |
| t1 = time.time() |
| speaker_num, diar_results = do_clustering( |
| chunks, embeddings, |
| speaker_num=args.speaker_num if args.speaker_num else None) |
| print(f"Clustering: {time.time() - t1:.2f}s, " |
| f"speakers: {speaker_num}") |
| for seg_st, seg_ed, spk in diar_results: |
| print(f" Speaker_{spk}: [{seg_st:.2f} {seg_ed:.2f}]") |
| return |
|
|
| |
| wav1 = load_wav_16k(args.wav1) |
| wav2 = load_wav_16k(args.wav2) |
| emb1 = model.extract(wav1) |
| emb2 = model.extract(wav2) |
| sim = cosine_similarity(emb1, emb2) |
| print(f"\n{args.wav1} vs {args.wav2}") |
| print(f"cosine similarity: {sim:.4f}") |
| print("note: the higher the similarity, the more likely the two audios " |
| "belong to the same speaker") |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|