diff --git a/app/api/controller/XunFeiController.php b/app/api/controller/XunFeiController.php index 32b7705b1..d5617df60 100644 --- a/app/api/controller/XunFeiController.php +++ b/app/api/controller/XunFeiController.php @@ -16,6 +16,7 @@ namespace app\api\controller; use IFlytek\Xfyun\Speech\ChatClient; use IFlytek\Xfyun\Speech\IatClient; +use IFlytek\Xfyun\Speech\TtsClient; use WebSocket\Client; /** @@ -24,7 +25,7 @@ use WebSocket\Client; */ class XunFeiController extends BaseApiController { - public array $notNeedLogin = ['chat', 'iat', 'iatWss']; + public array $notNeedLogin = ['chat', 'iat', 'tts']; private $app_id='2eda6c2e'; @@ -119,20 +120,21 @@ class XunFeiController extends BaseApiController } + //语音听写(流式版) public function iat() { header('X-Accel-Buffering: no'); $file = request()->file('audio'); if(empty($file)){ - return $this->fail('未上传音频文件'); + //return $this->fail('未上传音频文件'); } // 上传音频临时文件 $savename = \think\facade\Filesystem::putFile('audio', $file); $file = app()->getRootPath() . '/runtime/storage/' . $savename; if (!file_exists($file)) { - return $this->fail('未上传音频文件'); + //return $this->fail('未上传音频文件'); } - // $file = "https://lihai001.oss-cn-chengdu.aliyuncs.com/media/iat_mp3_16k.mp3"; + $file = "https://lihai001.oss-cn-chengdu.aliyuncs.com/media/iat_mp3_16k.mp3"; $audioFile = fopen($file, 'rb'); if ($audioFile === false) { return $this->fail('音频文件异常'); @@ -232,14 +234,66 @@ class XunFeiController extends BaseApiController return $this->data(['words' => $words]); } - - public function iatWss() + //语音合成(流式版) + public function tts() { header('X-Accel-Buffering: no'); - $iatHostUrl = "wss://iat-api.xfyun.cn/v2/iat"; - $iat = new IatClient($this->app_id,$this->api_key,$this->api_secret); - $wssUrl = $iat->assembleAuthUrl($iatHostUrl); - return $this->data(['wss_url' => $wssUrl]); + $ttsHostUrl = "wss://tts-api.xfyun.cn/v2/tts"; + $tts = new TtsClient($this->app_id,$this->api_key,$this->api_secret); + $date = date('YmdHis', time()); + $file_name = $date . '.mp3'; + $audioxxxFile = app()->getRootPath() . '/runtime/storage/audio/' . date('Ymd') . '/' . $file_name; + file_put_contents($audioxxxFile, $tts->request('欢迎使用科大讯飞语音能力,让我们一起用人工智能改变世界')->getBody()->getContents()); + exit; + + $client = new Client($tts->assembleAuthUrl($ttsHostUrl)); + $draft_content = '这是一段测试的音频文件,正常调试接口,这是一段测试的音频文件,正常调试接口'; + + $message = [ + 'common' => [ + 'app_id' => $this->app_id, + ], + 'business' => [ + 'aue' => 'raw', + 'auf' => 'audio/L16;rate=16000', + 'vcn' => 'xiaoyan', + 'tte' => 'UTF8', + ], + 'data' => [ + 'status' => 2, + 'text' => base64_encode($draft_content), + ] + ]; + try { + $client->send(json_encode($message, true)); + + //需要以追加的方式进行写文件 + $audioxxxFile = app()->getRootPath() . '/runtime/storage/audio/' . date('Ymd') . '/' . $file_name; + + file_put_contents($audioxxxFile, $tts->request('欢迎使用科大讯飞语音能力,让我们一起用人工智能改变世界')->getBody()->getContents()); + exit; + $audio_file = fopen($audioxxxFile, 'ab'); + $response = $client->receive(); + $response = json_decode($response, true); + do { + //返回的音频需要进行base64解码 + $audio = base64_decode($response['data']['audio']); + fwrite($audio_file, $audio); + //继续接收消息 + $response = $client->receive(); + $response = json_decode($response, true); + } while ($response['data']['status'] != 2); + fclose($audio_file); + return $this->data([ + 'audio_name' => $file_name, + 'audio_url' => './audio/' . $file_name, + ]); + } catch (Exception $e) { + return $this->fail($e->getMessage()); + } finally { + $client->close(); + } + return $this->fail('error'); } } diff --git a/extend/IFlytek/Xfyun/Speech/TtsClient.php b/extend/IFlytek/Xfyun/Speech/TtsClient.php index f4978f8b9..45d431958 100644 --- a/extend/IFlytek/Xfyun/Speech/TtsClient.php +++ b/extend/IFlytek/Xfyun/Speech/TtsClient.php @@ -97,4 +97,41 @@ class TtsClient ]); return $client->sendAndReceive(); } + + function assembleAuthUrl($addr,$method='GET') { + $apiKey=$this->apiKey; + $apiSecret=$this->apiSecret; + if ($apiKey == "" && $apiSecret == "") { // 不鉴权 + return $addr; + } + + $ul = parse_url($addr); // 解析地址 + if ($ul === false) { // 地址不对,也不鉴权 + return $addr; + } + // // $date = date(DATE_RFC1123); // 获取当前时间并格式化为RFC1123格式的字符串 + $timestamp = time(); + $rfc1123_format = gmdate("D, d M Y H:i:s \G\M\T", $timestamp); + // $rfc1123_format = "Mon, 31 Jul 2023 08:24:03 GMT"; + // 参与签名的字段 host, date, request-line + $signString = array("host: " . $ul["host"], "date: " . $rfc1123_format, $method . " " . $ul["path"] . " HTTP/1.1"); + // 对签名字符串进行排序,确保顺序一致 + // ksort($signString); + // 将签名字符串拼接成一个字符串 + $sgin = implode("\n", $signString); + // 对签名字符串进行HMAC-SHA256加密,得到签名结果 + $sha = hash_hmac('sha256', $sgin, $apiSecret,true); + + $signature_sha_base64 = base64_encode($sha); + + // 将API密钥、算法、头部信息和签名结果拼接成一个授权URL + $authUrl = "api_key=\"$apiKey\",algorithm=\"hmac-sha256\",headers=\"host date request-line\",signature=\"$signature_sha_base64\""; + // 对授权URL进行Base64编码,并添加到原始地址后面作为查询参数 + $authAddr = $addr . '?' . http_build_query(array( + 'host' => $ul['host'], + 'date' => $rfc1123_format, + 'authorization' => base64_encode($authUrl), + )); + return $authAddr; + } }