Improves microphone audio capture.
- Uses audio monitoring during recording. - Records the audio in a WAV file.
This commit is contained in:
parent
d0ff6aac6b
commit
15db99b032
|
|
@ -3,7 +3,7 @@
|
||||||
<ui:VisualElement name="VE_Panel" style="flex-grow: 1;">
|
<ui:VisualElement name="VE_Panel" style="flex-grow: 1;">
|
||||||
<ui:Button text="Iniciar sessão" name="B_Session" />
|
<ui:Button text="Iniciar sessão" name="B_Session" />
|
||||||
<ui:Label text="Chat:" />
|
<ui:Label text="Chat:" />
|
||||||
<ui:TextField placeholder-text="Digite sua mensagem" name="TF_ChatInput" hide-mobile-input="false" class="expandable" />
|
<ui:TextField placeholder-text="Digite sua mensagem" name="TF_ChatInput" class="expandable" />
|
||||||
<ui:Button text="Enviar mensagem" name="B_SendChat" />
|
<ui:Button text="Enviar mensagem" name="B_SendChat" />
|
||||||
<ui:ProgressBar value="0" title="Processando... Por favor, espere..." name="PB_Progress" enabled="true" />
|
<ui:ProgressBar value="0" title="Processando... Por favor, espere..." name="PB_Progress" enabled="true" />
|
||||||
<ui:TextField placeholder-text="response" multiline="true" readonly="true" name="TF_ChatOutput" class="expandable" />
|
<ui:TextField placeholder-text="response" multiline="true" readonly="true" name="TF_ChatOutput" class="expandable" />
|
||||||
|
|
|
||||||
|
|
@ -440,6 +440,18 @@ PrefabInstance:
|
||||||
serializedVersion: 3
|
serializedVersion: 3
|
||||||
m_TransformParent: {fileID: 0}
|
m_TransformParent: {fileID: 0}
|
||||||
m_Modifications:
|
m_Modifications:
|
||||||
|
- target: {fileID: 796320736253214861, guid: 004ea84725223334994cf72d2115c180, type: 3}
|
||||||
|
propertyPath: OutputAudioMixerGroup
|
||||||
|
value:
|
||||||
|
objectReference: {fileID: -7600268988427216071, guid: b4346293b16e5254aadce52e891ff5e9, type: 2}
|
||||||
|
- target: {fileID: 1938294408165125026, guid: 004ea84725223334994cf72d2115c180, type: 3}
|
||||||
|
propertyPath: _fileName
|
||||||
|
value: RecordedAudio.wav
|
||||||
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 1938294408165125026, guid: 004ea84725223334994cf72d2115c180, type: 3}
|
||||||
|
propertyPath: _selectedMicrophoneIndex
|
||||||
|
value: 0
|
||||||
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 3050228793350555746, guid: 004ea84725223334994cf72d2115c180, type: 3}
|
- target: {fileID: 3050228793350555746, guid: 004ea84725223334994cf72d2115c180, type: 3}
|
||||||
propertyPath: m_Name
|
propertyPath: m_Name
|
||||||
value: -- GAME MANAGER --
|
value: -- GAME MANAGER --
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,7 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -17,43 +21,239 @@ namespace PPGIA.X540.Project3
|
||||||
[Header("Audio Capture Settings")]
|
[Header("Audio Capture Settings")]
|
||||||
[SerializeField]
|
[SerializeField]
|
||||||
private SampleRate _sampleRateInHz = SampleRate.Hz44100;
|
private SampleRate _sampleRateInHz = SampleRate.Hz44100;
|
||||||
|
|
||||||
[SerializeField]
|
[SerializeField]
|
||||||
private bool _playingBack = false;
|
private int _maxRecordingSeconds = 300; // safety cap
|
||||||
|
[SerializeField]
|
||||||
|
private string _fileName = "RecordedAudio.wav";
|
||||||
|
[SerializeField, Range(0f, 1f)]
|
||||||
|
private float _playbackVolume = 1f; // volume while monitoring
|
||||||
|
[SerializeField]
|
||||||
|
private bool _enableMonitoring = true; // if true, playback your mic
|
||||||
|
[SerializeField, Tooltip("Latency in milliseconds to offset monitoring playback from the microphone write head")]
|
||||||
|
private int _monitorLatencyMs = 80;
|
||||||
|
[SerializeField]
|
||||||
|
private string[] _microphones;
|
||||||
|
[SerializeField]
|
||||||
|
private int _selectedMicrophoneIndex = 0;
|
||||||
|
|
||||||
|
public string SelectedDevice =>
|
||||||
|
_microphones.Length > 0 &&
|
||||||
|
_selectedMicrophoneIndex < _microphones.Length &&
|
||||||
|
_selectedMicrophoneIndex >= 0
|
||||||
|
? _microphones[_selectedMicrophoneIndex]
|
||||||
|
: null;
|
||||||
|
public bool IsRecording => _isRecording;
|
||||||
|
public string LastSavedFilePath { get; private set; }
|
||||||
|
|
||||||
|
public event Action<string> OnRecordingSaved; // path
|
||||||
|
public event Action OnRecordingStarted;
|
||||||
|
public event Action OnRecordingStopped;
|
||||||
|
|
||||||
private AudioSource _audioSource;
|
private AudioSource _audioSource;
|
||||||
private AudioClip _audioClip;
|
private bool _isRecording;
|
||||||
|
private List<short> _capturedSamples =
|
||||||
|
new List<short>(1024 * 32); // filled only on Stop
|
||||||
|
private int _channels = 1; // microphone channel count (Unity usually mono)
|
||||||
|
private AudioClip _recordingClip;
|
||||||
|
private string _currentDevice;
|
||||||
|
|
||||||
void Awake()
|
private void Awake()
|
||||||
{
|
{
|
||||||
_audioSource = GetComponent<AudioSource>();
|
_audioSource = GetComponent<AudioSource>();
|
||||||
|
_microphones = Microphone.devices;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnDestroy()
|
private void OnDestroy()
|
||||||
{
|
{
|
||||||
Stop();
|
StopRecording();
|
||||||
}
|
}
|
||||||
|
|
||||||
[ContextMenu("Record Audio")]
|
[ContextMenu("Start recording audio")]
|
||||||
public void Record()
|
public void StartRecording()
|
||||||
{
|
{
|
||||||
_audioClip = Microphone.Start(null, true, 1, (int)_sampleRateInHz);
|
if (_isRecording)
|
||||||
_audioSource.clip = _audioClip;
|
|
||||||
_audioSource.loop = true;
|
|
||||||
|
|
||||||
// Wait until the microphone starts recording
|
|
||||||
while (!(Microphone.GetPosition(null) > 0)) { }
|
|
||||||
|
|
||||||
if (_playingBack)
|
|
||||||
{
|
{
|
||||||
|
Debug.LogWarning("Already recording.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (_microphones == null || _microphones.Length == 0)
|
||||||
|
{
|
||||||
|
Debug.LogError("No microphone devices found.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_currentDevice = SelectedDevice;
|
||||||
|
int frequency = GetSupportedFrequency(_currentDevice, (int)_sampleRateInHz);
|
||||||
|
|
||||||
|
// start as looped for safe monitoring without underruns
|
||||||
|
_recordingClip = Microphone.Start(
|
||||||
|
_currentDevice, true, _maxRecordingSeconds, frequency);
|
||||||
|
if (_recordingClip == null)
|
||||||
|
{
|
||||||
|
Debug.LogError("Failed to start microphone recording.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_channels = Mathf.Max(1, _recordingClip.channels);
|
||||||
|
|
||||||
|
_capturedSamples.Clear();
|
||||||
|
_isRecording = true;
|
||||||
|
|
||||||
|
StartCoroutine(WaitAndStartMonitoring());
|
||||||
|
OnRecordingStarted?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerator WaitAndStartMonitoring()
|
||||||
|
{
|
||||||
|
// Wait until microphone has started providing data
|
||||||
|
while (Microphone.GetPosition(_currentDevice) <= 0)
|
||||||
|
{
|
||||||
|
yield return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recording might have been stopped early
|
||||||
|
if (!_isRecording) yield break;
|
||||||
|
|
||||||
|
if (_enableMonitoring)
|
||||||
|
{
|
||||||
|
_audioSource.loop = true;
|
||||||
|
_audioSource.clip = _recordingClip;
|
||||||
|
_audioSource.volume = _playbackVolume;
|
||||||
|
// Start playback slightly behind the microphone write position to avoid underruns/noise
|
||||||
|
int pos = Microphone.GetPosition(_currentDevice);
|
||||||
|
int latency = Mathf.Clamp((int)(_recordingClip.frequency * (_monitorLatencyMs / 1000f)), 0, _recordingClip.samples - 1);
|
||||||
|
int start = pos - latency;
|
||||||
|
if (start < 0) start += _recordingClip.samples; // wrap around for looped clip
|
||||||
|
_audioSource.timeSamples = start % _recordingClip.samples;
|
||||||
_audioSource.Play();
|
_audioSource.Play();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[ContextMenu("Stop Audio")]
|
[ContextMenu("Stop recording audio")]
|
||||||
public void Stop()
|
public void StopRecording()
|
||||||
{
|
{
|
||||||
Microphone.End(null);
|
if (!_isRecording)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("Not currently recording.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_audioSource.isPlaying) _audioSource.Stop();
|
||||||
|
var position = Microphone.GetPosition(_currentDevice);
|
||||||
|
Microphone.End(_currentDevice);
|
||||||
|
_isRecording = false;
|
||||||
|
|
||||||
|
// Capture only the recorded portion (GetPosition tells how many samples per channel were written)
|
||||||
|
if (position <= 0)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("Microphone position is zero; no data captured.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
int samplesToCopy = position * _recordingClip.channels;
|
||||||
|
float[] floatData = new float[samplesToCopy];
|
||||||
|
_recordingClip.GetData(floatData, 0);
|
||||||
|
for (int i = 0; i < floatData.Length; i++)
|
||||||
|
{
|
||||||
|
float clamped = Mathf.Clamp(floatData[i], -1f, 1f);
|
||||||
|
short sample = (short)(clamped * short.MaxValue);
|
||||||
|
_capturedSamples.Add(sample);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.LogError($"Failed to read microphone data: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OnRecordingStopped?.Invoke();
|
||||||
|
SaveWavFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SaveWavFile()
|
||||||
|
{
|
||||||
|
if (_capturedSamples.Count == 0)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("No audio samples captured; skipping file save.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string filePath = Path.Combine(Application.persistentDataPath, _fileName);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var fs = new FileStream(filePath, FileMode.Create, FileAccess.Write))
|
||||||
|
using (var writer = new BinaryWriter(fs))
|
||||||
|
{
|
||||||
|
int sampleRate = _recordingClip != null ? _recordingClip.frequency : (int)_sampleRateInHz;
|
||||||
|
int bitsPerSample = 16;
|
||||||
|
int channels = _recordingClip != null ? _recordingClip.channels : _channels;
|
||||||
|
int byteRate = sampleRate * channels * bitsPerSample / 8;
|
||||||
|
byte[] dataBytes = new byte[_capturedSamples.Count * 2];
|
||||||
|
Buffer.BlockCopy(_capturedSamples.ToArray(), 0, dataBytes, 0, dataBytes.Length);
|
||||||
|
int subchunk2Size = dataBytes.Length;
|
||||||
|
int chunkSize = 36 + subchunk2Size;
|
||||||
|
|
||||||
|
// RIFF header
|
||||||
|
writer.Write(System.Text.Encoding.ASCII.GetBytes("RIFF"));
|
||||||
|
writer.Write(chunkSize);
|
||||||
|
writer.Write(System.Text.Encoding.ASCII.GetBytes("WAVE"));
|
||||||
|
|
||||||
|
// fmt subchunk
|
||||||
|
writer.Write(System.Text.Encoding.ASCII.GetBytes("fmt "));
|
||||||
|
writer.Write(16); // PCM header length
|
||||||
|
writer.Write((short)1); // Audio format = PCM
|
||||||
|
writer.Write((short)channels);
|
||||||
|
writer.Write(sampleRate);
|
||||||
|
writer.Write(byteRate);
|
||||||
|
writer.Write((short)(channels * bitsPerSample / 8)); // block align
|
||||||
|
writer.Write((short)bitsPerSample);
|
||||||
|
|
||||||
|
// data subchunk
|
||||||
|
writer.Write(System.Text.Encoding.ASCII.GetBytes("data"));
|
||||||
|
writer.Write(subchunk2Size);
|
||||||
|
writer.Write(dataBytes);
|
||||||
|
}
|
||||||
|
LastSavedFilePath = filePath;
|
||||||
|
Debug.Log($"Audio saved to: {filePath}");
|
||||||
|
OnRecordingSaved?.Invoke(filePath);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.LogError($"Failed to save WAV file: {ex.Message}");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_capturedSamples.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ContextMenu("Refresh microphone list")]
|
||||||
|
public void RefreshMicrophones()
|
||||||
|
{
|
||||||
|
_microphones = Microphone.devices;
|
||||||
|
if (_microphones == null || _microphones.Length == 0)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("No microphones detected after refresh.");
|
||||||
|
_selectedMicrophoneIndex = -1;
|
||||||
|
}
|
||||||
|
else if (
|
||||||
|
_selectedMicrophoneIndex < 0 ||
|
||||||
|
_selectedMicrophoneIndex >= _microphones.Length)
|
||||||
|
{
|
||||||
|
_selectedMicrophoneIndex = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int GetSupportedFrequency(string device, int requested)
|
||||||
|
{
|
||||||
|
int min, max;
|
||||||
|
Microphone.GetDeviceCaps(device, out min, out max);
|
||||||
|
// When both are 0, any frequency is supported
|
||||||
|
if (min == 0 && max == 0) return requested;
|
||||||
|
if (requested < min) return min;
|
||||||
|
if (requested > max) return max;
|
||||||
|
return requested;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue