以下是对 Application::OutputAudio() 函数的详细解释:
源码:
cpp
void Application::OutputAudio() { // 扬声器的输出
auto now = std::chrono::steady_clock::now();
auto codec = Board::GetInstance().GetAudioCodec();
const int max_silence_seconds = 10;
std::unique_lock<std::mutex> lock(mutex_);
if (audio_decode_queue_.empty()) {
// Disable the output if there is no audio data for a long time
if (device_state_ == kDeviceStateIdle) {
auto duration = std::chrono::duration_cast<std::chrono::seconds>(now - last_output_time_).count();
if (duration > max_silence_seconds) {
codec->EnableOutput(false);
}
}
return;
}
if (device_state_ == kDeviceStateListening) {
audio_decode_queue_.clear();
return;
}
last_output_time_ = now;
auto opus = std::move(audio_decode_queue_.front());
audio_decode_queue_.pop_front();
lock.unlock();
background_task_->Schedule([this, codec, opus = std::move(opus)]() mutable {
if (aborted_) {
return;
}
std::vector<int16_t> pcm;
if (!opus_decoder_->Decode(std::move(opus), pcm)) {
return;
}
// Resample if the sample rate is different
if (opus_decode_sample_rate_ != codec->output_sample_rate()) {
int target_size = output_resampler_.GetOutputSamples(pcm.size());
std::vector<int16_t> resampled(target_size);
output_resampler_.Process(pcm.data(), pcm.size(), resampled.data());
pcm = std::move(resampled);
}
codec->OutputData(pcm);
});
}
函数概述
Application::OutputAudio() 函数主要负责处理音频数据的输出,通过扬声器播放音频。它会检查音频解码队列的状态,根据不同情况决定是否输出音频、是否禁用输出设备,并且会对音频数据进行解码和重采样处理,最后将处理后的音频数据发送到音频编解码器进行输出。
代码详细解释
1. 时间获取和编解码器获取
cpp
auto now = std::chrono::steady_clock::now();
auto codec = Board::GetInstance().GetAudioCodec();
const int max_silence_seconds = 10;
now:获取当前时间,用于后续计算音频数据的空闲时长。codec:通过Board::GetInstance().GetAudioCodec()获取音频编解码器的实例,用于音频数据的输出。max_silence_seconds:定义了最大允许的静音时长,单位为秒。
2. 加锁并检查音频解码队列
cpp
std::unique_lock<std::mutex> lock(mutex_);
if (audio_decode_queue_.empty()) {
if (device_state_ == kDeviceStateIdle) {
auto duration = std::chrono::duration_cast<std::chrono::seconds>(now - last_output_time_).count();
if (duration > max_silence_seconds) {
codec->EnableOutput(false);
}
}
return;
}
std::unique_lock<std::mutex> lock(mutex_):对互斥锁mutex_加锁,确保在访问audio_decode_queue_时线程安全。audio_decode_queue_.empty():检查音频解码队列是否为空。- 如果队列为空且设备状态为
kDeviceStateIdle,计算从上次输出音频到现在的时长duration。 - 如果
duration超过max_silence_seconds,调用codec->EnableOutput(false)禁用音频输出设备。 - 无论是否禁用输出设备,只要队列为空,函数直接返回。
- 如果队列为空且设备状态为
3. 检查设备状态
cpp
if (device_state_ == kDeviceStateListening) {
audio_decode_queue_.clear();
return;
}
- 如果设备状态为
kDeviceStateListening,清空音频解码队列并返回,不进行音频输出。
4. 更新最后输出时间并取出音频数据
cpp
last_output_time_ = now;
auto opus = std::move(audio_decode_queue_.front());
audio_decode_queue_.pop_front();
lock.unlock();
last_output_time_ = now:更新最后输出音频的时间。auto opus = std::move(audio_decode_queue_.front()):将队列头部的音频数据(OPUS 格式)移动到opus变量中。audio_decode_queue_.pop_front():从队列中移除头部元素。lock.unlock():解锁互斥锁,允许其他线程访问audio_decode_queue_。
5. 调度后台任务处理音频数据
cpp
background_task_->Schedule([this, codec, opus = std::move(opus)]() mutable {
if (aborted_) {
return;
}
std::vector<int16_t> pcm;
if (!opus_decoder_->Decode(std::move(opus), pcm)) {
return;
}
if (opus_decode_sample_rate_ != codec->output_sample_rate()) {
int target_size = output_resampler_.GetOutputSamples(pcm.size());
std::vector<int16_t> resampled(target_size);
output_resampler_.Process(pcm.data(), pcm.size(), resampled.data());
pcm = std::move(resampled);
}
codec->OutputData(pcm);
});
background_task_->Schedule(...):将一个 lambda 函数调度到后台任务中执行。if (aborted_):检查是否已终止操作,如果是则直接返回。opus_decoder_->Decode(std::move(opus), pcm):使用opus_decoder_对 OPUS 格式的音频数据进行解码,解码结果存储在pcm向量中。如果解码失败,函数返回。if (opus_decode_sample_rate_ != codec->output_sample_rate()):检查解码后的音频采样率是否与音频编解码器的输出采样率一致。如果不一致,进行重采样处理:output_resampler_.GetOutputSamples(pcm.size()):获取重采样后的样本数量。output_resampler_.Process(pcm.data(), pcm.size(), resampled.data()):执行重采样操作。pcm = std::move(resampled):将重采样后的音频数据移动到pcm向量中。
codec->OutputData(pcm):将处理后的 PCM 音频数据发送到音频编解码器进行输出。
代码优化建议
- 错误处理 :在
codec->OutputData(pcm)调用后,可以添加错误处理逻辑,以处理可能的输出错误。 - 日志记录:在关键步骤添加日志记录,方便调试和监控程序运行状态。
- 资源管理 :确保
opus_decoder_和output_resampler_在使用完毕后正确释放资源。