fix: 오디오 테스트 버튼 구현 — 스텁 제거, 실제 SoX 캡처

2초간 마이크 캡처 후 평균/피크 RMS 레벨 반환.
설정 오디오 탭에서 테스트 버튼 정상 작동.
This commit is contained in:
Yun Chan 2026-04-08 09:04:03 +09:00
parent 2a465f22e9
commit fa73ad3bb2
2 changed files with 65 additions and 2 deletions

View file

@ -31,7 +31,15 @@ export function registerAudioHandlers(): void {
}) })
ipcMain.handle(IPC_CHANNELS.AUDIO.TEST_DEVICE, async () => { ipcMain.handle(IPC_CHANNELS.AUDIO.TEST_DEVICE, async () => {
// Phase 1: 스텁 try {
return ipcSuccess({ averageLevel: 0, peakLevel: 0, hasAudio: false }) const service = getAudioCaptureService()
const result = await service.testCapture(2000)
return ipcSuccess(result)
} catch (err) {
return ipcError(
ErrorCode.AudioDeviceNotFound,
`Audio test failed: ${err instanceof Error ? err.message : String(err)}`,
)
}
}) })
} }

View file

@ -278,6 +278,61 @@ class AudioCaptureService extends EventEmitter {
logger.info('AudioCaptureService disposed') logger.info('AudioCaptureService disposed')
} }
/**
* (ms) / .
*/
async testCapture(durationMs: number): Promise<{ averageLevel: number; peakLevel: number; hasAudio: boolean }> {
const soxExe = getSoxPath()
const selectedDeviceId = configGet('selectedDeviceId')
const deviceArg = selectedDeviceId && selectedDeviceId !== 'default'
? selectedDeviceId
: 'default'
const soxArgs = process.platform === 'win32'
? ['-t', 'waveaudio', deviceArg, '--no-show-progress',
'-r', String(AUDIO_FORMAT.SAMPLE_RATE), '-c', String(AUDIO_FORMAT.CHANNELS),
'-b', '16', '-e', 'signed-integer', '-t', 'raw', '-']
: ['-d', '--no-show-progress',
'-r', String(AUDIO_FORMAT.SAMPLE_RATE), '-c', String(AUDIO_FORMAT.CHANNELS),
'-b', '16', '-e', 'signed-integer', '-t', 'raw', '-']
return new Promise((resolve, reject) => {
const proc = spawn(soxExe, soxArgs, { stdio: ['ignore', 'pipe', 'pipe'] })
const buffers: Buffer[] = []
let peakRms = 0
proc.stdout?.on('data', (chunk: Buffer) => {
buffers.push(chunk)
const rms = calculateRMS(chunk)
if (rms > peakRms) peakRms = rms
})
proc.on('error', (err) => {
reject(new D3ROError(ErrorCode.AudioCaptureStartFailed, `SoX 실행 실패: ${err.message}`))
})
setTimeout(() => {
proc.kill()
}, durationMs)
proc.on('close', () => {
if (buffers.length === 0) {
resolve({ averageLevel: 0, peakLevel: 0, hasAudio: false })
return
}
const combined = Buffer.concat(buffers)
const avgRms = calculateRMS(combined)
const avgLevel = avgRms > 0 ? Math.min(1.0, Math.pow(avgRms, 0.28)) : 0
const peakLevel = peakRms > 0 ? Math.min(1.0, Math.pow(peakRms, 0.28)) : 0
resolve({
averageLevel: avgLevel,
peakLevel: peakLevel,
hasAudio: avgRms > 0.001,
})
})
})
}
/** /**
* 60ms (1920 bytes) emit한다. * 60ms (1920 bytes) emit한다.
* . * .