
    @iQ              
          U d Z ddlZddlmZmZmZmZ ddlZddlZddl	m
Z
 ddlZddlZddlZddlmZ 	 ddlZ eed      s
d Zee_        	 dd	lmZ dZd
ej6                  dej6                  defdZdedededeej6                  ef   fdZ d'dej6                  dededej6                  fdZ!da"ee   e#d<    ejH                         Z% e
e&      jO                         jP                  d   dz  Z)	 e)jU                  dd      5 Z+ ejX                  e+      Z-ddd        e.e-j_                  d      xs d      ja                         Z1e1re1a2e.e#d<   n ed      a2e.e#d<    ee-j_                  dd            Z3d(d e.defd!Z4d(dej6                  ded e.dej6                  fd"Z5d)d ee.   ddfd#Z6 G d$ d%      Z7d%d&gZ8y# e$ r dZY zw xY w# e$ r$ 	 dd	lmZ dZn# e$ rZdZeZY dZ[ndZ[ww xY wY w xY w# 1 sw Y   xY w# e$ r i Z-Y w xY w)*u  轻量级内存说话人注册表，用于处理 PCM 切片。

- 在内存中维护说话人 embedding 缓存。
- 给定 PCM 切片，如果相似度超过阈值则返回已有说话人 ID；
    否则分配新的 ID 并缓存。
- 设计为被 `gzzm.app` 导入以支持下游的说话人分离逻辑。

注意：当前使用一个简单的 embedding（对帧求均值和标准差）作为占位实现。
如有更好的说话人模型，请替换 `_extract_embedding` 以提取真实 embedding。
    N)ListOptionalTupleUnion)Path   )choose_devicelist_audio_backendsc                      g S N r       /app/gzzm/utils/speaker_id.py_dummy_backendsr      s    Ir   )SpeakerRecognitionabreturnc                     t         j                  j                  |       t         j                  j                  |      z  dz   }t        t        j                  | |      |z        S )N:0yE>)nplinalgnormfloatdot)r   r   denoms      r   _cosine_simr   5   sE    YY^^A!22d:E1%&&r   datasample_ratechannelsc                    t        |       dz  dk7  rt        d      |dk  rt        d      |dk  rt        d      t        j                  | t        j                        }|dkD  r\|j
                  |z  dk7  rt        d      |j                  d	|      j                  d
      j                  t        j                        }|j                  t        j                        dz  }||fS )N   r   z'PCM bytes length must be even for int16zInvalid sample_ratezInvalid channelsdtyper   z"PCM size not divisible by channelsaxisg      @)
len
ValueErrorr   
frombufferint16sizereshapemeanastypefloat32)r   r   r    pcmwavs        r   _pcm_bytes_to_mono_float32r3   :   s    
4y1}BCCa.//1}+,,
--BHH
-C!|88h!#ABBkk"h',,!,4;;BHHE
**RZZ
 7
*Cr   r2   src_srdst_src           	         | j                   dk(  r%t        j                  dt        j                        S ||k(  r!| j	                  t        j                  d      S | j
                  d   t        |      z  }t        dt        t        ||z                    }t        j                  d|| j
                  d   d      }t        j                  d||d      }t        j                  |||       j	                  t        j                        S )	Nr   )r   r#   F)copyr           )numendpoint)r,   r   zerosr0   r/   shaper   maxintroundlinspaceinterp)r2   r4   r5   durout_lenx_oldx_news          r   _resample_linearrF   K   s    
xx1}xxBJJ//zz"**5z11
))A,v
&C!SsV|,-.GKKSciilUCEKKSg>E99UE3'..rzz::r   _MODELgzzm_config.jsonrutf-8encodingmodel_device_speaker _MODEL_DEVICE speaker_initial_baseline_seconds      $@devicec                     t         t        d      t        t        5  t        	| t
        k7  rt        j                  dd| i      a| at        cd d d        S # 1 sw Y   y xY w)Nz.speechbrain is required for speaker embeddingsz!speechbrain/spkrec-ecapa-voxcelebrR   )sourcerun_opts)r   ImportError_IMPORT_ERROR_MODEL_LOCKrG   rO   from_hparamsrR   s    r   
_get_modelr[   l   sY    !JKQ^^ 
 N- 7'44:"F+F #M 
s   0AA c                 V   | j                   dk(  r%t        j                  dt        j                        S t	        | |d      }t        |      }t        j                  |      j                  d      }t        j                         5  |j                  |j                  |            }ddd       j                         j                         j                         j                  t        j                        }t        j                   j#                  |      dz   }||z  S # 1 sw Y   yxY w)uG   通过 SpeechBrain 提取 ECAPA embedding（spkrec-ecapa-voxceleb）。r      r#   >  rZ   Nr   )r,   r   r;   r0   rF   r[   torch
from_numpy	unsqueezeno_gradencode_batchtosqueezecpunumpyr/   r   r   )	r2   r   rR   wav16modeltensorembemb_npr   s	            r   _extract_embeddingrm   ~   s    
xx1}xx2::..S+u5Ef%Ee$..q1F	  6!23 
[[] &&(//

;F99>>&!D(DD= 
s   !DD(c                 ,    t        | xs t               y)zLPreload speaker recognition model at startup to avoid first-request latency.rZ   N)r[   rO   rZ   s    r   preload_speaker_modelro      s    f-.r   c                      e Zd Z	 	 d edddddddeded	ed
ee   dededededdfdZ		 	 	 d!de
dededee   dedeeeef   z  fdZdej                  defdZdedefdZdee   dee   fdZ	 	 d"de
dededee   def
dZd#dZdefdZy)$SpeakerRegistryNgMbP?i  g333333?T)model_device disable_soft_reuse_for_persistedenergy_thresholdmin_duration_ms	agg_alphaupdate_on_matchsimilarity_thresholdsoft_marginrr   rs   rt   ru   rv   rw   r   c          	      0   	 g | _         g | _        d| _        t        j                         | _        || _        t        d|      | _        |	 t        t              j                         j                  d   dz  }	|	j                  dd      5 }
t        j                  |
      }ddd       t#        j%                  d|j%                  d	d
                  }t#        |      | _        t)        |      | _        t-        |      | _        t)        |      | _        t#        |      | _        t5        |      | _        g | _        	 t)        t:        j%                  dd            dz  | _        t5        t:        j%                  dt:        j%                  dd                  }t        |      | _        	 | j>                  jA                  dd       tC               | _"        y# 1 sw Y   5xY w# t         $ r i }Y Fw xY w# t         $ r
 d| _        Y w xY w# t         $ r Y Pw xY w)u  内存中说话人注册表构造函数。

        新增参数说明（仅列出新增参数的含义与推荐档位）：

        参数（新增）:
            energy_threshold: 当片段的 RMS 能量低于该阈值时视为静音并跳过。值越大越严格（更多片段被视为静音）。
            min_duration_ms: 只有时长 >= 该值（毫秒）的片段才会被用于提取 embedding。增大可减少短切片带来的噪声。
            agg_alpha: 命中后使用移动平均聚合缓存 embedding 时新 embedding 的权重，范围 0.0-1.0。
            update_on_match: 命中后是否更新缓存 embedding（启用可逐步聚合说话人特征）。

        推荐档位（示例，按实际数据/场景调整）:
            - 高速度（低延迟/更少模型调用）:
                energy_threshold=5e-3, min_duration_ms=800, agg_alpha=0.6, update_on_match=False
            - 均衡（默认，实时场景常用）:
                energy_threshold=1e-3, min_duration_ms=400, agg_alpha=0.6, update_on_match=True
            - 高精确度（更敏感，适合离线或批处理）:
                energy_threshold=5e-4, min_duration_ms=250, agg_alpha=0.3, update_on_match=True

        使用建议:
            - 实时低延迟服务优先选择“高速度”或“均衡”。
            - 做离线评估或要求较高识别质量时使用“高精确度”。
            - 在引入这些值后，请在目标环境下用小样本集合验证并微调阈值。
        r   r8   NrH   rI   rJ   rK   speaker_disable_soft_reusers   FrP   rQ        @@     @speaker_store_dirspeaker_storeT)parentsexist_ok)#_embs_ids_next_id	threadingLock_lock_thrr=   _softr   __file__resolver   openjsonload	Exceptionboolget_disable_soft_for_persistedr   _energy_thrr>   _min_dur_ms
_agg_alpha_update_on_matchstr_model_device
_durations_GZZM_CONFIG_initial_baseline_ms
_store_dirmkdirset_persisted_ids)selfrx   ry   rr   rs   rt   ru   rv   rw   cfg_path_f_cfgstores                r   __init__zSpeakerRegistry.__init__   s   L	
 (*
+-	^^%
(	k*
 ,3>113;;A>ASS]]3]9R99R=D : 045txx@bdi7jk0, ,00P+Q( !!12/  	* $_ 5 !. (*	0(-l.>.>?acg.h(ilr(rD%
 L$$%8,:J:J?\k:lmnu+	OO!!$!>
 "eQ :9 4  	0(/D%	0  		sO   A G! G!G! <'G3 'H	 GG! !G0/G03HH		HH	pcm_bytesr   r    rR   
return_simc           	         t        |||      \  }}|xs | j                  }|j                  dk(  rd}d}	nmt        t	        j
                  t	        j                  t	        j                  |      d                  }t        |j                  d         t        |      z  dz  }	|| j                  k  s|	t        | j                        k  r|rdS dS t        |||      }
t        j                  j                  |
      dk  r|rdS dS | j                  5  d	}d
}d	}t        t!        | j"                  | j$                              D ]   \  }\  }}t'        |
|      }||kD  s|}|}|}" |R|| j(                  k\  rB| j*                  r$|!|t-        | j.                        k  r| j.                  |   nd}|dkD  r-|	dkD  r(t        |	      |	|z   z  }t1        dt3        |d            }n t1        dt3        d| j4                              }||
z  d|z
  | j$                  |   z  z   }t        j                  j                  |      dz   }||z  j7                  t        j8                        | j$                  |<   	 |t-        | j.                        k  r| j.                  |   nd|	z   | j.                  |<   |r||fn|cd	d	d	       S | j>                  dz  }d}| j@                  r|| jB                  v rd}|X|rU|| j(                  |z
  k\  rB| j*                  r$|!|t-        | j.                        k  r| j.                  |   nd}|dkD  r-|	dkD  r(t        |	      |	|z   z  }t1        dt3        |d            }n t1        dt3        d| j4                              }||
z  d|z
  | j$                  |   z  z   }t        j                  j                  |      dz   }||z  j7                  t        j8                        | j$                  |<   	 |t-        | j.                        k  r| j.                  |   nd|	z   | j.                  |<   |r||fn|cd	d	d	       S |\|Y|t-        | j.                        k  r| j.                  |   nd}t1        d| j(                  | j>                  dz  z
        }|	| jD                  k\  r||	k  r||k\  r| j*                  r|dkD  rt        |	      |	|z   z  nd}t1        dt3        |d            }||
z  d|z
  | j$                  |   z  z   }t        j                  j                  |      dz   }||z  j7                  t        j8                        | j$                  |<   	 |t-        | j.                        k  r| j.                  |   nd|	z   | j.                  |<   |r||fn|cd	d	d	       S | jF                  }| xjF                  dz  c_#        | j"                  j=                  |       | j$                  j=                  |
       	 | j.                  j=                  |	       |r||fn|cd	d	d	       S # t:        $ rG |t-        | j.                        k  r|	| j.                  |<   n| j.                  j=                  |	       Y w xY w# t:        $ rG |t-        | j.                        k  r|	| j.                  |<   n| j.                  j=                  |	       Y w xY w# t:        $ rG |t-        | j.                        k  r|	| j.                  |<   n| j.                  j=                  |	       Y w xY w# t:        $ r | j.                  |	gz   | _        Y +w xY w# 1 sw Y   y	xY w)u  如果相似度超过阈值则返回已有说话人 ID，否则新增并返回新 ID。

        参数：
            pcm_bytes: 原始 PCM int16 字节流。
            sample_rate: PCM 的采样率。
            channels: 通道数。
            device: 用于说话人模型的 torch 设备字符串（"cpu" | "cuda" | "cuda:0" ...）。
            return_sim: 如果为 True，同时返回最佳相似度分数。
        r   r8   r&   r|   )r%   r8   r%   rZ   gư>Ng      g?gffffff?g      ?r   g      ?TFg?r"   r   )$r3   r   r,   r   r   sqrtr.   squarer<   r   r   rm   r   r   r   	enumeratezipr   r   r   r   r   r(   r   r=   minr   r/   r0   r   appendr   r   r   r   r   )r   r   r   r    rR   r   r2   srrmsdur_msrk   best_idbest_simbest_idxidx	cached_id
cached_embsim
stored_durr   new_embr   effective_softcan_soft_reusemin_enhance_simnew_ids                             r   register_or_matchz!SpeakerRegistry.register_or_match   s   " -YXNR -4-- 88q=CF		#Q ?@AC399Q<(594v=F$"""t7G7G1H(H *922 b899>>#% *922ZZ15GH&*H09#dii:T0U,,i!#z2>"H'G"H 1V "x499'<((X-A>FT__I]>]!:cfJ!A~&1*!&MVj-@Ac!Tl3Sdoo%>? 3wC!Gtzz(7K+KLG99>>'2T9D,3dN+B+B2::+NDJJx(;RZ]`aeapap]qRqT__X5Nwz  E  5E1 /9*gC ZH "ZZ#-N!N//W@S@S5S!&"~(tyySaGa:b((X-A>FT__I]>]!:cfJ!A~&1*!&MVj-@Ac!Tl3Sdoo%>? 3wC!Gtzz(7K+KLG99>>'2T9D,3dN+B+B2::+NDJJx(;RZ]`aeapap]qRqT__X5Nwz  E  5E1 /9*gw Z~ "x';:BSEY:YT__X6_b
"%c499

Q+G"HT666:;NS[_nSn,,EORS^E&MVj-@AY\c!Tl3#$s7a4::h;O/O"P!yy~~g6=07$/F/Frzz/R

8,?V^adeietetauVu9R{~  CI  9IDOOH5 3=GX.'a Zf ]]FMMQMIIV$JJc"=&&v. *4FH%w Z6 % ;#c$//&::8>DOOH5 OO226:;6 % ;#c$//&::8>DOOH5 OO226:	;*  ) ?'#doo*>><B 9 $ 6 6v >	?  ="&//VH"<=s Zs   A
]D]/9X$(]9D=]79Y70]D]9[
] A]8\]$AY40]3Y44]7A[][]
A\]\]\?;]>\??]]rk   c                     t        t        j                               }| j                  | dz  }	 t	        j
                  ||       |S # t        $ r  w xY w)u>   将 embedding 持久化到磁盘并返回其 UUID 字符串。.npy)r   uuiduuid4r   r   saver   )r   rk   uidpaths       r   persist_embeddingz!SpeakerRegistry.persist_embeddingy  sT    $**,C5-	GGD# 
  		s   A	 	Ar   c                    |sy| j                   | dz  }|j                         sy	 t        j                  |      }| j
                  5  || j                  v r
	 ddd       y| j                  j                  |       |j                  t        j                        }t        j                  j                  |      dz   }|dk7  r||z  }| j                  j                  |       | j                  j                  |       	 | j                  j                  | j                          ddd       y# t        $ r Y yw xY w# t        $ r | j                  j                  d       Y >w xY w# 1 sw Y   yxY w)uU   通过 UUID 将持久化的 embedding 加载到内存缓存；成功则返回 True。Fr   NTr   r   r}   )r   existsr   r   r   r   r   r   r/   r0   r   r   r   r   addr   r   )r   r   r   rk   r   s        r   load_persistedzSpeakerRegistry.load_persisted  s4   C5-{{}	''$-C ZZdii Z IIS!**RZZ(C99>>#&-DqyDjJJc"##C(0&&t'@'@A " '  		"  0&&w/0 " sB   D- 	E&"BE&?%D<-	D98D9<$E# E&"E##E&&E/uidsc                     g }|xs g D ](  }	 | j                  |      }|s|j                  |       * |S # t        $ r d}Y %w xY w)uC   尝试加载多个持久化 id；返回成功加载的 id 列表。F)r   r   r   )r   r   loadedr   oks        r   load_persisted_bulkz#SpeakerRegistry.load_persisted_bulk  sZ    :2:C((- c"  	  s   6AAc                     t        |||      \  }}|xs | j                  }t        |||      }| j                  |      }	 | j	                  |       |S # t
        $ r Y |S w xY w)ue   从原始 PCM 字节提取 embedding，持久化到磁盘，加载到内存缓存，并返回 UUID。rZ   )r3   r   rm   r   r   r   )	r   r   r   r    rR   r2   r   rk   r   s	            r   persist_from_pcmz SpeakerRegistry.persist_from_pcm  sw     -YXNR-4-- b8$$S)	$ 
  	
	s   A 	A! A!c                    | j                   5  | j                  j                          | j                  j                          d| _        	 | j
                  j                          d d d        y # t        $ r Y w xY w# 1 sw Y   y xY w)Nr   )r   r   clearr   r   r   r   r   s    r   r   zSpeakerRegistry.clear  sf    ZZJJIIOODM%%' Z   Zs)   <A<
A--	A96A<8A99A<<Bc                     | j                   5  t        | j                        t        | j                        | j                  dcd d d        S # 1 sw Y   y xY w)N)countids	threshold)r   r(   r   listr   r   s    r   statszSpeakerRegistry.stats  s5    ZZTYYDII!YY ZZs   6AA)g      ?g333333?)r   NF)r   N)r   N)__name__
__module____qualname__rO   r   r   r   r   r>   r   bytestupler   r   ndarrayr   r   r   r   r   r   dictr   r   r   r   rq   rq      s    '+ _$
 * <@"&" $_$#_$ _$
 _$ +34._$  _$ _$ _$ _$ 
_$J  $ B@B@ B@ 	B@
 B@ B@ 
uS%Z 	 B@H	RZZ 	C 	# $ <
S	 
d3i 
   $  	
  
&t r   rq   ro   )r^   )rf   r   )9__doc__r   typingr   r   r   r   r   ospathlibr   r   rg   r   r_   device_utilsr	   
torchaudiohasattrr   r
   r   speechbrain.inferencer   rW   speechbrain.pretrainedexcr   r   r   r   r>   r3   rF   rG   __annotations__r   rX   r   r   r   _config_pathr   r   r   r   r   r   strip_cfg_devrO   _INITIAL_BASELINE_SECONDSr[   rm   ro   rq   __all__r   r   r   <module>r      s  	  / /  	     '	:45	 *9
&8 M
'2:: '"** ' '
U   PUVXV`V`beVePf "	;"** 	;c 	;3 	;2:: 	; (,#$ +inn H~%%'//25GG			3		1R tyy} 
2 | 67=2>DDF!M3!&r*M3*!,"2"23UW["\] s '9 $BJJ S # RTR\R\  /(3- /4 /
~ ~B
 5
6g  J
  =
 	  !	n 
2	1 Lsw   F< G
 1H G6H <GG
G3GG3G,G'"G3'G,,G32G36G?;H HH