1.获取鉴权需要的Appkey以及Token 

  • appkey 获取方式:

                ​​​​​​阿里云登录 - 欢迎登录阿里云,安全稳定的云计算服务平台

  • token 获取方式:

            1. 获取  AccessKey和AccessKeySecret

             

            2. 根据AccessKey和AccessKeySecret 生成token

                (1)安装Alibaba Cloud SDK for PHP  SDK安装命令:

                        composer require  alibabacloud/sdk

                (2)php通过sdk获取token

                

    /**
     * @return string
     */
    protected function getToken()
    {
        AlibabaCloud::accessKeyClient(
            $accessKey,
            $accessKeySecret)
            ->regionId("cn-shanghai")
            ->asDefaultClient();
        try {
            $response = AlibabaCloud::nlsCloudMeta()
                ->v20180518()
                ->createToken()
                ->request();
            $token = $response["Token"];
            if ($token != NULL) {
                return  $token['Id'];
            }
            else {
                return false;
            }
        } catch (ClientException $exception) {
            // 获取错误消息
            $this->error = $exception->getMessage();
            return false;
        } catch (ServerException $exception) {              // 获取错误消息
            $this->error = $exception->getErrorMessage();
            return false;
        }
    }

   2.进行语音合成

         1.创建基类,用于实现公共部分

<?php

namespace app\common\aliyun;

use AlibabaCloud\Client\AlibabaCloud;
use AlibabaCloud\Client\Exception\ClientException;
use AlibabaCloud\Client\Exception\ServerException;
use yii\caching\Cache;

abstract  class Aliyun
{
    /**
     * 阿里云应用api_key
     * @var string
     */
    protected $api_key = '';

    /**
     * @var string
     */
    protected $accessKeyId = '你的accessKey';

    /**
     * @var string
     */
    protected $accessKeySecret = '你的accessKeySecret';

    /**
     * 控制台创建应用的key
     * @var string
     */
    protected $appKey = '你的appKey';

    /**
     * 自定义返回内容
     * @var
     */
    protected $return_json;


    /**
     * 错误信息
     * @var string
     */
    protected $error = '';


    /**
     * 返回错误
     * @return string
     */
    public function getError()
    {
        return $this->error;
    }


    /**
     * @return string
     */
    protected function getToken()
    {
        $ali_token = \Yii::$app->cache->get('aliyun_token');
        if ($ali_token) {
            return $ali_token;
        }
        AlibabaCloud::accessKeyClient(
            $this->accessKeyId,
            $this->accessKeySecret)
            ->regionId("cn-shanghai")
            ->asDefaultClient();
        try {
            $response = AlibabaCloud::nlsCloudMeta()
                ->v20180518()
                ->createToken()
                ->request();
            $token = $response["Token"];
            if ($token != NULL) {
                \Yii::$app->cache->set('aliyun_token', $token['Id'],60);
                return  $token['Id'];
            }
            else {
                return false;
            }
        } catch (ClientException $exception) {
            // 获取错误消息
            $this->error = $exception->getMessage();
            return false;
        } catch (ServerException $exception) {              // 获取错误消息
            $this->error = $exception->getErrorMessage();
            return false;
        }
    }


    /**
     * 返回响应数据
     * @return mixed
     */
    public function getReturnJson() {
        return $this->return_json;
    }

}

        2.新建应用类(语音合成实现类),继承上面父类

               这里请求使用php webstocks方式实现,composer安装webstocks,命令:

                       (1) composer require cboden/ratchet

                       (2) composer require react/socket

                这是我 composer.json 的内容

        

                

                文档地址生成式语音大模型服务_智能语音交互(ISI)-阿里云帮助中心

<?php

namespace app\common\aliyun;

use Ratchet\Client\Connector;
use React\EventLoop\Loop;
use React\Socket\Connector as SocketConnector;

class WsCosyVoice extends Aliyun
{

    /**
     * webstocket_url
     * @var string
     */
    protected $websocket_url = "wss://nls-gateway-cn-beijing.aliyuncs.com/ws/v1";

    /**
     * 输出文件路径
     * @var string
     */
    protected $output_file = '';

    /**
     * 音色名称
     * @var mixed|string
     */
    protected $voice_name = 'xiaoyun';

    /**
     * 合成文本
     * @var false|string[]
     */
    protected $texts;


    public function __construct($texts, $output_file, $options = [])
    {
        $this->output_file = $output_file;
        $this->texts = explode(',', $texts);
        if (isset($options['voice_name'])) {
            $this->voice_name = $options['voice_name'];
        }
    }

    /**
     * 启动
     */
    public function run()
    {
        $loop = Loop::get();

        if (file_exists($this->output_file)) {
            // 清空文件内容
            file_put_contents($this->output_file, '');
        }
        $socketConnector = new SocketConnector([
            'tcp' => [
                'bindto' => '0.0.0.0:0',
            ],
            'tls' => [
                'verify_peer' => false,
                'verify_peer_name' => false,
            ],
        ], $loop);
        $connector = new Connector($loop, $socketConnector);
        $output_file = $this->output_file;
        $taskId = $this->generate32Id();
        $token = $this->getToken();
        if (!$token) {
            return false;
        }
        $connector($this->websocket_url.'?token='.$token)->then(function ($conn) use ($loop, $output_file, $taskId) {
            //连接到WebSocket服务器
            // 发送 StartSynthesis 指令
            $this->sendStartSynthesi($conn, $taskId);
            // 定义发送上传识别文本指令的函数
            $sendContinueTask = function () use ($conn, $loop, $taskId) {
                // 待发送的文本
                foreach ($this->texts as $text) {
                    $continueTaskMessage = json_encode([
                        "header" => [
                            'message_id'=> $this->generate32Id(),
                            'task_id'=> $taskId,
                            'namespace' => "FlowingSpeechSynthesizer",
                            "name"=> "RunSynthesis",
                            "appkey"=> $this->appKey
                        ],
                        "payload" => [
                            "text" => $text,
                        ]
                    ]);
                    $conn->send($continueTaskMessage);
                }
                $this->sendFinishTaskMessage($conn, $taskId);
            };
            $taskStarted = false;
            // 监听消息
            $conn->on('message', function ($msg) use ($conn, $loop, $taskId, $output_file,$sendContinueTask,$taskStarted) {
                if ($msg->isBinary()) {
                    // 写入二进制数据到本地文件
                    file_put_contents($output_file, $msg->getPayload(), FILE_APPEND);
                } else {
                    // 处理非二进制消息
                    $response = json_decode($msg, true);
                    if (isset($response['header']['name'])) {
                        $this->handleEvent($conn, $response, $sendContinueTask, $loop, $taskId, $taskStarted);
                    }
                }
            });

            // 监听连接关闭
            $conn->on('close', function ($code = null, $reason = null) {
                $this->return_json['code'] = 1;
            });
        }, function ($e) {
            $this->return_json['code'] = 0;
            $this->return_json['error_message'] = $e->getMessage();
        });
        $loop->run();
    }

    /**
     * 生成32位ID
     * @return string
     */
    protected function generate32Id(): string
    {
        return bin2hex(random_bytes(16));
    }


    /**
     * 开启StartSynthesis 指令
     */
    public function sendStartSynthesi($conn, $taskId) {
        $runTaskMessage = json_encode([
            "header" => [
                'message_id'=> $this->generate32Id(),
                'task_id'=> $taskId,
                'namespace' => "FlowingSpeechSynthesizer",
                "name"=> "StartSynthesis",
                "appkey"=> $this->appKey
            ],
            "payload"=> [
                "voice"=> $this->voice_name,
                "format"=> "mp3",
                "sample_rate"=> 16000,
                "volume"=> 50,
                "speech_rate"=> 0,
                "pitch_rate"=> 0,
                "enable_subtitle"=> true
            ]
        ]);
        $conn->send($runTaskMessage);
    }


    /**
     * 发送 StopSynthesis 指令
     * @param $conn
     * @param $taskId
     */
    protected function sendFinishTaskMessage($conn, $taskId) {
        $finishTaskMessage = json_encode([
            "header" => [
                'message_id'=> $this->generate32Id(),
                'task_id'=> $taskId,
                'namespace' => "FlowingSpeechSynthesizer",
                "name"=> "StopSynthesis",
                "appkey"=> $this->appKey
            ],

        ]);
        // 准备发送StopSynthesis指令
        $conn->send($finishTaskMessage);
    }


    /**
     * 处理事件
     * @param $conn
     * @param $response
     * @param $sendContinueTask
     * @param $loop
     * @param $taskId
     * @param $taskStarted
     */
    protected function handleEvent($conn, $response, $sendContinueTask, $loop, &$taskStarted) {
        if ($response['header']['status'] == 20000000) {
            switch ($response['header']['name']) {
                case 'SynthesisStarted':
                    $taskStarted = true;
                    // 发送 RunSynthesis 指令
                    $sendContinueTask();
                    break;
                case 'SynthesisCompleted':   //所有音频以下发完毕
                    $taskStarted = false;
                    $conn->close();
                    break;
                case 'TaskFailed':
                    $conn->close();
                    $this->return_json['code'] = $response['header']['status'];
                    $this->return_json['error_message'] = $response['header']['status_text'];
                    break;
            }
        }else {
            // 错误信息
            $this->return_json['code'] = $response['header']['status'];
            $this->return_json['error'] = $response['header']['status_text'];
            $conn->close();
        }

        // 如果任务已完成,关闭连接
        if ($response['header']['name'] == 'SynthesisCompleted') {
            // 等待1秒以确保所有数据都已传输完毕
            $loop->addTimer(1, function() use ($conn) {
                // 关闭客户端连接
                $conn->close();
            });
        }

        // 如果没有收到 SynthesisStarted 事件,关闭连接
        if (!$taskStarted && in_array($response['header']['name'], ['SynthesisStarted'])) {
            $conn->close();
        }
    }
}

3. 调用示例

// 输出合成语音的保存路径
$output_file = Yii::$app->basePath . 'web/uploads/demo.mp3';
// 合成的文本
$texts = '我是demo,测试哈哈哈';
// 初始化语音合成
$cosyVoice = new WsCosyVoice($output_file, $texts, [
      'voice_name' => 'xiaoxun'  // 合成语音的音色,可传入调用声音复刻返回video_name
     ]);
// 运行
$cosyVoice->run();
// 打印执行信息
$json  = $cosyVoice->getReturnJson();
dump($json);exit();

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐