科大訊飛離線lunix tts demo使用

Rolay發表於2024-11-06

專案中需要用到後臺服務端用文字生成語音,網上大部分都是透過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語言的開發就到這裡了,用其他語言發起命令列呼叫就能實現合成了,具體的要看你的業務怎麼設計的了。

相關文章