diff --git a/robot_voice/CMakeLists.txt b/robot_voice/CMakeLists.txt index 3f8b561..6323d27 100644 --- a/robot_voice/CMakeLists.txt +++ b/robot_voice/CMakeLists.txt @@ -12,6 +12,7 @@ find_package(catkin REQUIRED COMPONENTS rospy std_msgs geometry_msgs + message_generation ) ## System dependencies are found with CMake's conventions @@ -55,11 +56,10 @@ find_package(catkin REQUIRED COMPONENTS # ) ## Generate services in the 'srv' folder -# add_service_files( -# FILES -# Service1.srv -# Service2.srv -# ) +add_service_files( + FILES + StringToVoice.srv +) ## Generate actions in the 'action' folder # add_action_files( @@ -69,10 +69,10 @@ find_package(catkin REQUIRED COMPONENTS # ) ## Generate added messages and services with any dependencies listed here -# generate_messages( -# DEPENDENCIES -# std_msgs -# ) +generate_messages( + DEPENDENCIES + std_msgs +) ################################################ ## Declare ROS dynamic reconfigure parameters ## @@ -106,7 +106,7 @@ find_package(catkin REQUIRED COMPONENTS catkin_package( # INCLUDE_DIRS include # LIBRARIES robot_voice -# CATKIN_DEPENDS roscpp rospy std_msgs + CATKIN_DEPENDS message_runtime roscpp rospy std_msgs # DEPENDS system_lib ) @@ -134,11 +134,14 @@ include_directories( ## Declare a C++ executable ## With catkin_make all packages are built within a single CMake context ## The recommended prefix ensures that target names across packages don't collide -add_executable(voice_controller - src/voice_controller.cpp +add_executable(voice_to_string + src/voice_to_string.cpp ifly/speech_recognizer.c ifly/linuxrec.c) +add_executable(robot_controller src/robot_controller.cpp) + +add_executable(string_to_voice src/string_to_voice.cpp) ## Rename C++ executable without prefix ## The above recommended prefix causes long target names, the following renames the @@ -155,10 +158,21 @@ add_executable(voice_controller # ${catkin_LIBRARIES} # ) -target_link_libraries(voice_controller - ${catkin_LIBRARIES} - libmsc.so -ldl -lpthread -lm -lrt -lasound - ) +target_link_libraries(voice_to_string + ${catkin_LIBRARIES} + libmsc.so -ldl -lpthread -lm -lrt -lasound +) + +add_dependencies(robot_controller ${PROJECT_NAME}_generate_messages_cpp) +target_link_libraries(robot_controller + ${catkin_LIBRARIES} +) + +add_dependencies(string_to_voice ${PROJECT_NAME}_generate_messages_cpp) +target_link_libraries(string_to_voice + ${catkin_LIBRARIES} + libmsc.so -ldl -pthread +) ############# ## Install ## diff --git a/robot_voice/package.xml b/robot_voice/package.xml index 19aa222..cd4417e 100644 --- a/robot_voice/package.xml +++ b/robot_voice/package.xml @@ -53,15 +53,21 @@ rospy std_msgs geometry_msgs + message_generation + message_runtime roscpp rospy std_msgs geometry_msgs + message_generation + message_runtime roscpp rospy std_msgs geometry_msgs - + message_generation + message_runtime + diff --git a/robot_voice/src/ifly_spark.cpp b/robot_voice/src/ifly_spark.cpp new file mode 100644 index 0000000..e69de29 diff --git a/robot_voice/src/robot_controller.cpp b/robot_voice/src/robot_controller.cpp new file mode 100644 index 0000000..e69de29 diff --git a/robot_voice/src/string_to_voice.cpp b/robot_voice/src/string_to_voice.cpp new file mode 100644 index 0000000..035c87f --- /dev/null +++ b/robot_voice/src/string_to_voice.cpp @@ -0,0 +1,208 @@ +#include +#include +#include +#include +#include +#include +#include "ifly/qisr.h" +#include "ifly/qtts.h" +#include "ifly/msp_cmn.h" +#include "ifly/formats.h" +#include "ifly/msp_errors.h" +#include "ifly/speech_recognizer.h" +#include + + +class Helper { +public: + static void SignalHandler(int signal) { + ROS_INFO("\nCaught signal %d. Exiting gracefully...\n", signal); + exit(0); + } +}; + + +class VoiceResponse { +public: + VoiceResponse() { + ROS_INFO("voice Response Constructor"); + } + + ~VoiceResponse() { + ROS_INFO("voice Response Destructor "); + } + + int ProcessTxt(std::string& txt) { + int ret = -1; + FILE* fp = NULL; + const char* sessionID = NULL; + unsigned int audio_len = 0; + int synth_status = MSP_TTS_FLAG_STILL_HAVE_DATA; + WavePcmHdr_t wav_hdr = { + { 'R', 'I', 'F', 'F' }, + 0, + {'W', 'A', 'V', 'E'}, + {'f', 'm', 't', ' '}, + 16, + 1, + 1, + 16000, + 32000, + 2, + 16, + {'d', 'a', 't', 'a'}, + 0 + }; + + const char* src_text = txt.c_str(); + const char* des_path = filename_.c_str(); + const char* params = session_begin_params_.c_str(); + + if (NULL == src_text || NULL == des_path) { + ROS_ERROR("params is error!"); + return ret; + } + + fp = fopen(des_path, "wb"); + if (NULL == fp) { + ROS_ERROR("open %s error", des_path); + return ret; + } + + /* 开始合成 */ + sessionID = QTTSSessionBegin(params, &ret); + if (MSP_SUCCESS != ret) { + ROS_ERROR("QTTSSessionBegin failed, error code: %d", ret); + fclose(fp); + return ret; + } + + ret = QTTSTextPut(sessionID, src_text, (unsigned int)strlen(src_text), NULL); + if (MSP_SUCCESS != ret) { + ROS_ERROR("QTTSTextPut failed, error code: %d",ret); + QTTSSessionEnd(sessionID, "TextPutError"); + fclose(fp); + return ret; + } + + printf("正在合成 ...\n"); + fwrite(&wav_hdr, sizeof(wav_hdr) ,1, fp); //添加wav音频头,使用采样率为16000 + while (1) { + /* 获取合成音频 */ + const void* data = QTTSAudioGet(sessionID, &audio_len, &synth_status, &ret); + if (MSP_SUCCESS != ret) { + break; + } + + if (NULL != data) { + fwrite(data, audio_len, 1, fp); + wav_hdr.data_size += audio_len; //计算data_size大小 + } + + if (MSP_TTS_FLAG_DATA_END == synth_status) { + break; + } + + printf(">"); + usleep(150*1000); //防止频繁占用CPU + } + printf("\n"); + + if (MSP_SUCCESS != ret) { + ROS_ERROR("QTTSAudioGet failed, error code: %d",ret); + QTTSSessionEnd(sessionID, "AudioGetError"); + fclose(fp); + return ret; + } + + /* 修正wav文件头数据的大小 */ + wav_hdr.size_8 += wav_hdr.data_size + (sizeof(wav_hdr) - 8); + + /* 将修正过的数据写回文件头部,音频文件为wav格式 */ + fseek(fp, 4, 0); + fwrite(&wav_hdr.size_8,sizeof(wav_hdr.size_8), 1, fp); //写入size_8的值 + fseek(fp, 40, 0); //将文件指针偏移到存储data_size值的位置 + fwrite(&wav_hdr.data_size,sizeof(wav_hdr.data_size), 1, fp); //写入data_size的值 + fclose(fp); + fp = NULL; + /* 合成完毕 */ + ret = QTTSSessionEnd(sessionID, "Normal"); + if (MSP_SUCCESS != ret) { + ROS_ERROR("QTTSSessionEnd failed, error code: %d", ret); + return ret; + } + + fp = popen(play_cmd_.c_str(),"r"); + if (fp == NULL) { + ROS_ERROR("play /tmp/tts_sample.wav failed"); + return -1; + } + pclose(fp); + + return 0; + } + + bool Speeking(robot_voice::StringToVoice::Request &req, robot_voice::StringToVoice::Response &resp) { + int ret = -1; + ret = ProcessTxt(req.data); + if (MSP_SUCCESS != ret) { + ROS_ERROR("AnswerVoice failed, error code: %d", ret); + resp.success = false; + return false; + } else { + resp.success = true; + } + + return resp.success; + } + + void Start(ros::NodeHandle& nh) { + server_ = nh.advertiseService("str2voice", &VoiceResponse::Speeking, this); + ROS_INFO("voice Response Start"); + } + +private: + ros::ServiceServer server_; + + const std::string session_begin_params_ = + "voice_name = xiaoyan, text_encoding = utf8, " + "sample_rate = 16000, speed = 50, volume = 50, " + "pitch = 50, rdn = 2"; + const std::string filename_ = "/tmp/tts_sample.wav"; //合成的语音文件名称 + const std::string play_cmd_ = "play /tmp/tts_sample.wav"; + + /* wav音频头部格式 */ + typedef struct WavePcmHdr { + char riff[4]; // = "RIFF" + int size_8; // = FileSize - 8 + char wave[4]; // = "WAVE" + char fmt[4]; // = "fmt " + int fmt_size; // = 下一个结构体的大小 : 16 + + short int format_tag; // = PCM : 1 + short int channels; // = 通道数 : 1 + int samples_per_sec; // = 采样率 : 8000 | 6000 | 11025 | 16000 + int avg_bytes_per_sec; // = 每秒字节数 : samples_per_sec * bits_per_sample / 8 + short int block_align; // = 每采样点字节数 : wBitsPerSample / 8 + short int bits_per_sample; // = 量化比特数: 8 | 16 + + char data[4]; // = "data"; + int data_size; // = 纯数据长度 : FileSize - 44 + } WavePcmHdr_t; + +}; + +int main(int argc, char ** argv) { + ros::init(argc, argv, "string_to_voice"); + ros::NodeHandle nh; + + if (signal(SIGINT, Helper::SignalHandler) == SIG_ERR) { + return -1; + } + + VoiceResponse vr; + vr.Start(nh); + + ros::spin(); + return 0; +} diff --git a/robot_voice/src/voice_controller.cpp b/robot_voice/src/voice_controller.cpp index c6d5b67..9563111 100644 --- a/robot_voice/src/voice_controller.cpp +++ b/robot_voice/src/voice_controller.cpp @@ -1,4 +1,3 @@ -#include #include #include #include diff --git a/robot_voice/src/voice_to_string.cpp b/robot_voice/src/voice_to_string.cpp new file mode 100644 index 0000000..0705535 --- /dev/null +++ b/robot_voice/src/voice_to_string.cpp @@ -0,0 +1,168 @@ +#include +#include +#include +#include +#include +#include +#include "ifly/qisr.h" +#include "ifly/qtts.h" +#include "ifly/msp_cmn.h" +#include "ifly/formats.h" +#include "ifly/msp_errors.h" +#include "ifly/speech_recognizer.h" +#include + +class Helper { +public: + static void SignalHandler(int signal) { + ROS_INFO("\nCaught signal %d. Exiting gracefully...\n", signal); + exit(0); + } +}; + +class VoiceDetector { +public: + VoiceDetector() { + ROS_INFO("voice detector Constructor"); + } + ~VoiceDetector() { + ROS_INFO("voice detector Destructor "); + } + + int Init() { + int ret = MSP_SUCCESS; + ret = MSPLogin(NULL, NULL, login_params_.c_str()); + if (MSP_SUCCESS != ret) { + ROS_ERROR("MSPLogin failed , Error code %d", ret); + MSPLogout(); // Logout... + return -1; + } + + ROS_INFO("MSP Login for update, waiting for seconds..."); + + return 0; + } + + static void JoinTxt(const char *result, char is_last) { + if (result) { + std::string slice_txt = result; + + VoiceDetector::voice_txt_ += slice_txt; + } + if (is_last) { + printf("voice txt : %s\n", VoiceDetector::voice_txt_.c_str()); + } + } + + static void InitSpeech() { + VoiceDetector::voice_txt_ = ""; + + printf("Start Listening...\n"); + } + + static void EndSpeech(int reason) { + if (reason == END_REASON_VAD_DETECT) { + printf("\nSpeaking done \n"); + } else { + printf("\nRecognizer error %d\n", reason); + } + } + + int SpeechOnce() { + int ret; + int i = 0; + + struct speech_rec iat; + + struct speech_rec_notifier recnotifier = { + JoinTxt, + InitSpeech, + EndSpeech + }; + + ret = sr_init(&iat, session_begin_params_.c_str(), SR_MIC, &recnotifier); + if (ret) { + ROS_ERROR("speech recognizer init failed"); + return -1; + } + + ret = sr_start_listening(&iat); + if (ret) { + printf("start listen failed %d\n", ret); + } + + /* demo 15 seconds recording */ + sleep(15); + + ret = sr_stop_listening(&iat); + if (ret) { + printf("stop listening failed %d\n", ret); + } + + sr_uninit(&iat); + + return 0; + } + + static std::string get_voice_txt_() { + return voice_txt_; + } + + +private: + + + const std::string login_params_ = "appid = bb839ccf, work_dir = ."; + const std::string session_begin_params_ = + "sub = iat, domain = iat, language = zh_cn, " + "accent = mandarin, sample_rate = 16000, " + "result_type = plain, result_encoding = utf8"; + + const uint32_t BufferSize = 4096; + uint64_t g_buffersize = BufferSize; + static std::string voice_txt_; +}; + +std::string VoiceDetector::voice_txt_ = ""; + + +int main(int argc, char* argv[]) { + int ret = 0; + ros::init(argc, argv, "voice_to_string"); + ros::NodeHandle nh; + ros::Publisher str_pub_ = nh.advertise("/human/chatter", 1000); + + if (signal(SIGINT, Helper::SignalHandler) == SIG_ERR) { + return -1; + } + + VoiceDetector vd; + ret = vd.Init(); + if (ret < 0) { + return -1; + } + + while (1) { + ret = vd.SpeechOnce(); + if (ret < 0) { + return -1; + } + + std::string voice_txt = VoiceDetector::get_voice_txt_(); + if (voice_txt == "") { + continue; + } else if (voice_txt.find("结束") != std::string::npos) { + break; + } + + std_msgs::String msg; + msg.data = voice_txt; + str_pub_.publish(msg); + } + + ros::spin(); + + return 0; +} + + \ No newline at end of file diff --git a/robot_voice/srv/StringToVoice.srv b/robot_voice/srv/StringToVoice.srv new file mode 100644 index 0000000..cfec3c5 --- /dev/null +++ b/robot_voice/srv/StringToVoice.srv @@ -0,0 +1,3 @@ +string data +--- +bool success \ No newline at end of file