ラベル android の投稿を表示しています。 すべての投稿を表示
ラベル android の投稿を表示しています。 すべての投稿を表示

2022/11/18

Android Speech Translator Plugin for Unity

AndroidのSpeechRecognizerとMLKit Translateを使ってオンデバイスでリアルタイムスピーチ翻訳をUnityのプラグインとして実装してみた

ソースパッケージはGistで公開

概要はGistのREADMEに書いたので、ここでは作ってみて思ったことを書き残しておく

Unityプラグイン実装

よく見る記事ではAndroidネイティブAPIをUnityプラグインにする場合、AndroidStudio使って.aarにしてUnityのPlugins/Androidに置くとか書いてあるのが多いが、ものすごくめんどくさいので直接ネイティブソースを置いてUnityのgradleでビルドするようにした。

ソースファイルだけではだめで、AndroidManifest.xml、gradleTemplate.properties、mainTemplate.gradleファイルが必要。これらのファイルは、Android Player Settings>Publishing SettingsのBuildで、Custom Main Manifest、Custom Main Gradle Template、Custom Gradle Properties Templateにチェックを入れると自動的にPlugins/Androidにファイルが作成される。よくUnityインストールフォルダの奥深くから、これらのファイルを自分でコピーしてくるようなことを書いてある記事を見かけるが、そんな必要はない。

AndroidManifest.xmlに

  <uses-permission android:name="android.permission.RECORD_AUDIO" />
  <uses-permission android:name="android.permission.INTERNET" />

gradleTemplate.propertiesに

android.useAndroidX=true

mainTemplate.gradleのdependenciesに

    implementation 'com.google.android.gms:play-services-mlkit-text-recognition:18.0.2'
    implementation 'com.google.mlkit:language-id:17.0.4'
    implementation 'com.google.mlkit:translate:17.0.1'

を追記。translateバージョンは17.0.1を入れたがその時点の最新を入れればいいと思う。
GistのunityPackageにはこれらのファイルは含まれている。

因みに、Unityのバージョンは2021.3.13f1を使用。2020.3.xxでもビルド可能だけど、Gradleなどのツールチェーンが今のAndroidStudioに比べかなり古いのでできるだけ新しいUnityバージョンを使うのがよさそう。2021.3.13f1で出力したAndroidProjectをAndroidStudioで開いてビルドしても問題なくビルドできた。

API Level

Player Settings>Other Settings:IdentificationのMinimum API LevelとTarget API Levelだけど、Minimumには24、TargetはHighest(30)を指定した。

SpeechRecognizer

品質はどうやらOSバージョンに強く依存するようだ。OS9くらいだとめちゃめちゃで使い物にならない。OS12だとまあ使える。OS13だともっといいかもしれない。

1回認識するとリスナーが終わってしまうので継続させるためには再度リスナー起動する必要がある。これが結構厄介。onResultsとonErrorイベント受けたタイミングでリスナー再起動している。本来ならonEndOfSpeechのタイミングが適切なのでは?と思うが、ここだといまいち安定しない。時々ERROR_CLIENTが出る。

何もしゃべらなかったとき、ERROR_SPEECH_TIMEOUTになるかなと思いきやERROR_NO_MATCHが出る。バグってない?と思ってしまう。

EXTRA_MAX_RESULTSで結果の最大数設定できるので1と設定しているが、結果が1つだけになるのはよっぽど自信があるときだけ。微妙な時は2~4つ候補が返ってくる。どんな仕様?

EXTRA_BIASING_STRINGSの使い方がさっぱり分からない。ドキュメントには

Optional list of strings, towards which the recognizer should bias the recognition results. These are separate from the device context.

と書かれているだけ。探しても具体的な設定方法とか効果とかがさっぱり分からない。
この設定で誤認識を少しは改善できるのでは?と思っているのだが。。。

onRmsChangedイベントって何?って思ったけど、簡単に言うと音量の事。rmsはRoot Mean Squareの略で一定時間内の平均音量ってこと。今音声拾ってますよというレベルメーターに使えるデータをずーっと返してくる。0..4はノイズ成分ぽいので捨ててます。

音声文字変換&音検知通知アプリもそうだけど、、

Pixel6に標準搭載された音声文字変換アプリに単語登録機能があるが、何入れても何の効果も感じられない。この機能がひょっとしてEXTRA_BIASING_STRINGSを使ってる部分かな?知らんけど。

結局EndToEndなんだよなぁ。学習モデルによるIN/OUTなので、しゃべる>直接日本語テキストが出てくる。出力結果に変化を与えたいと思ったら再学習させるしかないのだよね。
しゃべる>ひらがな認識>日本語入力エンジン>日本語テキストなら、途中のIME段階で辞書学習させれば誤認識も改善できそうなものだが。

固有名詞の誤認識を直したいときは、Unity上でonResultsで得られた文字列に対して特定の文字列があったら正しい文字列に直す、みたいな方法しか取れないかな。

2012/12/07

XBMC用.nfoファイルを用意する

XMBCがAndroid端末(MK808)で動きましたので、次の段階です。

再生時の操作については別途書きたいなと思ってますが、今回のテーマは録画ビデオファイルのリスト表示を、ファイル名だけとかサムネイルだけとかじゃなくてちゃんと番組名、内容説明を表示するようにしたいということへの取り組みです。

詳しくはないんですが、XBMCのImport-export library を読むと、nfoファイルというものを用意すれば、無機質なファイル名ではなくて、タイトル名やらなんやら、オンデマンドコンテンツのように表示させることが出来るとありますね。

ビデオファイル名.nfoファイルを作ってあげればよいと

初めは、XBMCの方でそのファイルをインポートするなんて操作をしなければならないのかな?面倒だな。なんて勘違いしてましたが、全然大丈夫でした。
ビデオファイルが入っている同じフォルダ内に拡張子を"nfo"にして置いておけば勝手に読んでライブラリ化してくれます。これなら自動化も楽ちんです。

nfoファイルの中身

XBMCがライブラリ化できるのは大きく分けて、映画(movie)、TV番組(tvshow)、TVエピソード(episodedetails)及び音楽ビデオ(musicvideo)のようです。

メジャーコンテンツ扱う場合なら、オンラインデータベースからの情報とリンクさせてより詳しいデータとかを取得出来たりするようですね。そういうのが一切関係ないローカルで汎用的なビデオ(video)とかあればいいなと思いましたがね。

どのライブラリタイプを使うか

どれが一番適しているか色々試してみました。

本来はtvshow(TV番組)としてライブラリ化するのが自然だろうなぁと思いましたが、tvshowは"tvshow.nfo"と名前が決まっていて、1ファイル1ディレクトリ管理じゃないとだめっぽい。
TV_Show_files_naming_conventions
この辺を読むと、さらにその下にepisodedetailsを置いて、ファイル名とか色々構成を整えないといい感じにならないらしい。

movieとmusicvideoは、ファイル単位のnfoを作るだけで読まれたので、どっちでもよさそうでしたが、musicvideoだと、並べ替えで「日付」を選ぶことが出来なくて、リストがいまいち使いにくい。どちらもpodcastのような公開日とかがないので、実ファイルの作成日が使えることが重要だったりします。

消去法で、内容はTV番組ですが、映画ライブラリとして構成するのが一番使い勝手が良いようです。結論を先に書きましたが、作成と読み込み手順は以下のような感じで進めました。

nfoファイル作成

録画サーバーから提供できる番組情報は、元々地デジEPG情報なので、番組名・内容・日時・長さ(分)・ジャンル・チャンネル位なので、それをどうにか対応させます。
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<movie>
 <id>番組ID</id>
 <title>番組名</title>
 <outline>内容</outline>
 <plot>内容</plot>
 <runtime>長さ</runtime>
 <genre>ジャンル</genre>
 <studio>チャンネル</studio>
 <director>録画日時</director>
 <set>番組名のサブセット</set>
</movie>
outline,plotどっちで内容が出てくれるのかよく分からないので両方に。
チャンネル項目がないので、studioに適当に入れました。
録画日時を表示させたいので、directorに適当に入れました。
studioとdirectorがとにかく目立つところに表示されるのでね。意味が全然違いますけど・・・
idってのがあったのでチケットID入れてみましたが、どこにも表示されませんな。

このnfoファイルを作成する録画システム上のスクリプトがこちら
xbmc-movie.rb

#!/usr/bin/ruby
# -*- coding: utf-8 -*-
#
require 'rubygems'
require 'active_record'
require 'rexml/document'
require 'digest/md5'

@media_path = '/opt/videos/'

ActiveRecord::Base.establish_connection(
        :adapter => 'mysql2',
        :host => 'localhost',
        :username => 'redmine',
        :password => 'redmine',
        :database => 'redmine'
)

class Issue < ActiveRecord::Base; end
class IssueCategories < ActiveRecord::Base; end
class Attachments < ActiveRecord::Base; end
class CustomValues < ActiveRecord::Base; end

def add_element_with_text (parent, element, text)
  e = parent.add_element element
  e.text = text
end

# create a nfo file
def create_nfo (issue)

  video = Attachments.first(:conditions => {
                              :container_id => issue.id,
                              :content_type => "video/mp4"})
  channel = CustomValues.first(:conditions => {
                                 :customized_id => issue.id,
                                 :custom_field_id => 2})
  time_tmp = format("%04d", CustomValues.first( :conditions => {
                                                  :customized_id => issue.id,
                                                  :custom_field_id => 1}).value.to_i)
  time = "#{time_tmp[0..1]}:#{time_tmp[2..3]}"

  # 試行錯誤の末こんな感じに
  set = issue.subject.sub(/[  ”’#(「・◇].*/,'')

  xml = REXML::Document.new
  info = xml.add_element 'movie'
  add_element_with_text info, 'id', issue.id
  add_element_with_text info, 'title', issue.subject
  add_element_with_text info, 'year', issue.start_date
  add_element_with_text info, 'runtime', issue.estimated_hours
  add_element_with_text info, 'outline', issue.description
  add_element_with_text info, 'plot', issue.description
  add_element_with_text info, 'genre', IssueCategories.find(issue.category_id).name
  add_element_with_text info, 'director', "#{issue.start_date} / #{time}"
  add_element_with_text info, 'studio', channel.value
  add_element_with_text info, 'set', set

  nfo_file = video.filename.to_s.sub(/.mp4/,".nfo")
  puts "#{nfo_file}:#{set}"

  nfo = File.open(@media_path + nfo_file,"w")
  nfo.puts '<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>'
  xml.write nfo
  nfo.puts "\n"
  nfo.close

  # 本件に関係ないので省略
  #attach "nfoファイル", issue.id, nfo_file, "xbmc/nfo"
end

# 公開ステータスの録画チケット
Issue.find( :all, :conditions => {:tracker_id => 3,:status_id => 4}).each {|issue|
  create_nfo issue
#  break
}

今までのビデオファイル全てにnfoファイルを作成するものになってますが、録画システムとしては番組録画後にそれ用のnfoファイルを作成するフローにしています。以前podcastがらみでやったこととほとんど同じ要領なので、ちゃちゃと出来ました。

XBMC側で読んでもらうには

ビデオソースを追加して、HTTPとかSMBとかで、ビデオフォルダを見れるようにします。
この辺の手順は他のHOWTO記事を参考にされた方が親切でいいです。

コンテンツの種類として”(映画)”を選んで設定ボタンを押して、有効になっているやつを全部外します。外さなくてもいいけれど勝手にDBへアクセスしに行ったりするようなので。
設定を全部OKしてダイアログを全部閉じるとライブラリ化はその瞬間から自動で開始されます。

終了するまで数分時間がかかりました。その最中でも大丈夫そうでしたが、落ち着いたかなぁという頃合いにXBMCのホームに戻るとビデオの下にはにはライブラリ、横に「映画」という項目が増えています。この辺をたどっていくとnfoファイルで設定した内容でビデオファイルが表示されました。

新しい順にリストさせるには、表示オプションで、並べ替えを「日付」にソートを「降順」にすれば新しい順に出ます。後は表示の種類を自分の好みにしてみる。うーん。いい感じです。

setがいい

setタグのところで、番組名のサブセットと書きましたが、XBMC上で同じセット名は仮想的なサブディレクトリとして扱われます。
毎週録画している番組とか、本来はエピソード番号とかで管理されるといいと思いますが、地デジ情報からは得られないので、番組名の共通項をセット名として定義することでシリーズ番組とかを同じセットグループとして分類が出来て便利です。

日本語での検索

表示オプションから検索が出来ますが、残念ながら日本語入力が出来ません。これはがっかりですが、xbmcユーザからすると周知の事実らしいですね。バージョンが上がると解決するんでしょうか。パッチとかあるんでしょうか?

別のAndroid/iOS/その他デバイスでXBMCリモートアプリを使って検索すれば日本語検索できるんで、さほど問題ではないですけど、出来れば普通に本体だけで済ませたいですね。

何だか思っていた以上の環境が整いそうな予感です。
もっといい感じにできるのか調べてみましょう。