專案中需要用到後臺服務端用文字生成語音,網上大部分都是透過ai大模型推理出來的,還有寫其他方式的,效果和生成時間都比較不理想,但是訊飛生成的只需要零點幾秒,不愧是行業NO1,下面說下怎麼使用。
1、下載官方demo。
2、在官方demo目錄下,執行source 32bit_make.sh 或64bit_make.sh ,根據你的位數來。
3、執行後,不報錯的情況下在bin目錄下就會生成可執行檔案,直接執行 ./tts_offline_sample就可以測試生成結果。
4、在專案中使用,這樣肯定是不行的,在demo目錄下的.c檔案就要改造一下。
/* * 語音合成(Text To Speech,TTS)技術能夠自動將任意文字實時轉換為連續的 * 自然語音,是一種能夠在任何時間、任何地點,向任何人提供語音資訊服務的 * 高效便捷手段,非常符合資訊時代海量資料、動態更新和個性化查詢的需求。 */ #include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <inttypes.h> #include <sys/types.h> #include <sys/stat.h> #include <time.h> #include "../include/qtts.h" #include "../include/msp_cmn.h" #include "../include/msp_errors.h" typedef int SR_DWORD; typedef short int SR_WORD ; /* wav音訊頭部格式 */ typedef struct _wave_pcm_hdr { 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 } wave_pcm_hdr; /* 預設wav音訊頭部資料 */ wave_pcm_hdr default_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 }; /* 文字合成 */ int text_to_speech(const char* src_text, const char* des_path, const char* params) { int ret = -1; FILE* fp = NULL; const char* sessionID = NULL; unsigned int audio_len = 0; wave_pcm_hdr wav_hdr = default_wav_hdr; int synth_status = MSP_TTS_FLAG_STILL_HAVE_DATA; if (NULL == src_text || NULL == des_path) { printf("params is error!\n"); return ret; } fp = fopen(des_path, "wb"); if (NULL == fp) { printf("open %s error.\n", des_path); return ret; } /* 開始合成 */ sessionID = QTTSSessionBegin(params, &ret); if (MSP_SUCCESS != ret) { printf("QTTSSessionBegin failed, error code: %d.\n", ret); fclose(fp); return ret; } ret = QTTSTextPut(sessionID, src_text, (unsigned int)strlen(src_text), NULL); if (MSP_SUCCESS != ret) { printf("QTTSTextPut failed, error code: %d.\n",ret); QTTSSessionEnd(sessionID, "TextPutError"); fclose(fp); return ret; } 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("\n"); if (MSP_SUCCESS != ret) { printf("QTTSAudioGet failed, error code: %d.\n",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) { printf("QTTSSessionEnd failed, error code: %d.\n",ret); } return ret; } int ensure_directory_exists(const char* path) { // 使用F_OK檢查檔案(或資料夾)是否存在 if (access(path, F_OK) != -1) { // 路徑存在,可能是檔案或資料夾,但我們假設它是資料夾(這裡不進一步區分) return 0; // 成功,路徑已存在 } else { // 路徑不存在,嘗試建立資料夾 // 注意:mkdir的第二個引數設定了資料夾的許可權(如0755表示rwxr-xr-x) if (mkdir(path, 0755) != 0) { // 建立資料夾失敗 perror("mkdir failed"); return -1; // 失敗 } } return 0; // 成功(無論是已存在還是新建立) } char* create_filename(const char* file_path) { const char* base_path = "/tts/"; char date_str[11]; // "YYYY_MM_DD"格式最多需要10個字元+1個空字元 char directory_path[256]; // 足夠大的緩衝區來儲存完整路徑 // 獲取當前日期並格式化為"YYYY_MM_DD" time_t rawtime; struct tm *timeinfo; time(&rawtime); timeinfo = localtime(&rawtime); strftime(date_str, sizeof(date_str), "%Y_%m_%d", timeinfo); // 構建完整路徑 snprintf(directory_path, sizeof(directory_path), "%s%s%s", base_path, date_str,"/"); ensure_directory_exists(directory_path); const char* extension = ".wav"; // 計算所需的總長度(包括null終止符) size_t total_length = strlen(directory_path) + strlen(file_path) + strlen(extension) + 1; // 動態分配記憶體來儲存完整的檔名 char* filename = (char*)malloc(total_length * sizeof(char)); if (filename == NULL) { perror("malloc failed"); return NULL; // 記憶體分配失敗時返回NULL } // 使用snprintf來安全地拼接字串 snprintf(filename, total_length, "%s%s%s", directory_path, file_path, extension); return filename; } int main(int argc, char* argv[]) { /*解析入口引數*/ /**tts文字*/ const char* tts_txt = argv[1]; printf(tts_txt); printf("\n"); /*生成檔案儲存檔名字*/ const char* file_path=argv[2]; printf(file_path); printf("\n"); /*tts發聲引數*/ const char* tts_param=argv[3]; printf(tts_param); printf("\n"); int ret = MSP_SUCCESS; const char* login_params = "appid = 換成你自己的, work_dir = .";//登入引數,appid與msc庫繫結,請勿隨意改動 /* * rdn: 合成音訊數字發音方式 * volume: 合成音訊的音量 * pitch: 合成音訊的音調 * speed: 合成音訊對應的語速 * voice_name: 合成發音人 * sample_rate: 合成音訊取樣率 * text_encoding: 合成文字編碼格式 * */ const char* session_begin_params = tts_param; printf(session_begin_params); const char* filename = create_filename(file_path); const char* text = tts_txt; /* 使用者登入 */ ret = MSPLogin(NULL, NULL, login_params); //第一個引數是使用者名稱,第二個引數是密碼,第三個引數是登入引數,使用者名稱和密碼可在http://www.xfyun.cn註冊獲取 if (MSP_SUCCESS != ret) { printf("MSPLogin failed, error code: %d.\n", ret); MSPLogout(); //退出登入 return 0; } /* 文字合成 */ ret = text_to_speech(text, filename, session_begin_params); if (MSP_SUCCESS != ret) { printf("text_to_speech failed, error code: %d.\n", ret); } MSPLogout(); //退出登入 return 0; }
5、再執行第二步的操作。
6、在bin目錄下新建tts_test.sh指令碼用於測試,也可以不用,直接執行內容的命令,內容如下
tts_offline_sample "tts測 試 " ewr5we "engine_type = local,voice_name=xiaoyan, text_encoding = UTF8, tts_res_
path = fo|res/tts/xiaoyan.jet;fo|res/tts/common.jet, sample_rate = 16000, speed = 50, volume = 50, pitch = 5
0, rdn = 2"
執行新建的指令碼測試。
7、bin目錄下的msc目錄下的msc.cfg檔案直接刪除,不然會有很多的日誌。
8、指令碼執行可以的話,C語言的開發就到這裡了,用其他語言發起命令列呼叫就能實現合成了,具體的要看你的業務怎麼設計的了。