Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit a639956

Browse files
committed
feat: 음성 녹음및 sst 서버 전송 기능능
1 parent faabbd5 commit a639956

File tree

11 files changed

+596
-151
lines changed

11 files changed

+596
-151
lines changed

‎Assets/Core/Audio/AudioRecorder.cs‎

Lines changed: 174 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,43 +2,64 @@
22
using System;
33
using UnityEngine;
44
using System.Collections.Generic;
5+
using ProjectVG.Infrastructure.Audio;
56

67
namespace ProjectVG.Core.Audio
78
{
9+
/// <summary>
10+
/// 정확한 시간 기반 음성 녹음 시스템
11+
/// 녹음 시작/중지 시간을 기반으로 정확한 길이의 오디오를 생성합니다.
12+
/// </summary>
813
public class AudioRecorder : Singleton<AudioRecorder>
914
{
1015
[Header("Recording Settings")]
1116
[SerializeField] private int _sampleRate = 44100;
1217
[SerializeField] private int _channels = 1;
13-
[SerializeField] private int _maxRecordingLength = 30;
18+
[SerializeField] private int _maxRecordingLength = 30; // 최대 녹음 시간 (초)
19+
20+
[Header("Audio Processing")]
21+
[SerializeField] private bool _enableNoiseReduction = false; // 노이즈 제거 비활성화
22+
[SerializeField] private float _silenceThreshold = 0.001f; // 무음 임계값 낮춤
1423

1524
private AudioClip? _recordingClip;
1625
private bool _isRecording = false;
1726
private float _recordingStartTime;
18-
private List<float> _audioBuffer;
19-
20-
public bool IsRecording => _isRecording;
21-
public float RecordingDuration => _isRecording ? Time.time - _recordingStartTime : 0f;
22-
public bool IsRecordingAvailable => Microphone.devices.Length > 0;
27+
private float _recordingEndTime;
28+
private string _currentDevice = null;
2329

30+
// 이벤트
2431
public event Action? OnRecordingStarted;
2532
public event Action? OnRecordingStopped;
2633
public event Action<AudioClip>? OnRecordingCompleted;
2734
public event Action<string>? OnError;
35+
public event Action<float>? OnRecordingProgress; // 녹음 진행률 (0-1)
36+
37+
// 프로퍼티
38+
public bool IsRecording => _isRecording;
39+
public float RecordingDuration => _isRecording ? Time.time - _recordingStartTime : 0f;
40+
public bool IsRecordingAvailable => Microphone.devices.Length > 0;
41+
public float RecordingProgress => _isRecording ? Mathf.Clamp01(RecordingDuration / _maxRecordingLength) : 0f;
2842

2943
#region Unity Lifecycle
3044

3145
protected override void Awake()
3246
{
3347
base.Awake();
34-
_audioBuffer=newList<float>();
48+
InitializeMicrophone();
3549
}
3650

3751
private void Update()
3852
{
39-
if (_isRecording&&RecordingDuration>=_maxRecordingLength)
53+
if (_isRecording)
4054
{
41-
StopRecording();
55+
// 녹음 진행률 이벤트 발생
56+
OnRecordingProgress?.Invoke(RecordingProgress);
57+
58+
// 최대 녹음 시간 체크
59+
if (RecordingDuration >= _maxRecordingLength)
60+
{
61+
StopRecording();
62+
}
4263
}
4364
}
4465

@@ -54,6 +75,10 @@ private void OnDestroy()
5475

5576
#region Public Methods
5677

78+
/// <summary>
79+
/// 음성 녹음 시작
80+
/// </summary>
81+
/// <returns>녹음 시작 성공 여부</returns>
5782
public bool StartRecording()
5883
{
5984
if (_isRecording)
@@ -73,10 +98,11 @@ public bool StartRecording()
7398
{
7499
_isRecording = true;
75100
_recordingStartTime = Time.time;
76-
_audioBuffer.Clear();
77101

78-
_recordingClip = Microphone.Start(null, false, _maxRecordingLength, _sampleRate);
102+
// 최대 녹음 시간만큼 버퍼 할당
103+
_recordingClip = Microphone.Start(_currentDevice, false, _maxRecordingLength, _sampleRate);
79104

105+
Debug.Log($"[AudioRecorder] 녹음 시작 - 최대 시간: {_maxRecordingLength}초, 샘플레이트: {_sampleRate}Hz");
80106
OnRecordingStarted?.Invoke();
81107

82108
return true;
@@ -90,6 +116,10 @@ public bool StartRecording()
90116
}
91117
}
92118

119+
/// <summary>
120+
/// 음성 녹음 중지
121+
/// </summary>
122+
/// <returns>처리된 AudioClip</returns>
93123
public AudioClip? StopRecording()
94124
{
95125
if (!_isRecording)
@@ -101,17 +131,22 @@ public bool StartRecording()
101131
try
102132
{
103133
_isRecording = false;
134+
_recordingEndTime = Time.time;
135+
float actualRecordingDuration = _recordingEndTime - _recordingStartTime;
104136

105-
Microphone.End(null);
137+
Microphone.End(_currentDevice);
106138

107139
if (_recordingClip != null)
108140
{
109-
ProcessRecordingClip();
110-
OnRecordingCompleted?.Invoke(_recordingClip);
141+
AudioClip processedClip = ProcessRecordingClip(actualRecordingDuration);
142+
if (processedClip != null)
143+
{
144+
Debug.Log($"[AudioRecorder] 녹음 완료 - 실제 녹음 시간: {actualRecordingDuration:F2}초, 샘플: {processedClip.samples}");
145+
OnRecordingCompleted?.Invoke(processedClip);
146+
}
111147
}
112148

113149
OnRecordingStopped?.Invoke();
114-
115150
return _recordingClip;
116151
}
117152
catch (Exception ex)
@@ -123,72 +158,175 @@ public bool StartRecording()
123158
}
124159
}
125160

126-
public byte[] AudioClipToBytes(AudioClip audioClip)
161+
/// <summary>
162+
/// AudioClip을 WAV 바이트 배열로 변환
163+
/// </summary>
164+
public byte[] AudioClipToWavBytes(AudioClip audioClip)
127165
{
128166
if (audioClip == null)
129-
return new byte[0];
130-
167+
return Array.Empty<byte>();
131168
try
132169
{
133-
float[] samples = new float[audioClip.samples * audioClip.channels];
134-
audioClip.GetData(samples, 0);
135-
136-
byte[] audioBytes = new byte[samples.Length * 2];
137-
for (int i = 0; i < samples.Length; i++)
170+
return WavEncoder.FromAudioClip(audioClip);
171+
}
172+
catch (Exception ex)
173+
{
174+
Debug.LogError($"[AudioRecorder] WAV 변환 실패: {ex.Message}");
175+
return Array.Empty<byte>();
176+
}
177+
}
178+
179+
/// <summary>
180+
/// 녹음 파일 저장 (디버깅용)
181+
/// </summary>
182+
public bool SaveRecordingToFile(AudioClip audioClip, string fileName = "recording")
183+
{
184+
if (audioClip == null)
185+
{
186+
Debug.LogError("[AudioRecorder] 저장할 AudioClip이 null입니다.");
187+
return false;
188+
}
189+
190+
try
191+
{
192+
byte[] wavData = AudioClipToWavBytes(audioClip);
193+
if (wavData.Length == 0)
138194
{
139-
shortsample=(short)(samples[i]*short.MaxValue);
140-
BitConverter.GetBytes(sample).CopyTo(audioBytes,i*2);
195+
Debug.LogError("[AudioRecorder] WAV 데이터 변환 실패");
196+
returnfalse;
141197
}
198+
199+
string filePath = System.IO.Path.Combine(Application.persistentDataPath, $"{fileName}.wav");
200+
System.IO.File.WriteAllBytes(filePath, wavData);
201+
202+
Debug.Log($"[AudioRecorder] 녹음 파일 저장 완료: {filePath}");
203+
Debug.Log($"[AudioRecorder] 파일 크기: {wavData.Length} bytes");
204+
Debug.Log($"[AudioRecorder] AudioClip 정보 - 샘플: {audioClip.samples}, 채널: {audioClip.channels}, 주파수: {audioClip.frequency}");
142205

143-
return audioBytes;
206+
return true;
144207
}
145208
catch (Exception ex)
146209
{
147-
Debug.LogError($"[AudioRecorder] AudioClip을 byte 배열로 변환 실패: {ex.Message}");
148-
return newbyte[0];
210+
Debug.LogError($"[AudioRecorder] 파일 저장 실패: {ex.Message}");
211+
return false;
149212
}
150213
}
151214

215+
/// <summary>
216+
/// 사용 가능한 마이크 목록 반환
217+
/// </summary>
152218
public string[] GetAvailableMicrophones()
153219
{
154220
return Microphone.devices;
155221
}
156222

223+
/// <summary>
224+
/// 기본 마이크 반환
225+
/// </summary>
157226
public string GetDefaultMicrophone()
158227
{
159228
string[] devices = Microphone.devices;
160229
return devices.Length > 0 ? devices[0] : string.Empty;
161230
}
162231

232+
/// <summary>
233+
/// 현재 마이크 설정
234+
/// </summary>
235+
public void SetMicrophone(string deviceName)
236+
{
237+
if (Array.Exists(Microphone.devices, device => device == deviceName))
238+
{
239+
_currentDevice = deviceName;
240+
Debug.Log($"[AudioRecorder] 마이크 설정 변경: {deviceName}");
241+
}
242+
else
243+
{
244+
Debug.LogWarning($"[AudioRecorder] 존재하지 않는 마이크: {deviceName}");
245+
}
246+
}
247+
163248
#endregion
164249

165250
#region Private Methods
166251

167-
private void ProcessRecordingClip()
252+
/// <summary>
253+
/// 마이크 초기화
254+
/// </summary>
255+
private void InitializeMicrophone()
256+
{
257+
string[] devices = Microphone.devices;
258+
if (devices.Length > 0)
259+
{
260+
_currentDevice = devices[0];
261+
Debug.Log($"[AudioRecorder] 기본 마이크 설정: {_currentDevice}");
262+
}
263+
else
264+
{
265+
Debug.LogError("[AudioRecorder] 사용 가능한 마이크가 없습니다.");
266+
}
267+
}
268+
269+
/// <summary>
270+
/// 녹음된 AudioClip 처리
271+
/// </summary>
272+
private AudioClip? ProcessRecordingClip(float actualDuration)
168273
{
169274
if (_recordingClip == null)
170-
return;
275+
returnnull;
171276

172-
int recordedLength = Microphone.GetPosition(null);
173-
if (recordedLength <= 0)
277+
// 실제 녹음 시간을 기반으로 샘플 수 계산
278+
int actualSamples = Mathf.RoundToInt(actualDuration * _sampleRate);
279+
280+
// 최대 샘플 수 제한 (버퍼 크기)
281+
int maxSamples = _recordingClip.samples;
282+
actualSamples = Mathf.Min(actualSamples, maxSamples);
283+
284+
Debug.Log($"[AudioRecorder] 실제 녹음 길이: {actualSamples} 샘플, 전체 버퍼: {_recordingClip.samples} 샘플, 실제 시간: {actualDuration:F2}초");
285+
286+
if (actualSamples <= 0)
174287
{
175288
Debug.LogWarning("[AudioRecorder] 녹음된 데이터가 없습니다.");
176-
return;
289+
returnnull;
177290
}
178291

292+
// 실제 녹음된 길이만큼만 새로운 AudioClip 생성
179293
AudioClip processedClip = AudioClip.Create(
180294
"RecordedAudio",
181-
recordedLength,
295+
actualSamples,
182296
_recordingClip.channels,
183297
_recordingClip.frequency,
184298
false
185299
);
186300

187-
float[] samples = new float[recordedLength * _recordingClip.channels];
301+
float[] samples = new float[actualSamples * _recordingClip.channels];
188302
_recordingClip.GetData(samples, 0);
189-
processedClip.SetData(samples, 0);
190303

304+
// 노이즈 리덕션 적용
305+
if (_enableNoiseReduction)
306+
{
307+
ApplyNoiseReduction(samples);
308+
}
309+
310+
processedClip.SetData(samples, 0);
191311
_recordingClip = processedClip;
312+
313+
Debug.Log($"[AudioRecorder] 처리된 AudioClip - 샘플: {_recordingClip.samples}, 채널: {_recordingClip.channels}, 주파수: {_recordingClip.frequency}");
314+
315+
return _recordingClip;
316+
}
317+
318+
/// <summary>
319+
/// 노이즈 리덕션 적용
320+
/// </summary>
321+
private void ApplyNoiseReduction(float[] audioData)
322+
{
323+
for (int i = 0; i < audioData.Length; i++)
324+
{
325+
if (Mathf.Abs(audioData[i]) < _silenceThreshold)
326+
{
327+
audioData[i] = 0f;
328+
}
329+
}
192330
}
193331

194332
#endregion

‎Assets/Infrastructure/Network/Editor.meta‎ renamed to ‎Assets/Core/Chat.meta‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /