/************************************************************************************
|
|
Filename : OVRLipSyncSequence.cs
|
|
Content : LipSync frames container
|
|
Created : May 17th, 2018
|
|
Copyright : Copyright Facebook Technologies, LLC and its affiliates.
|
|
All rights reserved.
|
|
|
|
Licensed under the Oculus Audio SDK License Version 3.3 (the "License");
|
|
you may not use the Oculus Audio SDK except in compliance with the License,
|
|
which is provided at the time of installation or download, or which
|
|
otherwise accompanies this software in either electronic or hard copy form.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
https://developer.oculus.com/licenses/audio-3.3/
|
|
|
|
Unless required by applicable law or agreed to in writing, the Oculus Audio SDK
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
************************************************************************************/
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using UnityEngine;
|
|
|
|
// Sequence - holds ordered entries for playback
|
|
[System.Serializable]
|
|
public class OVRLipSyncSequence : ScriptableObject
|
|
{
|
|
public List<OVRLipSync.Frame> entries = new List<OVRLipSync.Frame>();
|
|
public float length; // in seconds
|
|
|
|
public OVRLipSync.Frame GetFrameAtTime(float time)
|
|
{
|
|
OVRLipSync.Frame frame = null;
|
|
if (time < length && entries.Count > 0)
|
|
{
|
|
float percentComplete = time / length;
|
|
frame = entries[(int)(entries.Count * percentComplete)];
|
|
}
|
|
return frame;
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
|
|
private static readonly int sSampleSize = 1024;
|
|
|
|
public static OVRLipSyncSequence CreateSequenceFromAudioClip(
|
|
AudioClip clip, bool useOfflineModel = false)
|
|
{
|
|
OVRLipSyncSequence sequence = null;
|
|
|
|
if (clip.channels > 2)
|
|
{
|
|
Debug.LogError(clip.name +
|
|
": Cannot process phonemes from an audio clip with " +
|
|
"more than 2 channels");
|
|
return null;
|
|
}
|
|
|
|
if (clip.loadType != AudioClipLoadType.DecompressOnLoad)
|
|
{
|
|
Debug.LogError(clip.name +
|
|
": Cannot process phonemes from an audio clip unless " +
|
|
"its load type is set to DecompressOnLoad.");
|
|
return null;
|
|
}
|
|
|
|
if (OVRLipSync.Initialize(clip.frequency, sSampleSize) != OVRLipSync.Result.Success)
|
|
{
|
|
Debug.LogError("Could not create Lip Sync engine.");
|
|
return null;
|
|
}
|
|
|
|
if (clip.loadState != AudioDataLoadState.Loaded)
|
|
{
|
|
Debug.LogError("Clip is not loaded!");
|
|
return null;
|
|
}
|
|
|
|
uint context = 0;
|
|
|
|
OVRLipSync.Result result = useOfflineModel
|
|
? OVRLipSync.CreateContextWithModelFile(
|
|
ref context,
|
|
OVRLipSync.ContextProviders.Enhanced,
|
|
Path.Combine(Application.dataPath, "Oculus/LipSync/Assets/OfflineModel/ovrlipsync_offline_model.pb"))
|
|
: OVRLipSync.CreateContext(ref context, OVRLipSync.ContextProviders.Enhanced);
|
|
|
|
if (result != OVRLipSync.Result.Success)
|
|
{
|
|
Debug.LogError("Could not create Phoneme context. (" + result + ")");
|
|
OVRLipSync.Shutdown();
|
|
return null;
|
|
}
|
|
|
|
List<OVRLipSync.Frame> frames = new List<OVRLipSync.Frame>();
|
|
float[] samples = new float[sSampleSize * clip.channels];
|
|
|
|
OVRLipSync.Frame dummyFrame = new OVRLipSync.Frame();
|
|
OVRLipSync.ProcessFrame(
|
|
context,
|
|
samples,
|
|
dummyFrame,
|
|
(clip.channels == 2) ? true : false
|
|
);
|
|
// frame delay in ms
|
|
float frameDelayInMs = dummyFrame.frameDelay;
|
|
|
|
int frameOffset = (int)(frameDelayInMs * clip.frequency / 1000);
|
|
|
|
int totalSamples = clip.samples;
|
|
for (int x = 0; x < totalSamples + frameOffset; x += sSampleSize)
|
|
{
|
|
int remainingSamples = totalSamples - x;
|
|
if (remainingSamples >= sSampleSize) {
|
|
clip.GetData(samples, x);
|
|
} else if (remainingSamples > 0) {
|
|
float[] samples_clip = new float[remainingSamples * clip.channels];
|
|
clip.GetData(samples_clip, x);
|
|
Array.Copy(samples_clip, samples, samples_clip.Length);
|
|
Array.Clear(samples, samples_clip.Length, samples.Length - samples_clip.Length);
|
|
} else {
|
|
Array.Clear(samples, 0, samples.Length);
|
|
}
|
|
|
|
OVRLipSync.Frame frame = new OVRLipSync.Frame();
|
|
if (clip.channels == 2)
|
|
{
|
|
// interleaved = stereo data, alternating floats
|
|
OVRLipSync.ProcessFrame(context, samples, frame);
|
|
}
|
|
else
|
|
{
|
|
// mono
|
|
OVRLipSync.ProcessFrame(context, samples, frame, false);
|
|
}
|
|
|
|
if (x < frameOffset)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
frames.Add(frame);
|
|
}
|
|
|
|
Debug.Log(clip.name + " produced " + frames.Count +
|
|
" viseme frames, playback rate is " + (frames.Count / clip.length) +
|
|
" fps");
|
|
OVRLipSync.DestroyContext(context);
|
|
OVRLipSync.Shutdown();
|
|
|
|
sequence = ScriptableObject.CreateInstance<OVRLipSyncSequence>();
|
|
sequence.entries = frames;
|
|
sequence.length = clip.length;
|
|
|
|
return sequence;
|
|
}
|
|
#endif
|
|
};
|