Ubuntu 20.04下用PortAudio库实现G1人形机器人实时喊话(附完整代码) Ubuntu 20.04下用PortAudio库实现G1人形机器人实时喊话附完整代码当我们需要让机器人具备实时语音交互能力时音频采集与播放是最基础的功能之一。本文将详细介绍如何在Ubuntu 20.04系统中利用PortAudio库为G1人形机器人实现麦克风声音的实时采集与播放功能。这个方案不仅适用于远程喊话场景也可作为更复杂语音交互系统的基础模块。1. 开发环境准备在开始编码前我们需要确保开发环境已正确配置。以下是必要的准备工作系统要求Ubuntu 20.04 LTS推荐使用原生安装非虚拟机GCC 9.4.0或更高版本CMake 3.16或更高版本依赖库安装sudo apt update sudo apt install -y build-essential cmake libportaudio2 portaudio19-dev提示如果后续编译时遇到链接错误可能需要额外安装libasound2-dev包验证PortAudio安装是否成功pkg-config --modversion portaudio-2.0正常应输出类似19.6.0的版本号。2. 项目结构与CMake配置合理的项目结构能显著提高代码可维护性。建议采用如下目录布局g1_audio_project/ ├── CMakeLists.txt ├── include/ │ └── audio_utils.h ├── src/ │ ├── main.cpp │ └── audio_processor.cpp └── third_party/ └── unitree_sdk/对应的CMake配置示例cmake_minimum_required(VERSION 3.16) project(g1_realtime_audio) set(CMAKE_CXX_STANDARD 17) find_package(PkgConfig REQUIRED) pkg_check_modules(PORTAUDIO REQUIRED portaudio-2.0) include_directories( ${PORTAUDIO_INCLUDE_DIRS} ${PROJECT_SOURCE_DIR}/include ${PROJECT_SOURCE_DIR}/third_party/unitree_sdk/include ) add_executable(g1_audio src/main.cpp src/audio_processor.cpp) target_link_libraries(g1_audio ${PORTAUDIO_LIBRARIES} ${PROJECT_SOURCE_DIR}/third_party/unitree_sdk/lib/libunitree_sdk.a )3. 核心音频处理实现音频处理流程主要分为三个部分初始化、采集循环和资源释放。以下是关键代码实现3.1 音频设备初始化bool init_audio_stream(PaStream* stream, PaStreamParameters input_params) { PaError err Pa_Initialize(); if(err ! paNoError) { std::cerr PortAudio init failed: Pa_GetErrorText(err) std::endl; return false; } input_params.device Pa_GetDefaultInputDevice(); if(input_params.device paNoDevice) { std::cerr No default input device available std::endl; return false; } input_params.channelCount 1; // 单声道 input_params.sampleFormat paInt16; input_params.suggestedLatency Pa_GetDeviceInfo(input_params.device)-defaultLowInputLatency; input_params.hostApiSpecificStreamInfo nullptr; err Pa_OpenStream( stream, input_params, nullptr, // 无输出 16000, // 采样率16kHz 512, // 每帧采样数 paNoFlag, nullptr, // 无回调 nullptr ); if(err ! paNoError) { std::cerr Stream open failed: Pa_GetErrorText(err) std::endl; Pa_Terminate(); return false; } err Pa_StartStream(stream); if(err ! paNoError) { std::cerr Stream start failed: Pa_GetErrorText(err) std::endl; Pa_CloseStream(stream); Pa_Terminate(); return false; } return true; }3.2 音频采集与处理循环void audio_processing_loop(PaStream* stream, AudioClient client) { const int buffer_size 512; std::vectorint16_t audio_buffer(buffer_size); std::vectoruint8_t pcm_buffer(buffer_size * sizeof(int16_t)); std::string stream_id generate_unique_stream_id(); while(!should_stop) { PaError err Pa_ReadStream(stream, audio_buffer.data(), buffer_size); if(err ! paNoError) { std::cerr Read error: Pa_GetErrorText(err) std::endl; break; } // 转换为机器人需要的PCM格式 std::memcpy(pcm_buffer.data(), audio_buffer.data(), pcm_buffer.size()); // 发送到机器人扬声器 int ret client.PlayStream(audio_app, stream_id, pcm_buffer); if(ret ! 0) { std::cerr PlayStream failed with code: ret std::endl; } } }3.3 信号处理与资源释放volatile sig_atomic_t should_stop 0; void handle_signal(int) { should_stop 1; } void cleanup(PaStream* stream, AudioClient* client) { if(stream) { Pa_StopStream(stream); Pa_CloseStream(stream); Pa_Terminate(); } if(client) { client-PlayStop(audio_app); } }4. 性能优化与调试技巧实现基本功能后我们需要关注系统的实时性和稳定性。以下是几个关键优化点4.1 延迟优化参数调整建议参数默认值优化值影响采样率44100 Hz16000 Hz降低带宽需求帧大小1024256-512平衡延迟与CPU负载缓冲区数量23减少断流风险4.2 常见问题排查无声音输出检查默认音频设备设置pactl list short sources验证麦克风权限arecord -l测试原始采集arecord -f cd -d 5 test.wav音频卡顿# 监控CPU使用率 top -p $(pgrep -d, your_program_name) # 检查中断频率 watch -n 0.1 cat /proc/interrupts | grep timer内存泄漏检测valgrind --leak-checkfull ./g1_audio4.3 高级功能扩展回声消除实现思路// 伪代码示例 void process_echo_cancellation(int16_t* input, int16_t* output) { static WebRtcAec3 aec_processor; aec_processor.ProcessCapture(input, output); // 需要同时提供参考信号扬声器输出 aec_processor.AnalyzeRender(output); }音频压缩配置// Opus编码器示例 OpusEncoder* encoder opus_encoder_create(16000, 1, OPUS_APPLICATION_VOIP, nullptr); opus_encoder_ctl(encoder, OPUS_SET_BITRATE(16000)); opus_encoder_ctl(encoder, OPUS_SET_COMPLEXITY(8));5. 完整实现代码以下是整合所有功能的完整实现#include iostream #include vector #include cstring #include csignal #include portaudio.h #include unitree/robot/g1/audio/g1_audio_client.hpp // 全局变量 volatile sig_atomic_t g_stop_flag 0; PaStream* g_audio_stream nullptr; unitree::robot::g1::AudioClient* g_audio_client nullptr; // 信号处理 void signal_handler(int) { g_stop_flag 1; } // 初始化音频流 bool init_audio_stream() { PaError err Pa_Initialize(); if(err ! paNoError) return false; PaStreamParameters input_params; input_params.device Pa_GetDefaultInputDevice(); if(input_params.device paNoDevice) return false; input_params.channelCount 1; input_params.sampleFormat paInt16; input_params.suggestedLatency Pa_GetDeviceInfo(input_params.device)-defaultLowInputLatency; input_params.hostApiSpecificStreamInfo nullptr; err Pa_OpenStream( g_audio_stream, input_params, nullptr, 16000, 512, paNoFlag, nullptr, nullptr ); if(err ! paNoError) { Pa_Terminate(); return false; } err Pa_StartStream(g_audio_stream); if(err ! paNoError) { Pa_CloseStream(g_audio_stream); Pa_Terminate(); return false; } return true; } // 主处理循环 void run_audio_loop() { const int frame_size 512; std::vectorint16_t audio_frame(frame_size); std::vectoruint8_t pcm_data(frame_size * sizeof(int16_t)); std::string stream_id std::to_string( unitree::common::GetCurrentTimeMillisecond() ); while(!g_stop_flag) { PaError err Pa_ReadStream( g_audio_stream, audio_frame.data(), frame_size ); if(err ! paNoError) break; std::memcpy(pcm_data.data(), audio_frame.data(), pcm_data.size()); int ret g_audio_client-PlayStream( realtime_audio, stream_id, pcm_data ); if(ret ! 0) { std::cerr Stream error: ret std::endl; } } } // 清理资源 void cleanup() { if(g_audio_stream) { Pa_StopStream(g_audio_stream); Pa_CloseStream(g_audio_stream); Pa_Terminate(); } if(g_audio_client) { g_audio_client-PlayStop(realtime_audio); } } int main(int argc, char** argv) { if(argc 2) { std::cerr Usage: argv[0] network_interface std::endl; return 1; } std::signal(SIGINT, signal_handler); // 初始化Unitree音频客户端 unitree::robot::ChannelFactory::Instance()-Init(0, argv[1]); unitree::robot::g1::AudioClient client; client.Init(); client.SetTimeout(10.0f); client.SetVolume(80); // 80%音量 g_audio_client client; // 初始化音频流 if(!init_audio_stream()) { std::cerr Audio stream initialization failed std::endl; return 1; } std::cout Start audio streaming std::endl; run_audio_loop(); cleanup(); return 0; }对应的CMakeLists.txt完整配置cmake_minimum_required(VERSION 3.16) project(g1_realtime_audio) set(CMAKE_CXX_STANDARD 17) find_package(PkgConfig REQUIRED) pkg_check_modules(PORTAUDIO REQUIRED portaudio-2.0) include_directories( ${PORTAUDIO_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/third_party/unitree_sdk/include ) add_executable(g1_audio src/main.cpp) target_link_libraries(g1_audio ${PORTAUDIO_LIBRARIES} ${CMAKE_SOURCE_DIR}/third_party/unitree_sdk/lib/libunitree_sdk.a )在实际部署中发现为减少网络延迟影响最好将音频采样率设置为16kHz帧大小控制在20ms以内即320个样本16kHz。同时建议添加简单的静音检测逻辑避免传输无效音频数据。