添加baidu tts asr

This commit is contained in:
lilong 2020-05-15 23:45:04 +08:00
parent d01bf2c8bf
commit a75f6c59f2
7 changed files with 201 additions and 131 deletions

View File

@ -14,32 +14,21 @@ class AudioController extends Controller
*
* @return \Illuminate\Http\Response
*/
public function index()
public function index(Request $request)
{
if ($request->ajax()) {
$res = Audio::orderBy('id')->paginate($request->get('limit', 30));
$data = [
'code' => 0,
'msg' => '正在请求中...',
'count' => $res->total(),
'data' => $res->items(),
];
return response()->json($data);
}
return view('admin.audio.index');
}
public function data(Request $request)
{
$res = Audio::orderBy('id')->paginate($request->get('limit', 30));
$data = [
'code' => 0,
'msg' => '正在请求中...',
'count' => $res->total(),
'data' => $res->items(),
];
return response()->json($data);
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
}
/**
* Store a newly created resource in storage.
@ -53,84 +42,24 @@ class AudioController extends Controller
if ($text==null){
return response()->json(['code'=>1,'msg'=>'请输入待合成文本']);
}
$url = 'http://api.xfyun.cn/v1/service/v1/tts';
$appid = config('freeswitch.xfyun.appid');
$apikey = config('freeswitch.xfyun.apikey');
$param = array (
'auf' => 'audio/L16;rate=8000',
'aue' => 'raw',
'voice_name' => 'xiaoyan',
'speed' => '50', //这三个参数必需是字符串
'volume' => '50', //这三个参数必需是字符串
'pitch' => '50', //这三个参数必需是字符串
'engine_type' => 'intp65',
);
$time = (string)time();
$xparam = base64_encode(json_encode(($param)));
$checksum = md5($apikey.$time.$xparam);
$header = array(
'X-CurTime:'.$time,
'X-Param:'.$xparam,
'X-Appid:'.$appid,
'X-CheckSum:'.$checksum,
'X-Real-Ip:127.0.0.1',
'Content-Type:application/x-www-form-urlencoded; charset=utf-8'
);
$content = [
'text' => $text,
];
try{
$response = $this->tocurl($url, $header, $content);
$header = $response['header'];
if($header['content_type'] == 'audio/mpeg'){
$ext = $param['aue']=='raw'?'.wav':'.mp3';
$filename = config('freeswitch.xfyun.sounds').$time.$ext;
file_put_contents($filename, $response['body']);
Audio::create(array_merge($param,[
'url' => $filename,
'text' => $text
$model = new Audio();
$res = $model->tts($text);
if ($res['code']==0) {
Audio::create([
'url' => $res['data']['url'],
'path' => $res['data']['path'],
'text' => $text,
'user_id' => auth()->user()->id,
]));
return response()->json(['code'=>0,'msg'=>'合成成功','data'=>['url'=>$filename]]);
return response()->json(['code'=>0,'msg'=>'合成成功','data'=>['url'=>$res['data']['url']]]);
}
return response()->json(['code'=>1,'msg'=>'合成失败','data'=>json_decode($response['body'],true)]);
return response()->json($res);
}catch (\Exception $exception){
return response()->json(['code'=>1,'msg'=>'合成失败'.$exception->getMessage()]);
return response()->json(['code'=>1,'msg'=>'合成失败']);
}
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
//
}
/**
* Show the form for editing the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function edit($id)
{
//
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, $id)
{
//
}
/**
* Remove the specified resource from storage.
@ -146,38 +75,15 @@ class AudioController extends Controller
return response()->json(['code'=>1,'msg'=>'请选择删除项']);
}
try{
if (file_exists($audio->url)){
unlink($audio->url);
if (file_exists($audio->path)){
unlink($audio->path);
}
$audio->delete();
return response()->json(['code'=>0,'msg'=>'删除成功']);
}catch (\Exception $exception){
return response()->json(['code'=>1,'msg'=>'删除失败'.$exception->getMessage()]);
return response()->json(['code'=>1,'msg'=>'删除失败']);
}
}
public function tocurl($url, $header, $content){
$ch = curl_init();
if(substr($url,0,5)=='https'){
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 跳过证书检查
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, true); // 从证书中检查SSL加密算法是否存在
}
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($content));
$response = curl_exec($ch);
$error=curl_error($ch);
//var_dump($error);
if($error){
die($error);
}
$header = curl_getinfo($ch);
curl_close($ch);
$data = array('header' => $header,'body' => $response);
return $data;
}
}

109
app/Models/Audio.php Normal file
View File

@ -0,0 +1,109 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
use GuzzleHttp\Client;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\Storage;
class Audio extends Model
{
protected $table = 'audio';
protected $fillable = ['text','url','path','user_id'];
public function getAccessToken(){
$key = 'baidu_token';
$token = Redis::get($key);
if ($token == null) {
$client = new Client();
try{
$response = $client->get(config('freeswitch.baidu.url.token'),[
'query' => [
'grant_type' => 'client_credentials',
'client_secret' => config('freeswitch.baidu.appKey'),
'client_id' => config('freeswitch.baidu.appSecret'),
]
]);
$result = json_decode($response->getBody(),true);
$token = $result['access_token'];
Redis::setex($key,$result['expires_in']-100,$token);
}catch(\Exception $exception){
$token = null;
}
}
return $token;
}
public function tts($text){
$token = $this->getAccessToken();
if ($token==null) {
return ['code'=>1,'msg'=>'获取accessToken失败'];
}
$client = new Client();
try{
$response = $client->post(config('freeswitch.baidu.url.tts'),[
'body' => http_build_query([
'tex' => urlencode($text),
'tok' => $token,
'cuid' => 'freeswitch',
'ctp' => 1,
'lan' => 'zh',
'spd' => 5 //语速0-15默认为5中语速
'pit' => 5 //音调0-15默认为5中音调
'vol' => 5 //音量0-15默认为5中音量
'aue' => 6 //3为mp3格式(默认) 4为pcm-16k5为pcm-8k6为wav内容同pcm-16k; 注意aue=4或者6是语音识别要求的格式
'per' => 4 //度小宇=1度小美=0度逍遥=3度丫丫=4 ,度博文=106度小童=110度小萌=111度米朵=103度小娇=5
])
]);
if ($response->getHeader('Content-Type')=='audio/wav') {
$file_url = Str::random().'.wav';
$file_path = public_path('uploads').'/'.$file_url;
Storage::disk('uploads')->put($file,$response->getBody());
return ['code'=>0,'msg'=>'合成成功','data'=>['url'=>$file_url,'path'=>$file_path];
}
return ['code'=>1,'msg'=>'合成失败'];
}catch(\Exception $exception){
return ['code'=>1,'msg'=>'合成异常'];
}
}
public function asr($file){
$token = $this->getAccessToken();
if ($token==null) {
return ['code'=>1,'msg'=>'获取accessToken失败'];
}
if (!file_exists($file)) {
return ['code'=>1,'msg'=>'文件不存在'];
}
$client = new Client();
try{
$response = $client->post(config('freeswitch.baidu.url.asr'),[
'body' => base64_encode(file_get_contents($file)),
'json' => http_build_query([
'format' => 'wav',
'rate' => 16000,
'channel' => 1,
'cuid' => 'freeswitch',
'token' => $token,
'dev_pid' => 1537 //语速0-15默认为5中语速
])
]);
$result = json_decode($response->getBody(),true);
if (!$result['err_no']) {
return ['code'=>0,'msg'=>'识别成功','data'=>$result['result'][0]];
}
return ['code'=>1,'msg'=>'识别失败'];
}catch(\Exception $exception){
return ['code'=>1,'msg'=>'识别异常'];
}
}
}

View File

@ -81,4 +81,14 @@ return [
//自增ID的key,用于群呼时生成uuid
'callcenter_call' => 'callcenter_call_id',
],
'baidu' => [
'appId' => '15143241',
'appKey' => 'YI8bXhDcthkBrQNRQFDf2iCG',
'appSecret' => 'NQo5Nk9rEZVAndjthwQd8OtYvNfjBcFM',
'url' => [
'token' => 'https://openapi.baidu.com/oauth/2.0/token',
'tts' => 'http://tsn.baidu.com/text2audio',
'asr' => 'https://vop.baidu.com/pro_api',
],
],
];

View File

@ -0,0 +1,36 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class Audio extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('audio', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('text')->nullable()->comment('待合成文本');
$table->string('url')->nullable()->comment('合成的语音文件的url');
$table->string('path')->nullable()->comment('合成的语音文件的完整路径');
$table->unsignedBigInteger('user_id')->comment('操作用户');
$table->timestamps();
});
\DB::statement("ALTER TABLE `audio` comment '语音合成'");
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('audio');
}
}

View File

@ -218,6 +218,14 @@ class MenuTableSeeder extends Seeder
'type' => 1,
'permission_name' => 'data.cdr.count',
],
[
'name' => '语音合成',
'route' => 'admin.audio',
'url' => null,
'icon' => 'layui-icon-ios',
'type' => 1,
'permission_name' => 'data.audio',
],
]
],
];

View File

@ -256,6 +256,14 @@ class UserTableSeeder extends Seeder
]
],
[
'name' => 'data.audio',
'display_name' => '语音合成',
'child' => [
['name' => 'data.audio.create', 'display_name' => '添加'],
['name' => 'data.audio.destroy', 'display_name' => '删除'],
]
],
]
],
];

View File

@ -16,7 +16,7 @@
<table id="dataTable" lay-filter="dataTable"></table>
<script type="text/html" id="options">
<div class="layui-btn-group">
@can('pbx.audio.destroy')
@can('data.audio.destroy')
<a class="layui-btn layui-btn-danger layui-btn-sm " lay-event="del">删除</a>
@endcan
</div>
@ -35,20 +35,13 @@
var dataTable = table.render({
elem: '#dataTable'
,height: 500
,url: "{{ route('admin.audio.data') }}" //数据接口
,url: "{{ route('admin.audio') }}" //数据接口
,page: true //开启分页
,cols: [[ //表头
{checkbox: true,fixed: true}
//,{field: 'id', title: 'ID', sort: true,width:80}
,{field: 'text', title: '文本'}
,{field: 'url', title: '地址'}
,{field: 'auf', title: '采样率'}
,{field: 'aue', title: '编码'}
,{field: 'voice_name', title: '发音人'}
,{field: 'speed', title: '语速'}
,{field: 'volume', title: '音量'}
,{field: 'pitch', title: '音高'}
,{field: 'engine_type', title: '引擎'}
,{fixed: 'right', width: 150, align:'center', toolbar: '#options', title:'操作'}
]]
});
@ -63,7 +56,7 @@
obj.del(); //删除对应行tr的DOM结构
}
layer.close(index);
var icon = result.code==0?6:5;
var icon = result.code==0?1:2;
layer.msg(result.msg,{icon:icon})
});
});
@ -77,13 +70,13 @@
$.post("{{route('admin.audio.store')}}",parms,function (res) {
layer.closeAll('loading');
if (res.code==0){
layer.msg(res.msg,{icon:6},function () {
layer.msg(res.msg,{icon:1},function () {
dataTable.reload({
page:{curr:1}
});
})
} else {
layer.msg(res.msg,{icon:5})
layer.msg(res.msg,{icon:2})
}
});
return false;