Open10
【Unity】自分の音声を録音し、AWS S3にwav形式でアップロードする
AWS SDK for .NETのインストール
- こちらの公式ページより、aws-sdk-netstandard2.0.zipをダウンロード → 解凍
- Assetsの下にAWSというフォルダを作成し、以下の2つのdllを入れる
- AWSSDK.Core.dll
- AWSSDK.S3.dll
- さらに、上記のdllの依存関係を解決するために、以下の3つのdllも同じく入れる
- Microsoft.Bcl.AsyncInterfaces.dll
- System.Runtime.CompilerServices.Unsafe.dll
- System.Threading.Tasks.Extensions.dll
AWSでの設定
- 新規バケットを作成
- バケット名以外はデフォの設定を変更していない(セキュアなリポジトリ)
- S3へのアクセス権限を持ったIAMを作成
- とりあえず
AmazonS3FullAccess
ポリシーのみをアタッチしておく
- とりあえず
- アクセスキーを発行し、シークレットキーとともに控えておく
コードの実装
- AWSの公式doc
- 作成するクラスは主に2つで(名前は...もっと考えます)
- VoiceAnalyzeManager: レコーディングの開始・終了や、S3アップロードの指示など起点となるクラス
- AWSS3Uploader: S3へのファイルアップロードを担当するクラス
実装: VoiceAnalyzeManager
- wav形式への変換については、こちらの記事を参考にしました。
- 録音が開始されたあとにマイクデバイスを変更した場合、録音が停止してしまうという問題が未解決です。現状録音に使われているデバイスから、同じデバイスへ変更する指示がいった場合も、録音が停止されることが分かりました。
using UnityEngine;
using System.IO;
public class VoiceAnalyzeManager : MonoBehaviour
{
private AudioClip _recordedClip;
private string _micDeviceName = "";
private const int SamplingFrequency = 11025; //サンプリング周波数: 44100がデフォっぽいが容量削減で下げている
private string _temporaryFilePath = "";
private AWSS3Uploader _awsS3Uploader;
void Start()
{
_awsS3Uploader = new AWSS3Uploader();
}
// 現在利用しているマイクが変更されたときの処理
// ただし、録音中に変更されると録音が停止されるという問題がある
public void SetMicDeviceName(string deviceName)
{
_micDeviceName = deviceName;
}
public void StartRecording(int lengthSec)
{
_recordedClip = Microphone.Start(deviceName: _micDeviceName, loop: false, lengthSec: lengthSec, frequency: SamplingFrequency);
}
public void FinishRecording()
{
if (Microphone.IsRecording(deviceName: _micDeviceName))
{
Microphone.End(deviceName: _micDeviceName);
}
else
{
return;
}
// wav形式に変換する
byte[] recordWavData = WavConverter.ToWav(_recordedClip);
_temporaryFilePath = Path.Combine(Application.temporaryCachePath, "MyRecording.wav");
File.WriteAllBytes(_temporaryFilePath, recordWavData);
UploadAudioToS3();
}
private void UploadAudioToS3()
{
// この辺のファイル名等はいい感じに調整
string objectName = "MyRecordingUploaded.wav";
_awsS3Uploader.StartUpload(objectName, _temporaryFilePath);
}
// tmpに保存してあったaudioデータを削除する
void OnDestroy()
{
if (File.Exists(_temporaryFilePath))
{
File.Delete(_temporaryFilePath);
}
}
}
実装: AWSS3Uploader
- TODO: キャンセル処理をちゃんとすべき
- access_keyやsecret_keyはビルドファイルに組み込むべきではないので、別途対応が必要。
using Amazon;
using Amazon.S3;
using Amazon.S3.Model;
using Cysharp.Threading.Tasks;
public class AWSS3Uploader
{
private readonly IAmazonS3 _s3Client;
private string _objectName = "";
private string _filePath = "";
// const
private const string BucketName = "bucket-name";
private static readonly RegionEndpoint BucketRegion = RegionEndpoint.APNortheast1;
// credentials
private const string S3AccessKey = "";
private const string S3SecretKey = "";
public AWSS3Uploader()
{
_s3Client = new AmazonS3Client(S3AccessKey, S3SecretKey, BucketRegion);
}
public async void StartUpload(string objectName, string filePath)
{
_objectName = objectName;
_filePath = filePath;
await UploadFileAsync();
}
private async UniTask UploadFileAsync()
{
PutObjectRequest request = new PutObjectRequest()
{
BucketName = BucketName,
Key = _objectName,
FilePath = _filePath
};
await _s3Client.PutObjectAsync(request);
}
}
UnityアプリからセキュアにS3にアクセスするために
- 自社でwebサービスを持っている場合は、Signed URLs or Signed Cookies for Amazon S3がよさそう感
Rubyでどう書けばよいかも聞いておく
require 'aws-sdk-s3'
class S3Controller < ApplicationController
def create
s3 = Aws::S3::Resource.new(region: 'us-west-2') # リージョンは適切なものに変更してください
bucket = s3.bucket('your-bucket-name') # S3 バケット名を適切に設定してください
object = bucket.object(params[:filename]) # ファイル名はリクエストパラメータから取得
presigned_url = object.presigned_url(:put, acl: 'public-read', content_type: params[:content_type])
render json: { url: presigned_url }
end
end
一時的なURLの期限は?
presigned_url = object.presigned_url(:put, acl: 'public-read', content_type: params[:content_type], expires_in: 3600)
一時URLを使用したスクリプト
- Webサービスなどの一時的なURLを発行するAPIを叩き、URLを取得する
- そのURLを用いて、S3へアップロードする
- これにより、UnityのクライアントがAWSのaccess token等を保持する必要がなくなる。また、AWS SDK for .NETも不要になる。
private async void UploadAudioToS3()
{
var token = this.GetCancellationTokenOnDestroy();
var presignedUrl = "https://一時的なURL";
await _awsS3Uploader.UploadObjectAsync(_filePath, presignedUrl, token);
}
using System.Threading;
using Cysharp.Threading.Tasks;
using UnityEngine.Networking;
using UnityEngine;
using System.IO;
public class AWSS3Uploader
{
public async UniTask<bool> UploadObjectAsync(string filePath, string url, CancellationToken token)
{
byte[] bytes = await File.ReadAllBytesAsync(filePath, token);
using (UnityWebRequest request = UnityWebRequest.Put(url, bytes))
{
await request.SendWebRequest().ToUniTask(cancellationToken: token);
if (request.result == UnityWebRequest.Result.Success)
{
return true;
}
else
{
return false;
}
}
}
}
一時的なURLの有効期限
- 有効期限(デフォルトでは15分に設定)されたURLの期限が切れた状態でアクセスすると、以下のようなエラーが発生する
UnityWebRequestException: HTTP/1.1 403 Forbidden
<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>AccessDenied</Code><Message>Request has expired</Message>...
Ruby側で実装したコード
class S3Service
REGION = "xxxxx"
BUCKET_NAME = "xxxxx"
def self.get_presigned_url(object_key)
s3_bucket.object(object_key).presigned_url(:put)
end
private
def self.s3_client
Aws::S3::Client.new(
region: REGION,
access_key_id: ENV['AWS_ACCESS_KEY_ID'],
secret_access_key: ENV['AWS_SECRET_ACCESS_KEY']
)
end
def self.s3_bucket
Aws::S3::Bucket.new(
name: BUCKET_NAME,
client: s3_client
)
end
end