有个项目要在c# winform程序中需要集成语音合成的功能,再三斟酌后还是选了科大讯飞,然后就对科大讯飞的语音合成模块进行简单的封装了一下;这个帮助类包含科大讯飞在线语音合成、离线语音合成功能的简单封装。首先在科大讯飞平台选择SDK功能(需要注意的是离线语音合成在不付费的情况下仍有90天的试用时长,这点很不错)。
注意:本例子使用x86的类库,所以项目编译平台应是改为x86才能正常运行。
选择完SDK能力后,下载SDK文件包;SDK包解压后执行如下操作。
- 将bin目录下的msc.dll与msc_x64.dll复制到程序运行目录下。
- 将bin>src>res>下的tts整个文件夹复制到程序运行目录下(如果有离线语音合成能力的话)
语音合成封装类
using CiCommon;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Media;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace CiCommon
{
/// <summary>
/// 讯飞语音离线生成
/// </summary>
public class XfVioceMsc
{
[DllImport("msc.dll", CallingConvention = CallingConvention.Winapi)]
public static extern int MSPLogin(string user, string password, string configs);
[DllImport("msc.dll", CallingConvention = CallingConvention.Winapi)]
public static extern int MSPLogout();
[DllImport("msc.dll", CallingConvention = CallingConvention.Winapi)]
public static extern IntPtr QTTSSessionBegin(string _params, ref int errorCode);
[DllImport("msc.dll", CallingConvention = CallingConvention.Winapi)]
public static extern int QTTSTextPut(string sessionID, string textString, uint textLen, string _params);
[DllImport("msc.dll", CallingConvention = CallingConvention.Winapi)]
public static extern IntPtr QTTSAudioGet(string sessionID, ref uint audioLen, ref SynthStatus synthStatus, ref int errorCode);
[DllImport("msc.dll", CallingConvention = CallingConvention.Winapi)]
public static extern IntPtr QTTSAudioInfo(string sessionID);
[DllImport("msc.dll", CallingConvention = CallingConvention.Winapi)]
public static extern int QTTSSessionEnd(string sessionID, string hints);
[DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int memcpy(IntPtr dst, IntPtr src, uint count);
[DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern unsafe int memcpy(void* dst, void* src, uint count);
private static int _ret = 0;
private static IntPtr _sessionId;
private static object syncObj = new object();
/// <summary>
/// 文字转语音
/// </summary>
/// <param name="text">文本</param>
/// <param name="online">是否在线生成</param>
public static void Text2Voice(string text, bool online = false)
{
try
{
lock (syncObj)
{
if (string.IsNullOrEmpty(text))
{
return;
}
else
{
//校验资源文件是否存在
if (!File.Exists(System.AppDomain.CurrentDomain.SetupInformation.ApplicationBase + "msc.dll"))
{
//Log.WorkLog("\"msc.dll\"文件不存在", "文件丢失");
return;
}
//校验资源文件是否存在
if (!File.Exists(System.AppDomain.CurrentDomain.SetupInformation.ApplicationBase + "msc_x64.dll"))
{
//Log.WorkLog("\"msc_x64.dll\"文件不存在", "文件丢失");
return;
}
//校验资源文件是否存在
if (!File.Exists(System.AppDomain.CurrentDomain.SetupInformation.ApplicationBase + "tts\\common.jet"))
{
//Log.WorkLog("\"common.jet\"文件不存在", "文件丢失");
return;
}
//校验资源文件是否存在
if (!File.Exists(System.AppDomain.CurrentDomain.SetupInformation.ApplicationBase + "tts\\xiaofeng.jet"))
{
//Log.WorkLog("\"xiaofeng.jet\"文件不存在", "文件丢失");
return;
}
//校验资源文件是否存在
if (!File.Exists(System.AppDomain.CurrentDomain.SetupInformation.ApplicationBase + "tts\\xiaoyan.jet"))
{
//Log.WorkLog("\"xiaoyan.jet\"文件不存在", "文件丢失");
return;
}
///APPID请勿随意改动
string login_configs = "appid =你的APPID ";//登录参数,自己注册后获取的appid
uint audio_len = 0;
SynthStatus synth_status = SynthStatus.MSP_TTS_FLAG_STILL_HAVE_DATA;
_ret = MSPLogin(string.Empty, string.Empty, login_configs);
if (_ret != (int)ErrorCode.MSP_SUCCESS)
{
//Log.WorkLog($"MSPLogin失败,错误码\"{_ret}\"", "MSPLogin");
return;
}
string _params = string.Empty;
if (online)
{
_params = "ssm=1,ent=sms16k,vcn=xiaoyan,spd=medium,aue=speex-wb;7,vol=x-loud,auf=audio/L16;rate=16000";
}
else
{
var dir = System.AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
_params = $"aue=speex-wb;7,auf=audio/L16,ssm=1,ent=sms16k,vol=10,engine_type = local, voice_name=xiaoyan, tts_res_path =fo|{dir}\\tts\\xiaoyan.jet;fo|{dir}\\tts\\common.jet";
}
_sessionId = QTTSSessionBegin(_params, ref _ret);
//QTTSSessionBegin方法返回失败
if (_ret != (int)ErrorCode.MSP_SUCCESS)
{
//Log.WorkLog($"QTTSSessionBegin失败,错误码\"{_ret}\"", "QTTSSessionBegin");
return;
}
_ret = QTTSTextPut(Ptr2Str(_sessionId), text, (uint)Encoding.Default.GetByteCount(text), string.Empty);
//QTTSTextPut方法返回失败
if (_ret != (int)ErrorCode.MSP_SUCCESS)
{
//Log.WorkLog($"QTTSTextPut失败,错误码\"{_ret}\"", "QTTSTextPut");
return;
}
using (MemoryStream memoryStream = new MemoryStream())
{
memoryStream.Write(new byte[44], 0, 44);
var _ptr = Ptr2Str(_sessionId);
var _arrList = new List<byte>();
while (true)
{
IntPtr _src_ptr = QTTSAudioGet(_ptr, ref audio_len, ref synth_status, ref _ret);
byte[] array = new byte[audio_len];
if (audio_len > 0 && _src_ptr != IntPtr.Zero)
{
unsafe
{
fixed (byte* _dest_ptr = array)
{
memcpy(_dest_ptr, _src_ptr.ToPointer(), audio_len);
}
}
_arrList.AddRange(array);
}
if (synth_status == SynthStatus.MSP_TTS_FLAG_DATA_END || _ret != 0)
{
memoryStream.Write(_arrList.ToArray(), 0, _arrList.Count);
break;
}
}
WAVE_Header wave_Header = GetWaveHeader((int)memoryStream.Length - 44);
byte[] array2 = StructToBytes(wave_Header);
memoryStream.Position = 0L;
memoryStream.Write(array2, 0, array2.Length);
memoryStream.Position = 0L;
using (SoundPlayer soundPlayer = new SoundPlayer(memoryStream))
{
soundPlayer.Stop();
soundPlayer.Play();
}
}
}
}
}
catch (Exception ex)
{
//Log.ErrorLog(ex.ToString());
}
finally
{
_ret = QTTSSessionEnd(Ptr2Str(_sessionId), "");
_ret = MSPLogout();//退出登录
}
}
/// <summary>
/// 结构体初始化赋值
/// </summary>
/// <param name="data_len"></param>
/// <returns></returns>
private static WAVE_Header GetWaveHeader(int data_len)
{
return new WAVE_Header
{
RIFF_ID = 1179011410,
File_Size = data_len + 36,
RIFF_Type = 1163280727,
FMT_ID = 544501094,
FMT_Size = 16,
FMT_Tag = 1,
FMT_Channel = 1,
FMT_SamplesPerSec = 16000,
AvgBytesPerSec = 32000,
BlockAlign = 2,
BitsPerSample = 16,
DATA_ID = 1635017060,
DATA_Size = data_len
};
}
/// <summary>
/// 结构体转字符串
/// </summary>
/// <param name="structure"></param>
/// <returns></returns>
private static byte[] StructToBytes(object structure)
{
int num = Marshal.SizeOf(structure);
IntPtr intPtr = Marshal.AllocHGlobal(num);
byte[] result;
try
{
Marshal.StructureToPtr(structure, intPtr, false);
byte[] array = new byte[num];
Marshal.Copy(intPtr, array, 0, num);
result = array;
}
finally
{
Marshal.FreeHGlobal(intPtr);
}
return result;
}
/// 指针转字符串
/// </summary>
/// <param name="p">指向非托管代码字符串的指针</param>
/// <returns>返回指针指向的字符串</returns>
private static string Ptr2Str(IntPtr p)
{
if (p == IntPtr.Zero) { return string.Empty; }
List<byte> lb = new List<byte>();
while (Marshal.ReadByte(p) != 0)
{
lb.Add(Marshal.ReadByte(p));
p = p + 1;
}
byte[] bs = lb.ToArray();
return Encoding.Default.GetString(lb.ToArray());
}
}
}
公共部分枚举
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CiCommon
{
public enum ErrorCode
{
MSP_SUCCESS = 0,
MSP_ERROR_FAIL = -1,
MSP_ERROR_EXCEPTION = -2,
/* General errors 10100(0x2774) */
MSP_ERROR_GENERAL = 10100, /* 0x2774 */
MSP_ERROR_OUT_OF_MEMORY = 10101, /* 0x2775 */
MSP_ERROR_FILE_NOT_FOUND = 10102, /* 0x2776 */
MSP_ERROR_NOT_SUPPORT = 10103, /* 0x2777 */
MSP_ERROR_NOT_IMPLEMENT = 10104, /* 0x2778 */
MSP_ERROR_ACCESS = 10105, /* 0x2779 */
MSP_ERROR_INVALID_PARA = 10106, /* 0x277A */
MSP_ERROR_INVALID_PARA_VALUE = 10107, /* 0x277B */
MSP_ERROR_INVALID_HANDLE = 10108, /* 0x277C */
MSP_ERROR_INVALID_DATA = 10109, /* 0x277D */
MSP_ERROR_NO_LICENSE = 10110, /* 0x277E */
MSP_ERROR_NOT_INIT = 10111, /* 0x277F */
MSP_ERROR_NULL_HANDLE = 10112, /* 0x2780 */
MSP_ERROR_OVERFLOW = 10113, /* 0x2781 */
MSP_ERROR_TIME_OUT = 10114, /* 0x2782 */
MSP_ERROR_OPEN_FILE = 10115, /* 0x2783 */
MSP_ERROR_NOT_FOUND = 10116, /* 0x2784 */
MSP_ERROR_NO_ENOUGH_BUFFER = 10117, /* 0x2785 */
MSP_ERROR_NO_DATA = 10118, /* 0x2786 */
MSP_ERROR_NO_MORE_DATA = 10119, /* 0x2787 */
MSP_ERROR_SKIPPED = 10120, /* 0x2788 */
MSP_ERROR_ALREADY_EXIST = 10121, /* 0x2789 */
MSP_ERROR_LOAD_MODULE = 10122, /* 0x278A */
MSP_ERROR_BUSY = 10123, /* 0x278B */
MSP_ERROR_INVALID_CONFIG = 10124, /* 0x278C */
MSP_ERROR_VERSION_CHECK = 10125, /* 0x278D */
MSP_ERROR_CANCELED = 10126, /* 0x278E */
MSP_ERROR_INVALID_MEDIA_TYPE = 10127, /* 0x278F */
MSP_ERROR_CONFIG_INITIALIZE = 10128, /* 0x2790 */
MSP_ERROR_CREATE_HANDLE = 10129, /* 0x2791 */
MSP_ERROR_CODING_LIB_NOT_LOAD = 10130, /* 0x2792 */
/* Error codes of network 10200(0x27D8)*/
MSP_ERROR_NET_GENERAL = 10200, /* 0x27D8 */
MSP_ERROR_NET_OPENSOCK = 10201, /* 0x27D9 */ /* Open socket */
MSP_ERROR_NET_CONNECTSOCK = 10202, /* 0x27DA */ /* Connect socket */
MSP_ERROR_NET_ACCEPTSOCK = 10203, /* 0x27DB */ /* Accept socket */
MSP_ERROR_NET_SENDSOCK = 10204, /* 0x27DC */ /* Send socket data */
MSP_ERROR_NET_RECVSOCK = 10205, /* 0x27DD */ /* Recv socket data */
MSP_ERROR_NET_INVALIDSOCK = 10206, /* 0x27DE */ /* Invalid socket handle */
MSP_ERROR_NET_BADADDRESS = 10207, /* 0x27EF */ /* Bad network address */
MSP_ERROR_NET_BINDSEQUENCE = 10208, /* 0x27E0 */ /* Bind after listen/connect */
MSP_ERROR_NET_NOTOPENSOCK = 10209, /* 0x27E1 */ /* Socket is not opened */
MSP_ERROR_NET_NOTBIND = 10210, /* 0x27E2 */ /* Socket is not bind to an address */
MSP_ERROR_NET_NOTLISTEN = 10211, /* 0x27E3 */ /* Socket is not listenning */
MSP_ERROR_NET_CONNECTCLOSE = 10212, /* 0x27E4 */ /* The other side of connection is closed */
MSP_ERROR_NET_NOTDGRAMSOCK = 10213, /* 0x27E5 */ /* The socket is not datagram type */
/* Error codes of mssp message 10300(0x283C) */
MSP_ERROR_MSG_GENERAL = 10300, /* 0x283C */
MSP_ERROR_MSG_PARSE_ERROR = 10301, /* 0x283D */
MSP_ERROR_MSG_BUILD_ERROR = 10302, /* 0x283E */
MSP_ERROR_MSG_PARAM_ERROR = 10303, /* 0x283F */
MSP_ERROR_MSG_CONTENT_EMPTY = 10304, /* 0x2840 */
MSP_ERROR_MSG_INVALID_CONTENT_TYPE = 10305, /* 0x2841 */
MSP_ERROR_MSG_INVALID_CONTENT_LENGTH = 10306, /* 0x2842 */
MSP_ERROR_MSG_INVALID_CONTENT_ENCODE = 10307, /* 0x2843 */
MSP_ERROR_MSG_INVALID_KEY = 10308, /* 0x2844 */
MSP_ERROR_MSG_KEY_EMPTY = 10309, /* 0x2845 */
MSP_ERROR_MSG_SESSION_ID_EMPTY = 10310, /* 0x2846 */
MSP_ERROR_MSG_LOGIN_ID_EMPTY = 10311, /* 0x2847 */
MSP_ERROR_MSG_SYNC_ID_EMPTY = 10312, /* 0x2848 */
MSP_ERROR_MSG_APP_ID_EMPTY = 10313, /* 0x2849 */
MSP_ERROR_MSG_EXTERN_ID_EMPTY = 10314, /* 0x284A */
MSP_ERROR_MSG_INVALID_CMD = 10315, /* 0x284B */
MSP_ERROR_MSG_INVALID_SUBJECT = 10316, /* 0x284C */
MSP_ERROR_MSG_INVALID_VERSION = 10317, /* 0x284D */
MSP_ERROR_MSG_NO_CMD = 10318, /* 0x284E */
MSP_ERROR_MSG_NO_SUBJECT = 10319, /* 0x284F */
MSP_ERROR_MSG_NO_VERSION = 10320, /* 0x2850 */
MSP_ERROR_MSG_MSSP_EMPTY = 10321, /* 0x2851 */
MSP_ERROR_MSG_NEW_RESPONSE = 10322, /* 0x2852 */
MSP_ERROR_MSG_NEW_CONTENT = 10323, /* 0x2853 */
MSP_ERROR_MSG_INVALID_SESSION_ID = 10324, /* 0x2854 */
/* Error codes of DataBase 10400(0x28A0)*/
MSP_ERROR_DB_GENERAL = 10400, /* 0x28A0 */
MSP_ERROR_DB_EXCEPTION = 10401, /* 0x28A1 */
MSP_ERROR_DB_NO_RESULT = 10402, /* 0x28A2 */
MSP_ERROR_DB_INVALID_USER = 10403, /* 0x28A3 */
MSP_ERROR_DB_INVALID_PWD = 10404, /* 0x28A4 */
MSP_ERROR_DB_CONNECT = 10405, /* 0x28A5 */
MSP_ERROR_DB_INVALID_SQL = 10406, /* 0x28A6 */
MSP_ERROR_DB_INVALID_APPID = 10407, /* 0x28A7 */
/* Error codes of Resource 10500(0x2904)*/
MSP_ERROR_RES_GENERAL = 10500, /* 0x2904 */
MSP_ERROR_RES_LOAD = 10501, /* 0x2905 */ /* Load resource */
MSP_ERROR_RES_FREE = 10502, /* 0x2906 */ /* Free resource */
MSP_ERROR_RES_MISSING = 10503, /* 0x2907 */ /* Resource File Missing */
MSP_ERROR_RES_INVALID_NAME = 10504, /* 0x2908 */ /* Invalid resource file name */
MSP_ERROR_RES_INVALID_ID = 10505, /* 0x2909 */ /* Invalid resource ID */
MSP_ERROR_RES_INVALID_IMG = 10506, /* 0x290A */ /* Invalid resource image pointer */
MSP_ERROR_RES_WRITE = 10507, /* 0x290B */ /* Write read-only resource */
MSP_ERROR_RES_LEAK = 10508, /* 0x290C */ /* Resource leak out */
MSP_ERROR_RES_HEAD = 10509, /* 0x290D */ /* Resource head currupt */
MSP_ERROR_RES_DATA = 10510, /* 0x290E */ /* Resource data currupt */
MSP_ERROR_RES_SKIP = 10511, /* 0x290F */ /* Resource file skipped */
/* Error codes of TTS 10600(0x2968)*/
MSP_ERROR_TTS_GENERAL = 10600, /* 0x2968 */
MSP_ERROR_TTS_TEXTEND = 10601, /* 0x2969 */ /* Meet text end */
MSP_ERROR_TTS_TEXT_EMPTY = 10602, /* 0x296A */ /* no synth text */
/* Error codes of Recognizer 10700(0x29CC) */
MSP_ERROR_REC_GENERAL = 10700, /* 0x29CC */
MSP_ERROR_REC_INACTIVE = 10701, /* 0x29CD */
MSP_ERROR_REC_GRAMMAR_ERROR = 10702, /* 0x29CE */
MSP_ERROR_REC_NO_ACTIVE_GRAMMARS = 10703, /* 0x29CF */
MSP_ERROR_REC_DUPLICATE_GRAMMAR = 10704, /* 0x29D0 */
MSP_ERROR_REC_INVALID_MEDIA_TYPE = 10705, /* 0x29D1 */
MSP_ERROR_REC_INVALID_LANGUAGE = 10706, /* 0x29D2 */
MSP_ERROR_REC_URI_NOT_FOUND = 10707, /* 0x29D3 */
MSP_ERROR_REC_URI_TIMEOUT = 10708, /* 0x29D4 */
MSP_ERROR_REC_URI_FETCH_ERROR = 10709, /* 0x29D5 */
/* Error codes of Speech Detector 10800(0x2A30) */
MSP_ERROR_EP_GENERAL = 10800, /* 0x2A30 */
MSP_ERROR_EP_NO_SESSION_NAME = 10801, /* 0x2A31 */
MSP_ERROR_EP_INACTIVE = 10802, /* 0x2A32 */
MSP_ERROR_EP_INITIALIZED = 10803, /* 0x2A33 */
/* Error codes of TUV */
MSP_ERROR_TUV_GENERAL = 10900, /* 0x2A94 */
MSP_ERROR_TUV_GETHIDPARAM = 10901, /* 0x2A95 */ /* Get Busin Param huanid*/
MSP_ERROR_TUV_TOKEN = 10902, /* 0x2A96 */ /* Get Token */
MSP_ERROR_TUV_CFGFILE = 10903, /* 0x2A97 */ /* Open cfg file */
MSP_ERROR_TUV_RECV_CONTENT = 10904, /* 0x2A98 */ /* received content is error */
MSP_ERROR_TUV_VERFAIL = 10905, /* 0x2A99 */ /* Verify failure */
/* Error codes of IMTV */
MSP_ERROR_IMTV_SUCCESS = 11000, /* 0x2AF8 */ /* 成功 */
MSP_ERROR_IMTV_NO_LICENSE = 11001, /* 0x2AF9 */ /* 试用次数结束,用户需要付费 */
MSP_ERROR_IMTV_SESSIONID_INVALID = 11002, /* 0x2AFA */ /* SessionId失效,需要重新登录通行证 */
MSP_ERROR_IMTV_SESSIONID_ERROR = 11003, /* 0x2AFB */ /* SessionId为空,或者非法 */
MSP_ERROR_IMTV_UNLOGIN = 11004, /* 0x2AFC */ /* 未登录通行证 */
MSP_ERROR_IMTV_SYSTEM_ERROR = 11005, /* 0x2AFD */ /* 系统错误 */
/* Error codes of HCR */
MSP_ERROR_HCR_GENERAL = 11100,
MSP_ERROR_HCR_RESOURCE_NOT_EXIST = 11101,
/* Error codes of http 12000(0x2EE0) */
MSP_ERROR_HTTP_BASE = 12000, /* 0x2EE0 */
/*Error codes of ISV */
MSP_ERROR_ISV_NO_USER = 13000, /* 32C8 */ /* the user doesn't exist */
}
/// <summary>
/// vol参数的枚举常量
/// </summary>
public enum EnuVol
{
x_soft,
soft,
medium,
loud,
x_loud
}
/// <summary>
/// speed语速参数的枚举常量
/// </summary>
public enum EnuSpeed
{
x_slow,
slow,
medium,
fast,
x_fast
}
/// <summary>
/// speeker朗读者枚举常量
/// </summary>
public enum EnuSpeeker
{
小燕_青年女声_中英文_普通话 = 0,
小宇_青年男声_中英文_普通话,
凯瑟琳_青年女声_英语,
亨利_青年男声_英语,
玛丽_青年女声_英语,
小研_青年女声_中英文_普通话,
小琪_青年女声_中英文_普通话,
小峰_青年男声_中英文_普通话,
小梅_青年女声_中英文_粤语,
小莉_青年女声_中英文_台普,
小蓉_青年女声_汉语_四川话,
小芸_青年女声_汉语_东北话,
小坤_青年男声_汉语_河南话,
小强_青年男声_汉语_湖南话,
小莹_青年女声_汉语_陕西话,
小新_童年男声_汉语_普通话,
楠楠_童年女声_汉语_普通话,
老孙_老年男声_汉语_普通话
}
public enum SynthStatus
{
MSP_TTS_FLAG_STILL_HAVE_DATA = 1,
MSP_TTS_FLAG_DATA_END = 2,
MSP_TTS_FLAG_CMD_CANCELED = 0
}
}