Skip to content

Commit

Permalink
更新接口
Browse files Browse the repository at this point in the history
  • Loading branch information
Entity-Now committed Oct 8, 2024
1 parent 3ce0cb9 commit b9b295c
Show file tree
Hide file tree
Showing 12 changed files with 277 additions and 102 deletions.
Binary file modified .vs/Edge_tts_sharp/DesignTimeBuild/.dtbcache.v2
Binary file not shown.
Binary file modified .vs/Edge_tts_sharp/v17/.futdcache.v2
Binary file not shown.
Binary file modified .vs/Edge_tts_sharp/v17/.suo
Binary file not shown.
Binary file modified .vs/ProjectEvaluation/edge_tts_sharp.metadata.v7.bin
Binary file not shown.
Binary file modified .vs/ProjectEvaluation/edge_tts_sharp.projects.v7.bin
Binary file not shown.
123 changes: 71 additions & 52 deletions Edge_tts_sharp/Edge_tts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ namespace Edge_tts_sharp
{
public class Edge_tts

Check warning on line 19 in Edge_tts_sharp/Edge_tts.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'Edge_tts'
{
/// <summary>
/// 调试模式
/// </summary>
public static bool Debug = false;
/// <summary>
/// 同步模式
/// </summary>
public static bool Await = false;
static string GetGUID()
{
return Guid.NewGuid().ToString().Replace("-","");
Expand All @@ -27,7 +35,7 @@ static string GetGUID()
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public static string FromatPercentage(double input)
static string FromatPercentage(double input)
{
string output;

Expand All @@ -53,26 +61,25 @@ static string ConvertToAudioFormatWebSocketString(string outputformat)
/// <param name="rate">语速,-100% - 100% 之间的值,无需传递百分号</param>
/// <param name="text"></param>
/// <returns></returns>
static string ConvertToSsmlText(string lang, string voice, int rate, string text)
static string ConvertToSsmlText(string lang, string voice, int rate, int volume, string text)

Check warning on line 64 in Edge_tts_sharp/Edge_tts.cs

View workflow job for this annotation

GitHub Actions / build

Parameter 'volume' has no matching param tag in the XML comment for 'Edge_tts.ConvertToSsmlText(string, string, int, int, string)' (but other parameters do)
{
return $"<speak version='1.0' xmlns='http://www.w3.org/2001/10/synthesis' xml:lang='{lang}'><voice name='{voice}'><prosody pitch='+0Hz' rate ='{FromatPercentage(rate)}' volume='+0%'>{text}</prosody></voice></speak>";
return $"<speak version='1.0' xmlns='http://www.w3.org/2001/10/synthesis' xml:lang='{lang}'><voice name='{voice}'><prosody pitch='+0Hz' rate ='{FromatPercentage(rate)}' volume='{volume}'>{text}</prosody></voice></speak>";
}
static string ConvertToSsmlWebSocketString(string requestId, string lang, string voice,int rate, string msg)
static string ConvertToSsmlWebSocketString(string requestId, string lang, string voice,int rate, int volume, string msg)
{
return $"X-RequestId:{requestId}\r\nContent-Type:application/ssml+xml\r\nPath:ssml\r\n\r\n{ConvertToSsmlText(lang, voice, rate, msg)}";
return $"X-RequestId:{requestId}\r\nContent-Type:application/ssml+xml\r\nPath:ssml\r\n\r\n{ConvertToSsmlText(lang, voice, rate, volume, msg)}";
}
/// <summary>
/// 语言转文本,将结果返回到回调函数中
/// </summary>
/// <param name="text">需要转换的文本</param>
/// <param name="voice">语音包名称</param>
/// <param name="rate">播放语速 -100到100的数值</param>
/// <param name="callback">第一个参数是binary数据</param>
public static void Invoke(string text, eVoice voice, int rate, Action<List<byte>> callback)
/// <param name="option">播放参数</param>
/// <param name="voice">音源参数</param>
public static void Invoke(PlayOption option, eVoice voice, Action<List<byte>> callback, IProgress<List<byte>> progress = null)

Check warning on line 77 in Edge_tts_sharp/Edge_tts.cs

View workflow job for this annotation

GitHub Actions / build

Parameter 'callback' has no matching param tag in the XML comment for 'Edge_tts.Invoke(PlayOption, eVoice, Action<List<byte>>, IProgress<List<byte>>)' (but other parameters do)

Check warning on line 77 in Edge_tts_sharp/Edge_tts.cs

View workflow job for this annotation

GitHub Actions / build

Parameter 'progress' has no matching param tag in the XML comment for 'Edge_tts.Invoke(PlayOption, eVoice, Action<List<byte>>, IProgress<List<byte>>)' (but other parameters do)
{
var binary_delim = "Path:audio\r\n";
var sendRequestId = GetGUID();
var binary = new List<byte>();
bool IsTurnEnd = false;

var wss = new Wss("wss://speech.platform.bing.com/consumer/speech/synthesize/readaloud/edge/v1?TrustedClientToken=6A5AA1D4EAFF4E9FB37E23D68491D6F4");
wss.OnMessage += (sender, e) =>
Expand All @@ -90,15 +97,15 @@ public static void Invoke(string text, eVoice voice, int rate, Action<List<byte>
// 返回内容
if (binary.Count > 0)
{
callback(binary);
callback?.Invoke(binary);
}
else
{
throw new Exception("返回值为空!");
}
// end of turn, close stream. 结束信号,可主动关闭socket
// 音频发送完毕后,最后还会收到一个表示音频结束的文本信息
wss.Close();
//wss.Close();
}
else if (data.Contains("Path:response"))
{
Expand All @@ -108,7 +115,8 @@ public static void Invoke(string text, eVoice voice, int rate, Action<List<byte>
{
// 未知错误,通常不会发生
}
Console.WriteLine(e.Data);
if (Debug) Console.WriteLine(e.Data);
IsTurnEnd = true;
}
else if (e.IsBinary)
{
Expand All @@ -121,85 +129,96 @@ public static void Invoke(string text, eVoice voice, int rate, Action<List<byte>
else
{
var index = Encoding.UTF8.GetString(data).IndexOf(binary_delim) + binary_delim.Length;
binary.AddRange(data.Skip(index));
var curVal = data.Skip(index);
binary.AddRange(curVal);
// 传出
progress?.Report(curVal.ToList());
}
}
};
wss.OnColse += (sender, e) =>
{
//File.WriteAllBytes($"{savePath}temp.mp3", binary.ToArray());
// ...
if (!string.IsNullOrEmpty(option.SavePath))
{
File.WriteAllBytes(option.SavePath, binary.ToArray());
}
};
wss.OnLog += (onmsg) =>
{
Console.WriteLine($"[{onmsg.level.ToString()}] {onmsg.msg}");
if(Debug) Console.WriteLine($"[{onmsg.level.ToString()}] {onmsg.msg}");
};
if (wss.Run())
{
wss.Send(ConvertToAudioFormatWebSocketString(voice.SuggestedCodec));
wss.Send(ConvertToSsmlWebSocketString(sendRequestId, voice.Locale, voice.Name, rate, text));
wss.Send(ConvertToSsmlWebSocketString(sendRequestId, voice.Locale, voice.Name, option.Rate, ((int)option.Volume * 100), option.Text));
}
while (Await && !IsTurnEnd)
{
Thread.Sleep(10);
}
}
/// <summary>
/// 另存为mp3文件
/// </summary>
/// <param name="text">需要转换的文本</param>
/// <param name="voice">语音包名称</param>
/// <param name="savePath">保存路径</param>
/// <param name="rate">播放语速 -100到100的数值</param>
public static void SaveAudio(string text, eVoice voice, int rate = 0, string savePath = "")
/// <param name="option">播放参数</param>
/// <param name="voice">音源参数</param>
public static void SaveAudio(PlayOption option, eVoice voice)
{
Invoke(text, voice, rate, (_binary) =>
if (string.IsNullOrEmpty(option.SavePath))
{
if (!string.IsNullOrWhiteSpace(savePath))
{
File.WriteAllBytes(savePath, _binary.ToArray());
}
else
{
throw new Exception("savePath 是空值.");
}
});
throw new Exception("保存路径为空,请核对参数后重试.");
}
Invoke(option, voice, null);
}
/// <summary>
/// 调用微软Edge接口,文字转语音
/// </summary>
/// <param name="msg">文本内容</param>
/// <param name="voice">音频名称</param>
/// <param name="rate">(可选)调整语速,是一个-100 - 100的数值</param>
/// <param name="volume">(可选)调整音量,是一个0 - 1的数值</param>
/// <param name="savePath">(可选)保存音频到指定路径</param>
public static void PlayText(string text, eVoice voice, int rate = 0, float volume = 1.0f, string savePath = "")
/// <param name="option">播放参数</param>
/// <param name="voice">音源参数</param>
public static void PlayText(PlayOption option, eVoice voice)
{
Invoke(text, voice, rate, (_binary) =>
Invoke(option, voice, (_binary) =>
{
Audio.PlayToByteAsync(_binary.ToArray(), volume);
if (!string.IsNullOrWhiteSpace(savePath))
{
File.WriteAllBytes(savePath, _binary.ToArray());
}
Audio.PlayToByteAsync(_binary.ToArray(), option.Volume);

Check warning on line 182 in Edge_tts_sharp/Edge_tts.cs

View workflow job for this annotation

GitHub Actions / build

Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.
});
}
/// <summary>
/// 获取一个`AudioPlayer`的对象
/// </summary>
/// <param name="text">文本内容</param>
/// <param name="voice">音频名称</param>
/// <param name="rate">(可选)调整语速,是一个-100 - 100的数值</param>
/// <param name="volume">(可选)调整音量,是一个0 - 1的数值</param>
public static AudioPlayer GetPlayer(string text, eVoice voice, int rate = 0, float volume = 1.0f)
/// <param name="option">播放参数</param>
/// <param name="voice">音源参数</param>
/// <returns></returns>
public static AudioPlayer GetPlayer(PlayOption option, eVoice voice)
{
AudioPlayer player = null;
Invoke(text, voice, rate, (_binary) =>
Invoke(option, voice, (_binary) =>
{
player = new AudioPlayer(_binary.ToArray(), volume);
player = new AudioPlayer(_binary.ToArray(), option.Volume);
});
while (player == null)
{
Thread.Sleep(10);
}
return player;
}
/// <summary>

Check warning on line 204 in Edge_tts_sharp/Edge_tts.cs

View workflow job for this annotation

GitHub Actions / build

XML comment is not placed on a valid language element
/// 同步等待播放音频结束
/// </summary>
/// <param name="option">播放参数</param>
/// <param name="voice">音源参数</param>
//public static void PlayTextAsync(PlayOption option, eVoice voice)
//{
// List<byte> buffer = new List<byte>();
// var audioStreamer = new Mp3AudioStreamer();
// var report = new Progress<List<byte>>((binary) =>
// {
// audioStreamer.OnAudioReceived(binary.ToArray());
// });
// Invoke(option, voice, null, report);

// audioStreamer.Stop();
//}

/// <summary>
/// 获取支持的音频列表
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion Edge_tts_sharp/Edge_tts_sharp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<!--以下是我们自己添加的-->
<!--版本号,重要性不必多说-->
<Version>1.1.1</Version>
<Version>1.1.2</Version>
<!--添加该配置,在编译时进行打包-->
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<!--添加该配置才会生成注释文件,默认无注释-->
Expand Down
29 changes: 29 additions & 0 deletions Edge_tts_sharp/Model/PlayOption.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace Edge_tts_sharp.Model
{
/// <summary>
/// 播放音频配置参数
/// </summary>
public class PlayOption
{
/// <summary>
/// 播放内容
/// </summary>
public string Text { get; set; }
/// <summary>
/// 语速,是一个-100 - 100的数值
/// </summary>
public int Rate { get; set; } = 0;
/// <summary>
/// 音量,是一个0 - 1的浮点数值
/// </summary>
public float Volume { get; set; } = 1.0f;
/// <summary>
/// 音频保存地址
/// </summary>
public string SavePath { get; set; } = string.Empty;
}
}
61 changes: 61 additions & 0 deletions Edge_tts_sharp/Utils/AudioStreamer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using NAudio.Wave;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace Edge_tts_sharp.Utils
{
public class Mp3AudioStreamer
{
private BufferedWaveProvider _bufferedWaveProvider;
private WaveOutEvent _waveOut;

public Mp3AudioStreamer()
{
// 设定音频格式,确保与解码后的PCM数据格式一致
_bufferedWaveProvider = new BufferedWaveProvider(new WaveFormat(44100, 16, 2));
_bufferedWaveProvider.BufferLength = 1024 * 1024; // 设置1MB缓冲区
_bufferedWaveProvider.DiscardOnBufferOverflow = true; // 避免缓冲区溢出

_waveOut = new WaveOutEvent
{
DesiredLatency = 100 // 减少播放延迟
};
_waveOut.Init(_bufferedWaveProvider);
_waveOut.Play();
}

// 处理WebSocket的音频数据
public void OnAudioReceived(byte[] mp3Data)
{
// 将 MP3 数据写入临时文件
string tempFilePath = Path.GetTempFileName() + ".mp3";
File.WriteAllBytes(tempFilePath, mp3Data);

// 使用 MediaFoundationReader 解码临时文件
using (var reader = new MediaFoundationReader(tempFilePath))
{
var buffer = new byte[16384]; // 16KB 缓冲区
int bytesRead;

while ((bytesRead = reader.Read(buffer, 0, buffer.Length)) > 0)
{
_bufferedWaveProvider.AddSamples(buffer, 0, bytesRead);
}
}

// 删除临时文件
File.Delete(tempFilePath);
}



public void Stop()
{
_waveOut.Stop();
}
}


}
53 changes: 31 additions & 22 deletions Edge_tts_sharp/Wss.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,32 +22,41 @@ public class Wss
public string wssAddress { get; set; }
public Wss(string url)
{
wssAddress = url;
wss = new WebSocket(wssAddress);
var sslProtocolHack = (System.Security.Authentication.SslProtocols)(SslProtocolsHack.Tls12 | SslProtocolsHack.Tls11 | SslProtocolsHack.Tls);
wss.SslConfiguration.EnabledSslProtocols = sslProtocolHack;
if (url.Contains("wss://"))
try
{
wss.SslConfiguration.ServerCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true;
}
wss.OnOpen += (sender, e) => {
OnLog(new Log { level = level.info, msg = "WebSocket Open" });
};
wss.OnMessage += (sender, e)=> OnMessage(sender, e);
wss.OnClose += (sender, e) =>
{
//TlsHandshakeFailure
if (e.Code == 1015 && wss.SslConfiguration.EnabledSslProtocols != sslProtocolHack)
wssAddress = url;
wss = new WebSocket(wssAddress);
var sslProtocolHack = (System.Security.Authentication.SslProtocols)(SslProtocolsHack.Tls12 | SslProtocolsHack.Tls11 | SslProtocolsHack.Tls);
wss.SslConfiguration.EnabledSslProtocols = sslProtocolHack;
if (url.Contains("wss://"))
{
OnLog(new Log { level = level.error, msg = "ssl握手失败,正在尝试重新连接." });
wss.SslConfiguration.EnabledSslProtocols = sslProtocolHack;
wss.Connect();
wss.SslConfiguration.ServerCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true;
}
else
wss.OnOpen += (sender, e) => {
OnLog(new Log { level = level.info, msg = "WebSocket Open" });
};
wss.OnMessage += (sender, e) => OnMessage(sender, e);
wss.OnClose += (sender, e) =>
{
OnColse(sender, e);
}
};
//TlsHandshakeFailure
if (e.Code == 1015 && wss.SslConfiguration.EnabledSslProtocols != sslProtocolHack)
{
OnLog(new Log { level = level.error, msg = "ssl握手失败,正在尝试重新连接." });
wss.SslConfiguration.EnabledSslProtocols = sslProtocolHack;
wss.Connect();
}
else
{
OnColse(sender, e);
}
};

}
catch (Exception e)
{
OnLog(new Log { level = level.error, msg = $"WebSocket Exception:{e}" });
throw e;
}
}
public bool Run()
{
Expand Down
Loading

0 comments on commit b9b295c

Please sign in to comment.