资讯详情

资讯详情

基于Matlab的说话人识别系统:从MFCC特征提取到GMM-UBM建模全解析

简介本资源是一套基于MATLAB实现的说话人语音识别完整源码工程面向信号处理、语音识别方向的本科生、研究生及算法工程师解决声纹特征提取、建模与身份判别等核心问题适用于智能家居认证、语音控制系统等实际场景。压缩包共940个文件含534段.wav语音样本用于训练与测试、259个.m主程序与函数脚本涵盖预处理、MFCC提取、SVM/神经网络分类等关键模块、143个.mat模型与特征数据文件另有PPT原理说明、PDF技术文档及exe可执行演示程序整体大小23.54MB。已有90人学习下载资源结构清晰模块解耦明确从音频采集、噪声抑制、梅尔谱分析到高斯混合模型GMM与支持向量机SVM分类器训练全流程覆盖附带psycest、gaussmix、spgrambw等典型语音处理函数便于理解算法原理并快速二次开发。1. 项目概述与核心价值最近在整理硬盘时翻到了一个老项目——“基于matlab实现的说话人语音识别源码.zip”。这让我想起了当年刚接触语音信号处理时自己动手从零搭建一个说话人识别系统的经历。说话人识别简单来说就是让机器“听声识人”判断一段语音是谁说的。这和我们常说的语音识别识别说的是什么内容是两码事它更关注说话人本身的声纹特征有点像声音的“指纹”。这个Matlab源码包就是一个完整的、可用于学习和研究的说话人识别系统实现。对于学生、研究者或者刚入行的工程师来说直接拿到这样一个能跑通的完整源码价值非常大。它不像论文只给个框图也不像某些商业库那样封装得严严实实。这个项目把特征提取、模型训练、识别测试的整个链条都清晰地展现了出来你不仅能知道结果更能看到每一步的计算细节。无论是想理解MFCC梅尔频率倒谱系数到底是怎么算出来的还是想弄明白GMM高斯混合模型是怎么对声纹建模的都可以通过阅读和修改这些代码来获得最直观的认识。接下来我就结合这个源码包把说话人识别从原理到实现的各个环节拆解清楚并分享一些我当年调试时踩过的坑和总结的技巧。2. 说话人识别系统核心原理拆解要理解这套源码首先得搞清楚一个说话人识别系统的基本框架。它通常遵循一个经典的“前端特征提取 后端模型建模”的模式。前端负责从原始的语音波形中提取出能够代表说话人个性、且相对稳定的声学特征后端则利用这些特征为每个说话人建立一个数学模型识别的时候就是计算待测语音特征与各个模型之间的匹配度。2.1 前端处理从声音到特征向量原始语音信号是一维的时域波形它包含了内容、说话人、情绪、环境噪声等各种信息的混合体。我们的目标是把“说话人”这部分信息尽可能地分离和凸显出来。2.1.1 预加重与分帧加窗语音信号的能量通常集中在低频预加重一个一阶高通滤波器可以提升高频部分让频谱变得更平坦便于后续分析。接着因为语音信号是短时平稳的一般认为在10-30毫秒内特性基本不变所以需要把长长的语音流切成一帧一帧来处理。分帧后直接对帧做傅里叶变换会产生频谱泄露因此需要对每一帧乘以一个窗函数比如汉明窗来平滑帧两端的突变。源码里一般会有类似preemphasis,enframe这样的函数。2.1.2 特征提取的核心MFCC这是本项目乃至绝大多数语音识别系统的基石。MFCC的提取过程模拟了人耳的听觉特性步骤非常经典快速傅里叶变换FFT将每一帧时域信号转换为频域频谱得到能量谱。梅尔滤波器组这是关键一步。人耳对不同频率的敏感度不同在低频区分辨率高高频区分辨率低。梅尔刻度是一种非线性频率刻度能模拟这种特性。我们将线性频谱通过一组重叠的三角带通滤波器梅尔滤波器组将频谱能量映射到梅尔刻度上。源码中你会看到一个melFilterBank函数需要指定滤波器个数如26、FFT点数、采样频率等参数。取对数对每个滤波器的输出能量取对数。这既是模仿人耳对声音强度的非线性感知对数响应也为后续的倒谱分析做准备。离散余弦变换DCT对取对数后的滤波器组能量进行DCT变换得到梅尔频率倒谱系数MFCC。DCT起到了“解相关”的作用并且通常只保留前12-13个系数因为高阶系数代表快速的频谱变化与说话人特性关系不大且容易受噪声影响。此外通常还会加上一阶和二阶差分Delta和Delta-Delta来表征特征的动态变化信息。注意在源码中一定要检查MFCC的提取流程是否完整特别是对数运算和DCT变换的顺序。我曾见过有的实现漏了取对数导致后续模型完全无法收敛。2.2 后端建模如何用数学描述一个人的声音提取出每帧的MFCC特征后假设是13维加上一阶二阶差分就是39维我们就得到了一串特征向量序列。如何用它们来代表一个人呢这就需要用概率统计模型来建模。2.2.1 高斯混合模型GMM的原理这套源码很可能采用GMM作为说话人模型。为什么是GMM因为一个人的语音特征分布在特征空间里并不是一个规则的球或椭圆而可能是由多个“簇”组成的复杂形状。比如发元音时的特征分布是一个簇发辅音时是另一个簇。GMM恰恰就是用来描述这种由多个高斯分布线性组合而成的复杂概率分布模型。一个GMM由K个高斯分量组成每个分量有三个参数权重π_k代表这个分量在模型中的重要性、均值向量μ_k代表这个分量在特征空间中的中心位置、协方差矩阵Σ_k代表这个分量的形状和朝向。训练GMM的过程就是给定一个人的所有语音特征向量通过期望最大化EM算法迭代地估计出这K个分量的最佳参数使得这个混合模型生成这些特征向量的总体概率最大。2.2.2 GMM-UBM框架在纯粹的GMM模型中每个说话人用自己的语音训练一个独立的GMM。但在实际中尤其是注册语音很少的情况下模型容易过拟合。更鲁棒的方法是使用GMM-UBM通用背景模型框架。UBM是一个用大量非目标说话人背景人群的语音训练出来的一个“通用”GMM它代表了语音特征在特征空间中的总体分布。训练特定说话人模型时我们不再从零开始而是以UBM作为先验用目标说话人的少量语音数据对UBM的参数进行自适应调整比如MAP自适应得到该说话人的GMM。在识别时计算测试语音在目标说话人GMM下的似然度与在UBM下的似然度相比通常取对数似然比这个比值就是得分。得分越高说明测试语音越像目标说话人而不像背景人群。实操心得GMM分量的数量K是一个关键超参数。K太小模型太简单区分能力不足K太大需要更多的训练数据否则容易过拟合。对于几十秒的注册语音K32或64是常见的起点。在源码中这个参数通常是一个可配置的变量需要根据你的数据量进行调整。3. 源码结构解析与关键模块实现拿到“基于matlab实现的说话人语音识别源码.zip”后解压开来我们通常会看到几个核心的文件夹和脚本文件。下面我以一个典型的项目结构为例解析每个部分的功能和实现细节。3.1 项目目录与文件功能解读speaker_recognition_system/ ├── data/ # 存放语音数据 │ ├── train/ # 训练集按说话人分文件夹 │ └── test/ # 测试集 ├── src/ # 源代码主目录 │ ├── feature_extraction/ # 特征提取模块 │ │ ├── mfcc.m # MFCC主函数 │ │ ├── mel_filter_banks.m │ │ └── preprocess.m # 预加重、分帧加窗 │ ├── modeling/ # 模型训练模块 │ │ ├── train_gmm.m # 训练单个GMM │ │ ├── train_ubm.m # 训练UBM │ │ └── map_adapt.m # MAP自适应 │ ├── recognition/ # 识别模块 │ │ └── score_gmm.m # 计算似然度得分 │ └── utils/ # 工具函数 │ ├── vqlbg.m # 矢量量化LBG算法用于GMM初始化 │ └── read_audio.m # 音频读取 ├── config.m # 全局配置文件 ├── main_train.m # 主训练脚本 └── main_test.m # 主测试脚本config.m是这个系统的大脑所有关键参数都在这里集中设置。一个良好的配置脚本能极大提升实验效率。% config.m 示例 % 音频参数 config.fs 16000; % 采样率16kHz config.frame_length 0.025; % 帧长25ms config.frame_shift 0.01; % 帧移10ms % MFCC参数 config.num_mel_filters 26; % 梅尔滤波器个数 config.num_mfcc 13; % 保留的MFCC系数个数 config.use_delta true; % 使用一阶差分 config.use_delta_delta true; % 使用二阶差分 % GMM-UBM参数 config.ubm_num_components 256; % UBM的高斯分量数 config.gmm_num_components 64; % 说话人GMM的分量数 config.max_iterations 20; % EM算法最大迭代次数3.2 特征提取模块深度剖析我们深入src/feature_extraction/mfcc.m这个核心函数看看。一个健壮的MFCC实现需要考虑很多边界情况。function [mfcc_features, delta_mfcc, delta_delta_mfcc] extract_mfcc(signal, fs, config) % 1. 预加重 pre_emph_coeff 0.97; signal filter([1, -pre_emph_coeff], 1, signal); % 2. 分帧 frame_len_samples round(config.frame_length * fs); frame_shift_samples round(config.frame_shift * fs); frames enframe(signal, frame_len_samples, frame_shift_samples); % 3. 加窗汉明窗 window hamming(frame_len_samples); frames frames .* window; % 4. 计算功率谱 NFFT 2^nextpow2(frame_len_samples); % 通常取2的整数次幂 mag_frames abs(fft(frames, NFFT, 2)); pow_frames (1/NFFT) * (mag_frames .^ 2); % 5. 应用梅尔滤波器组 mel_banks mel_filter_banks(config.num_mel_filters, NFFT, fs); filter_banks pow_frames * mel_banks; filter_banks(filter_banks eps) eps; % 防止取对数时出现log(0) % 6. 取对数并做DCT得到MFCC log_filter_banks log(filter_banks); mfcc_features dct(log_filter_banks); mfcc_features mfcc_features(:, 1:config.num_mfcc); % 保留低位系数 % 7. 计算差分系数动态特征 if config.use_delta delta_mfcc compute_delta(mfcc_features); end if config.use_delta_delta delta_delta_mfcc compute_delta(delta_mfcc); end end关键细节与避坑指南FFT点数NFFT通常取大于等于帧长的2的整数次幂。取2的幂次能最大化FFT计算效率。nextpow2函数就是用来找这个值的。能量谱计算功率谱是幅度平方再除以NFFT。有些实现会忽略这个归一化虽然不影响最终识别系统的相对性能但会影响特征值的尺度。对数运算前的保护filter_banks(filter_banks eps) eps;这行代码至关重要。因为经过滤波器组后某些频带的能量可能非常接近于零直接取对数会得到负无穷-Inf导致后续计算崩溃。eps是Matlab中的最小正浮点数。DCT后的取舍第0个DCT系数DCT-II下的c(0)代表对数能量总和的均值通常包含较多通道噪声信息很多系统会直接丢弃从第1个系数开始取。所以mfcc_features(:, 1:config.num_mfcc)实际上取的是c1到c13。这点需要根据源码的具体实现来确认。3.3 GMM训练与自适应代码实现在src/modeling/train_ubm.m中训练UBM通常使用K均值算法如LBG算法初始化然后EM算法迭代优化。function ubm train_ubm(features_all_speakers, config) % features_all_speakers: 所有背景说话人特征拼接成的矩阵 [dim x num_vectors] [dim, num_vectors] size(features_all_speakers); % 1. 初始化使用矢量量化LBG或K-means得到初始均值 [init_means, ~] vqlbg(features_all_speakers, config.ubm_num_components); weights ones(1, config.ubm_num_components) / config.ubm_num_components; % 均匀权重 % 初始化协方差矩阵通常取全局对角协方差或每个簇的协方差 global_var var(features_all_speakers, 0, 2) eps; covs repmat(global_var, 1, config.ubm_num_components); % 对角协方差矩阵 % 2. EM算法迭代 for iter 1:config.max_iterations fprintf(UBM Training Iteration %d/%d\n, iter, config.max_iterations); % E-step: 计算后验概率责任值 [log_likelihood, posterior] compute_posterior(features_all_speakers, init_means, covs, weights); % M-step: 更新参数 [weights, init_means, covs] update_parameters(features_all_speakers, posterior); % 可以在此处添加似然度变化检查判断是否收敛 end ubm.weights weights; ubm.means init_means; ubm.covs covs; endsrc/modeling/map_adapt.m则负责从UBM自适应到目标说话人模型。MAP自适应主要更新均值向量对权重和协方差的更新较小或不予更新这能在有限数据下有效防止过拟合。function gmm map_adapt(ubm, target_features, relevance_factor) % ubm: 通用背景模型 % target_features: 目标说话人的特征向量 % relevance_factor: 自适应因子控制新数据的影响程度通常取16左右 [dim, num_vectors] size(target_features); num_components length(ubm.weights); % 1. 计算目标数据在UBM各分量下的后验概率E-step [~, posterior] compute_posterior(target_features, ubm.means, ubm.covs, ubm.weights); % 2. 计算充分统计量 n_i sum(posterior, 2); % 每个分量的“软计数” E_x_i (target_features * posterior) ./ (n_i eps); % 每个分量的数据均值期望 % 3. MAP更新均值核心 alpha_i n_i ./ (n_i relevance_factor); adapted_means zeros(dim, num_components); for i 1:num_components adapted_means(:, i) (1 - alpha_i(i)) * ubm.means(:, i) alpha_i(i) * E_x_i(:, i); end % 4. 构建目标说话人GMM通常只更新均值 gmm.weights ubm.weights; % 权重沿用UBM gmm.means adapted_means; gmm.covs ubm.covs; % 协方差沿用UBM end注意事项EM算法对初始化敏感。糟糕的初始化可能导致迭代收敛到局部最优。源码中的vqlbg函数LBG算法是一种常见的基于分裂法的矢量量化初始化方法比随机初始化稳定得多。另外在更新协方差时一定要添加一个很小的正则化项如diag(covs) eps防止协方差矩阵奇异。4. 系统训练与测试全流程实操理解了核心模块后我们来看如何将它们串联起来完成整个系统的训练和测试。main_train.m和main_test.m这两个主脚本就是指挥棒。4.1 训练阶段构建UBM与说话人模型库main_train.m的典型流程如下%% 主训练脚本 clear; clc; close all; addpath(genpath(./src)); % 添加源码路径 config; % 加载配置文件 %% 步骤1: 准备训练数据用于UBM和说话人模型 % 假设数据组织方式data/train/speaker01/*.wav, speaker02/*.wav ... train_data_path ./data/train; speaker_dirs dir(fullfile(train_data_path, speaker*)); num_speakers length(speaker_dirs); all_features_for_ubm []; % 用于训练UBM的所有特征 speaker_models struct(id, {}, gmm, {}); % 存储说话人模型 fprintf(开始特征提取...\n); for spk_idx 1:num_speakers speaker_id speaker_dirs(spk_idx).name; audio_files dir(fullfile(train_data_path, speaker_id, *.wav)); speaker_features []; for file_idx 1:length(audio_files) file_path fullfile(train_data_path, speaker_id, audio_files(file_idx).name); [audio, fs] audioread(file_path); if fs ~ config.fs audio resample(audio, config.fs, fs); % 重采样到目标采样率 end % 提取MFCC特征 [mfcc_vec, delta, delta_delta] extract_mfcc(audio, config.fs, config); features [mfcc_vec; delta; delta_delta]; % 组合静态和动态特征转置为[dim x num_frames] speaker_features [speaker_features, features]; end % 将此说话人的部分特征如前N段加入UBM训练池 % 注意通常UBM用独立于目标说话人的数据训练。这里为简化用所有说话人部分数据。 num_frames_for_ubm min(size(speaker_features, 2), 5000); % 每个说话人最多取5000帧 rand_indices randperm(size(speaker_features, 2), num_frames_for_ubm); all_features_for_ubm [all_features_for_ubm, speaker_features(:, rand_indices)]; % 存储该说话人所有特征用于后续自适应 speaker_models(spk_idx).id speaker_id; speaker_models(spk_idx).features speaker_features; end %% 步骤2: 训练通用背景模型UBM fprintf(开始训练UBM使用 %d 帧特征...\n, size(all_features_for_ubm, 2)); ubm train_ubm(all_features_for_ubm, config); save(./models/ubm_model.mat, ubm, config); fprintf(UBM训练完成并保存。\n); %% 步骤3: 为每个目标说话人进行MAP自适应生成说话人模型 fprintf(开始为各说话人进行MAP自适应...\n); for spk_idx 1:num_speakers target_features speaker_models(spk_idx).features; speaker_gmm map_adapt(ubm, target_features, config.relevance_factor); speaker_models(spk_idx).gmm speaker_gmm; fprintf( 说话人 %s 模型适配完成。\n, speaker_models(spk_idx).id); end save(./models/speaker_models.mat, speaker_models, config); fprintf(所有说话人模型训练完成并保存。\n);4.2 测试阶段识别与性能评估main_test.m负责加载模型并对测试语音进行识别。%% 主测试脚本 clear; clc; addpath(genpath(./src)); config; load(./models/ubm_model.mat); % 加载UBM load(./models/speaker_models.mat); % 加载说话人模型库 %% 加载测试语音 test_audio_path ./data/test/test_speaker_01.wav; [audio, fs] audioread(test_audio_path); if fs ~ config.fs audio resample(audio, config.fs, fs); end %% 提取测试语音特征 [mfcc_vec, delta, delta_delta] extract_mfcc(audio, config.fs, config); test_features [mfcc_vec; delta; delta_delta]; %% 计算对数似然比得分 num_speakers length(speaker_models); scores zeros(1, num_speakers); % 计算测试语音在UBM下的对数似然度 log_likelihood_ubm compute_log_likelihood(test_features, ubm.means, ubm.covs, ubm.weights); for spk_idx 1:num_speakers % 计算测试语音在当前说话人GMM下的对数似然度 gmm speaker_models(spk_idx).gmm; log_likelihood_spk compute_log_likelihood(test_features, gmm.means, gmm.covs, gmm.weights); % 对数似然比得分 log P(test|speaker) - log P(test|UBM) scores(spk_idx) log_likelihood_spk - log_likelihood_ubm; end %% 识别决策 [top_score, top_index] max(scores); threshold 0; % 简单阈值实际应用中需要通过大量实验确定 if top_score threshold fprintf(识别结果说话人 %s得分%.2f\n, speaker_models(top_index).id, top_score); else fprintf(得分低于阈值可能为未注册说话人。\n); end %% 性能评估假设有带标签的测试集 % 可以遍历测试集目录计算等错误率(EER)、检测代价函数(DCF)等指标compute_log_likelihood函数是得分计算的核心需要高效地计算一个特征向量序列在GMM下的总对数似然度。这里涉及“对数求和”的数值稳定性技巧。function total_log_lik compute_log_likelihood(features, means, covs, weights) % features: [dim x num_frames] % means: [dim x num_components] % covs: [dim x num_components] (假设为对角协方差) % weights: [1 x num_components] [dim, num_frames] size(features); num_components length(weights); total_log_lik 0; for t 1:num_frames frame features(:, t); log_component_lik zeros(1, num_components); for i 1:num_components % 计算单高斯分布下的对数概率密度对角协方差 diff frame - means(:, i); exponent -0.5 * sum( (diff.^2) ./ covs(:, i) ); norm_term -0.5 * ( dim*log(2*pi) sum(log(covs(:, i))) ); log_component_lik(i) norm_term exponent; end % 计算混合模型下的对数似然log sum_i (w_i * N(x|mu_i, sigma_i)) % 使用 log-sum-exp 技巧避免数值下溢 max_log max(log_component_lik); log_sum max_log log(sum(weights .* exp(log_component_lik - max_log))); total_log_lik total_log_lik log_sum; end end实操心得log-sum-exp技巧是计算GMM似然度的关键。直接计算exp(log_component_lik)很容易因为log_component_lik是非常负的数而导致下溢结果为零。先减去最大值max_log计算指数后再加回来能保证数值稳定。这是工业级代码和学术demo代码的一个重要区别。5. 常见问题、调试技巧与进阶优化在实际运行这套源码时你几乎一定会遇到各种问题。下面是我总结的一些典型问题和解决方法。5.1 特征提取阶段的常见陷阱问题一MFCC特征值出现NaN或Inf。排查首先检查音频读取是否正常有无静音文件。然后重点检查MFCC计算中取对数的步骤。确保在log(filter_banks)之前对filter_banks矩阵进行了“归零保护”filter_banks(filter_banks eps) eps;。技巧可以在extract_mfcc函数中关键步骤后添加assert(~any(isnan(X(:))))和assert(~any(isinf(X(:))))进行断言检查快速定位问题步骤。问题二识别率很低甚至随机猜测。排查数据一致性训练和测试的音频采样率、量化位数是否一致使用audioread后打印fs确认。不一致务必重采样。特征维度检查训练和测试时提取的MFCC特征维度是否相同。确保config.num_mfcc、use_delta等开关设置一致。静音帧干扰语音中长时间的静音或噪声帧会产生无意义的特征干扰模型。可以增加一个简单的静音检测VAD前端。一个简单的方法是计算每一帧的短时能量和过零率设定阈值滤除能量过低的帧。function voiced_frames simple_vad(frames, energy_threshold, zcr_threshold) energy sum(frames.^2, 2); zcr sum(abs(diff(sign(frames), 1, 2)), 2) / (2*(size(frames,2)-1)); voiced_frames (energy energy_threshold) (zcr zcr_threshold); end在特征提取后用这个函数返回的掩码过滤掉非语音帧的特征。5.2 模型训练阶段的调试要点问题三GMM训练时EM算法不收敛或似然度震荡。原因与解决初始化太差确保使用了vqlbg或kmeans进行合理的初始化而不是随机初始化。协方差矩阵奇异在M步更新协方差时一定要添加一个“地板值”flooring防止方差过小。function new_covs update_covs_with_flooring(features, posterior, means, floor_val) % ... 计算方差 ... new_covs new_covs floor_val; % 添加地板值如1e-6 end数据量太少或特征有问题检查用于训练每个GMM的特征帧数是否足够。通常每个高斯分量需要至少几十帧数据来可靠地估计参数。如果数据太少考虑减少GMM分量数config.gmm_num_components。问题四自适应后模型性能提升不明显。调整relevance_factor这个参数控制UBM先验的强度。值越大自适应越“保守”新数据影响越小值越小自适应越“激进”。对于注册语音很短的情况如5秒应该使用较大的值如32-64防止过拟合。对于注册语音较长的情况如1分钟可以使用较小的值如8-16。这是一个需要通过开发集来调优的超参数。5.3 系统性能评估与优化方向当系统能跑通后如何评价其好坏最简单的就是准备一个测试集计算识别准确率。但更专业的评测需要使用等错误率EER。计算EER你需要一个测试集其中包含目标说话人的测试语音正例和非目标说话人的测试语音负例。对每一段测试语音计算其与目标模型的得分。然后遍历所有可能的决策阈值计算此时的误接受率FAR非目标被误认为目标的概率和误拒绝率FRR目标被拒绝的概率。FAR和FRR相等时的错误率就是EEREER越低系统性能越好。进阶优化方向特征增强在MFCC基础上可以尝试PLP感知线性预测特征或者使用RASTA滤波对特征进行通道归一化提升对线性信道差异的鲁棒性。模型升级GMM-UBM是经典方法但已被更强大的方法超越。可以尝试i-vector将一段语音的所有特征映射到一个低维的“身份向量”空间然后用简单的分类器如PLDA, LDA进行识别。这是GMM时代后的一个里程碑。深度学习使用DNN深度神经网络或TDNN时延神经网络直接从频谱图或Fbank特征中提取更鲁棒的声纹嵌入如x-vector, d-vector然后用余弦相似度或PLDA打分。这是当前的主流方法性能远超GMM。信道补偿实际应用中录音设备、环境噪声、传输信道差异巨大。可以使用倒谱均值减CMS或RASTA滤波在特征层面进行补偿或者使用Nuisance Attribute Projection (NAP)、Within-Class Covariance Normalization (WCCN)在i-vector层面进行补偿。这套基于Matlab的GMM-UBM源码是一个绝佳的起点。它把语音识别中“特征提取-概率建模-识别决策”的完整逻辑清晰地呈现了出来。虽然深度学习方法现在是主流但理解这套经典框架对于掌握语音信号处理的基础、理解概率模型在识别中的作用有着不可替代的价值。我建议你在成功运行此代码后可以尝试修改特征参数、增加VAD、实现EER计算甚至将其作为基线去复现和对比更先进的i-vector或x-vector系统这样对说话人识别技术的演进会有更深刻的体会。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →