[iOS][Swift 3] Microsoft Cognitive Services APIsのSpeech Translation API (音声翻訳) をSwiftで利用してみました。

logo_swift_400x400

1 はじめに

MicrosoftのCognitive Services APIs(マイクロソフト認知サービスAPI)では、AI技術を利用した、たくさんのサービスが提供されていますが、Translator APIは、そのうちの翻訳に関するものです。

https://www.microsoft.com/cognitive-services/en-us/translator-api

001

Translator APIでは、「テキスト」を入力とするものと、「音声」を入力とするものが提供されていますが、現在では、どちらも日本語に対応しています。

「音声から音声への翻訳」というニーズの場合は、Text Translation APIでは、別途、「音声」から「テキスト」への変換が必要になるため、断然、Speech Translation APIの方がお勧めとなるでしょう。

また、Speech Translation APIでは、入出力が「音声」になるだけでなく、入力(翻訳前)と出力(翻訳後)の内容を「テキスト」として取り出せるので、軽易に表示等に利用することもできます。

003

下図は、Speech Translation APIにおける処理の流れです。

002 https://www.microsoft.com/ja-jp/translator/speech.aspxより

今回は、このSpeech Translation API をiOS (Swift 3) で動作させて見ましたので、その紹介をさせて下さい。

最初に、作成したサンプルアプリを操作している様子です。下のセグメンテッドスイッチで、翻訳の言語を指定し、あとはボタンを押して話すだけです。

2 アプリの登録

Speech Translation APIを利用するためには、アプリの登録が必要です。

(1) リソ一スの作成

Microsoft Azureのポータルサイトにログインし、新規 > Intelligence + analytics > Cognitive Services APIs(プレビュー) とたどります。

004

Account nameResource group に、適当な名前を指定し、API typeTranslator Speech APIを選択します。

005

なお、価格レベルについては、初めて利用する場合は、F0を選択すると、2時間/月まで無料で試すことができます。

006 https://azure.microsoft.com/ja-jp/pricing/details/cognitive-services/translator-speech-api/ より

(2) KEYの取得

すべてのリソース から、作成したリソースを選択します。 007

Keysを選択して表示されるKEY 1 及び KEY 2 が、次の認証トークンを取得する処理で必要になります。

008

3 認証トークン

Speech Translator API を呼び出すたびに認証トークンが必要です。

認証トークンは、パラメータ(Subscription-Key)に、先程のKEY (KEY 1若しくはKEY 2のどちらでも良い) を指定して取得できます。(有効期間は10分間)

下記は、認証トークンを取得している例です。

fileprivate var tokenString = ""

func token(completionHandler: @escaping (String) -> Void) {
    if tokenString != "" {
        completionHandler(tokenString)
    }

    let key = "XXXXXXXXXXXXXXXXXXXXXXXXXXXX"
    let url = "https://api.cognitive.microsoft.com/sts/v1.0/issueToken?Subscription-Key=\(key)"

    let request = NSMutableURLRequest(url: NSURL(string: url)! as URL)
    request.httpMethod = "POST"

    let task = URLSession.shared.dataTask(with: request as URLRequest, completionHandler: {data, response, error in
        if (error == nil) {
            let str = String(data: data!, encoding: .utf8)
            if str != nil {
                self.tokenString = str!
                completionHandler(str!)
            } else {
                completionHandler("ERROR: Failed to convert to String")
            }
        } else {
            completionHandler("ERROR: \(error.debugDescription)"
        }
    })
    task.resume()
}


参考:Cognitive Services DocumentationAuthentication Token API

4 Starscream

Speech Translation APIのエンドポイントは、WebSocket で提供されています。SwiftでWebSocketを扱う場合、Starscreamというフレームワークがあり、これを利用させて頂くのが簡単でお薦めです。

Starscreamは、CocoaPodsで組み込み可能ですが、現在のmasterは、まだ、Swift 3.0 対応となっていません。

最新のXcode 8.3を利用する場合は、Siwft 3.1一択となってしまいますが、この場合、swift3というブランチが利用可能です。

pod 'Starscream',:git => 'https://github.com/daltoniam/Starscream', :branch => 'swift3'

5 APIの使い方

(1) エンドポイントへのアクセス

Speech Translation APIの基本的な使用例は、下記のとおりです。

GET wss://dev.microsofttranslator.com/speech/translate?from=en-US&to=it-IT&features=texttospeech&voice=it-IT-Elsa&api-version=1.0
Authorization: Bearer {access token}
X-ClientTraceId: {GUID}

そして、これを、Starscreamフレームワークを利用してSwiftで記述すると、次のようになります。

let to = "en"
let from = "ja"
let features = "partial,texttospeech"

let url = URL(string: "wss://dev.microsofttranslator.com/speech/translate?from=\(from)&to=\(to)&features=\(features)&api-version=1.0")
socket = WebSocket(url: url!, protocols: nil) 
socket.headers["Authorization"] = "Bearer " + (tokenString as String)
socket.headers["X-ClientTraceId"] = UUID.init().uuidString
socket.delegate = self
socket.connect()

ヘッダ Authorizationに設定しているのは、認証トークンです。パラメータ featuresTextToSpeechを設定しておくと、翻訳後の音声も取得できます。

(2) オーディオ送信

翻訳したい音声データは、RIFFフォーマットでクライアントからサーバへ送信されますが、その内容は、RIFFヘッダ、fmtチャンク、dataチャンクの順で並んでいます。 最初にデータを生成するためのクラスとして、下記のようなものを作成しました。[UInt8]のバッファに文字列や数値を順に配置して、最後にDataで受け取るクラスです。

class Buffer {

    var offset = 0
    var buffer: [UInt8]

    init(size: Int) {
        buffer = [UInt8](repeating : 0, count : size)
    }

    func append(short: Int16) {
        (0..<2).forEach { buffer[$0 + offset] = UInt8((Int(short) >> ($0 * 8)) & 0xff) }
        offset += 2
    }

    func append(int: Int) {
        (0..<4).forEach { buffer[$0 + offset] = UInt8((int >> ($0 * 8)) & 0xff) }
        offset += 4
    }

    func append(_ str: String) {
        str.utf8.enumerated().forEach { buffer[$0 + offset] = $1 }
        offset += str.utf8.count
    }

    var data: Data {
        return Data(bytes: buffer)
    }
}

そして、上記のクラスを利用して、各チャンクを生成しているコードは下記のとおりです。

//Riff Chunk
fileprivate func riff() -> Data {
    let size = 4
    let buffer = Buffer(size: size + 8)
    buffer.append("RIFF")
    buffer.append(int: size)
    buffer.append("WAVE")
    return buffer.data
}

// Wave Format Chunk
fileprivate func waveFormat() -> Data {
    let size = 16
    let buffer = Buffer(size: size + 8)
    buffer.append("fmt ")
    buffer.append(int: size)
    buffer.append(short: 1) // Audio Format
    buffer.append(short: 1) // Channels
    buffer.append(int: 16000) // SamplePerSecond
    buffer.append(int: 32000) // BytesPerSecond
    buffer.append(short: 2) // BlockAlign
    buffer.append(short: 16) //  BitsPerSample
    return buffer.data
}

// Wave Data Chunk (Header)
fileprivate func dataHeader(count: Int) -> Data {
    let buffer = Buffer(size: 8)
    buffer.append("data")
    buffer.append(int: count)
    return buffer.data
}

そして、サーバとの接続完了後に、これを送信しているコードです。

func websocketDidConnect(_ socket: WebSocket) {

    let channels = UnsafeBufferPointer(start: audioFileBuffer?.int16ChannelData, count: 1)
    let length = Int((audioFileBuffer?.frameCapacity)! * (audioFileBuffer?.format.streamDescription.pointee.mBytesPerFrame)!)
    let audioData = NSData(bytes: channels[0], length: length)

    if length > 50000 { // これ以下のサイズは、エラーとして送信しない
        socket.write(data: riff())
        socket.write(data: waveFormat())
        socket.write(data: dataHeader(count: length))
        socket.write(data: audioData as Data)
        socket.write(data: Buffer(size: 100000).data) // 最後に、いくらかの0データを送る必要がある
    } else {
        socket.disconnect()
    }
}

送受信処理は、StarscreamWebSocketDelegateの各メソッドで実装しています。 コードを置きましたので、不明な点があればご参照ください。


github [GitHub] https://github.com/furuya02/TranslatorSpeechAPISample


参考: Documentation Translation Speech API

6 最後に

今回は、Speech Translation APIについて、試してみましたが、非常に流暢で、かつ、優れていると実感しました。

時間を見つけて、Microsoftの Cognitive Services APIs の他の「認知」サービスも、色々試してみたいと思います。

7 参考リンク


最大100人まで同時通訳ーーMS、日本語の音声リアルタイム翻訳を提供開始
Microsoft 機械翻訳
Translator API を使い始める
Microsoft Translator のよくあるご質問
MicrosoftTranslator/InfoCenter-Speech-To-Speech-IOS-Swift
Microsoft Translator Documentation
Speech Translation API Document
StarScream
[iOS] 翻訳機能付Twitterクライアント ios

AWS Cloud Roadshow 2017 福岡