Bot Framewotk の ドキュメントにある、Speech Bot のサンプルををベースに、Speech to Text をちょっといじってみました。
https://docs.botframework.com/en-us/bot-intelligence/speech/#navtitle
成果物
今回の作ったサンプルは、音声ファイルのURLを Bot に食わせると、その音声ファイルの内容を分析し Text にして返してくれる Bot です。
デプロイしたのは、以下のやつです。
実行するとこんな感じです。

ついでに、サンプルのコード
まぁ、ドキュメントのソースのほぼコピペなんですけど。。
using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web.Http;
using Microsoft.Bot.Connector;
// add
using System.IO;
using System.Threading;
using System.Web;
using System.Text;
using System.Runtime.Serialization.Json;
using System.Runtime.Serialization;
using Newtonsoft.Json.Linq;
namespace SeeachTestBot
{
[BotAuthentication]
public class MessagesController : ApiController
{
///
<summary>
/// POST: api/Messages
/// Receive a message from a user and reply to it
/// </summary>
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
ConnectorClient connector = new ConnectorClient(new Uri(activity.ServiceUrl));
var text = "";
if (activity.Type == ActivityTypes.Message)
{
if (activity.Text.Length > 0)
{
//var reco = DoSpeechReco(activity.Attachments.First());
var reco = DoSpeechReco(activity.Text);
text = "You said : " + reco;
}
Activity reply = activity.CreateReply(text);
await connector.Conversations.ReplyToActivityAsync(reply);
}
else
{
HandleSystemMessage(activity);
}
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
private Activity HandleSystemMessage(Activity message)
{
if (message.Type == ActivityTypes.DeleteUserData)
{
// Implement user deletion here
// If we handle user deletion, return a real message
}
else if (message.Type == ActivityTypes.ConversationUpdate)
{
// Handle conversation state changes, like members being added and removed
// Use Activity.MembersAdded and Activity.MembersRemoved and Activity.Action for info
// Not available in all channels
}
else if (message.Type == ActivityTypes.ContactRelationUpdate)
{
// Handle add/remove from contact lists
// Activity.From + Activity.Action represent what happened
}
else if (message.Type == ActivityTypes.Typing)
{
// Handle knowing tha the user is typing
}
else if (message.Type == ActivityTypes.Ping)
{
}
return null;
}
// add
private string DoSpeechReco(String wevUrl)
{
AccessTokenInfo token;
string headerValue;
// Note: Sign up at https://microsoft.com/cognitive to get a subscription key.
// Use the subscription key as Client secret below.
Authentication auth = new Authentication("YOURUSERID", "<<BIng Speech API のキー>>");
string requestUri = "https://speech.platform.bing.com/recognize";
//URI Params. Refer to the Speech API documentation for more information.
requestUri += @"?scenarios=smd"; // websearch is the other main option.
requestUri += @"&appid=D4D52672-91D7-4C74-8AD8-42B1D98141A5"; // You must use this ID.
requestUri += @"&locale=en-US"; // read docs, for other supported languages.
// requestUri += @"&locale=ja-JP";
requestUri += @"&device.os=wp7";
requestUri += @"&version=3.0";
requestUri += @"&format=json";
requestUri += @"&instanceid=565D69FF-E928-4B7E-87DA-9A750B96D9E3";
requestUri += @"&requestid=" + Guid.NewGuid().ToString();
string host = @"speech.platform.bing.com";
string contentType = @"audio/wav; codec=""audio/pcm""; samplerate=16000";
var wav = HttpWebRequest.Create(wevUrl);
string responseString = string.Empty;
try
{
token = auth.GetAccessToken();
Console.WriteLine("Token: {0}\n", token.access_token);
//Create a header with the access_token property of the returned token
headerValue = "Bearer " + token.access_token;
Console.WriteLine("Request Uri: " + requestUri + Environment.NewLine);
HttpWebRequest request = null;
request = (HttpWebRequest)HttpWebRequest.Create(requestUri);
request.SendChunked = true;
request.Accept = @"application/json;text/xml";
request.Method = "POST";
request.ProtocolVersion = HttpVersion.Version11;
request.Host = host;
request.ContentType = contentType;
request.Headers["Authorization"] = headerValue;
using (Stream wavStream = wav.GetResponse().GetResponseStream())
{
byte[] buffer = null;
using (Stream requestStream = request.GetRequestStream())
{
int count = 0;
do
{
buffer = new byte[1024];
count = wavStream.Read(buffer, 0, 1024);
requestStream.Write(buffer, 0, count);
} while (wavStream.CanRead && count > 0);
// Flush
requestStream.Flush();
}
//Get the response from the service.
Console.WriteLine("Response:");
using (WebResponse response = request.GetResponse())
{
Console.WriteLine(((HttpWebResponse)response).StatusCode);
using (StreamReader sr = new StreamReader(response.GetResponseStream()))
{
responseString = sr.ReadToEnd();
}
Console.WriteLine(responseString);
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
Console.WriteLine(ex.Message);
}
dynamic data = JObject.Parse(responseString);
return data.header.name;
}
}
// add
[DataContract]
public class AccessTokenInfo
{
[DataMember]
public string access_token { get; set; }
[DataMember]
public string token_type { get; set; }
[DataMember]
public string expires_in { get; set; }
[DataMember]
public string scope { get; set; }
}
public class Authentication
{
public static readonly string AccessUri = "https://oxford-speech.cloudapp.net/token/issueToken";
private string clientId;
private string clientSecret;
private string request;
private AccessTokenInfo token;
private Timer accessTokenRenewer;
//Access token expires every 10 minutes. Renew it every 9 minutes only.
private const int RefreshTokenDuration = 9;
public Authentication(string clientId, string clientSecret)
{
this.clientId = clientId;
this.clientSecret = clientSecret;
//If clientid or client secret has special characters, encode before sending request
this.request = string.Format("grant_type=client_credentials&client_id={0}&client_secret={1}&scope={2}",
HttpUtility.UrlEncode(clientId),
HttpUtility.UrlEncode(clientSecret),
HttpUtility.UrlEncode("https://speech.platform.bing.com"));
this.token = HttpPost(AccessUri, this.request);
// renew the token every specfied minutes
accessTokenRenewer = new Timer(new TimerCallback(OnTokenExpiredCallback),
this,
TimeSpan.FromMinutes(RefreshTokenDuration),
TimeSpan.FromMilliseconds(-1));
}
//Return the access token
public AccessTokenInfo GetAccessToken()
{
return this.token;
}
//Renew the access token
private void RenewAccessToken()
{
AccessTokenInfo newAccessToken = HttpPost(AccessUri, this.request);
//swap the new token with old one
//Note: the swap is thread unsafe
this.token = newAccessToken;
Console.WriteLine(string.Format("Renewed token for user: {0} is: {1}",
this.clientId,
this.token.access_token));
}
//Call-back when we determine the access token has expired
private void OnTokenExpiredCallback(object stateInfo)
{
try
{
RenewAccessToken();
}
catch (Exception ex)
{
Console.WriteLine(string.Format("Failed renewing access token. Details: {0}", ex.Message));
}
finally
{
try
{
accessTokenRenewer.Change(TimeSpan.FromMinutes(RefreshTokenDuration), TimeSpan.FromMilliseconds(-1));
}
catch (Exception ex)
{
Console.WriteLine(string.Format("Failed to reschedule timer to renew access token. Details: {0}", ex.Message));
}
}
}
//Helper function to get new access token
private AccessTokenInfo HttpPost(string accessUri, string requestDetails)
{
//Prepare OAuth request
WebRequest webRequest = WebRequest.Create(accessUri);
webRequest.ContentType = "application/x-www-form-urlencoded";
webRequest.Method = "POST";
byte[] bytes = Encoding.ASCII.GetBytes(requestDetails);
webRequest.ContentLength = bytes.Length;
using (Stream outputStream = webRequest.GetRequestStream())
{
outputStream.Write(bytes, 0, bytes.Length);
}
using (WebResponse webResponse = webRequest.GetResponse())
{
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(AccessTokenInfo));
//Get deserialized object from JSON stream
AccessTokenInfo token = (AccessTokenInfo)serializer.ReadObject(webResponse.GetResponseStream());
return token;
}
}
}
}
補足
Microsoft Azure の Cognitive Service は、Speech API を使います。

現時点ではこのAPIにもFreeのプランがあります。(スクリーンショットにマウスアイコンかぶってるけど。。。)

作成した、Speech API のキーを、ソースの89行目あたりに設定しています。
あとは、Azure App Services の API Appsにデプロイして、
Bot Frameworkに登録してぐにゅぐにゅするくらいです。
元のサンプル
今回自分が作ったのは、元のサンプルの機能を削った形となっています。
元のサンプルは、Microsoft Bot Framework Channel Emulator での動作確認しかしてませんが、音声のついた Wev ファイルを添付して、メッセージに「SPACE」とうつと、解析結果のスペースの数も教えてくれるBotとなってます。
音声を分析して、そのままテキスト化したメッセージを返すのではなく、その結果をさらに分析したり、そのワードで検索かけたりとか、いろいろできそうですね。
Speech to Text のおまけ
Sppech to Text を使う場合、今回のサンプルは REST でたたくパターンでしたが、SDKを利用することもできます。
GitHubにあったサンプル
https://github.com/microsoft/cognitive-speech-stt-windows
動かして、マイクでしゃべるとこんな感じ。

デフォルトだと、英語なんですけど、
MainWindows.xaml.cs の241行目あたりの「DefaultLocale」で、「ja-JP」を指定してあげれば簡単に日本語化できます。
using に、以下が指定してあります。
using Microsoft.ProjectOxford.SpeechRecognition;
あとは、StartButton_Click() あたりから、見ていくといいと思います。
まとめ
Speech to Text をやっていて、ロケールを最初に知ってなくて、分析して判断したりできたりしないかなーって思ったのですが、SDKにしろRESTにしろ、ロケールを設定しているのでどうなんだろ。
その辺が、まだよくわからないかも。
SDK の勉強がてら、UWP で音を聞いて分析するのにチャレンジしてみようかなー。


コメント