62 lines
2.5 KiB
Swift
62 lines
2.5 KiB
Swift
import AVFoundation
|
|
import Foundation
|
|
|
|
@main
|
|
struct RecordRoom {
|
|
static func main() throws {
|
|
guard CommandLine.arguments.count == 2 else {
|
|
throw NSError(domain: "RoomMic", code: 1)
|
|
}
|
|
let engine = AVAudioEngine()
|
|
let input = engine.inputNode
|
|
let format = input.outputFormat(forBus: 0)
|
|
guard format.sampleRate > 0, format.channelCount > 0 else {
|
|
throw NSError(domain: "RoomMic", code: 2, userInfo: [NSLocalizedDescriptionKey: "Нет входа микрофона."])
|
|
}
|
|
let destination = URL(fileURLWithPath: CommandLine.arguments[1])
|
|
let output = try AVAudioFile(forWriting: destination, settings: [
|
|
AVFormatIDKey: kAudioFormatLinearPCM,
|
|
AVSampleRateKey: format.sampleRate,
|
|
AVNumberOfChannelsKey: format.channelCount,
|
|
AVLinearPCMBitDepthKey: 16,
|
|
AVLinearPCMIsFloatKey: false,
|
|
AVLinearPCMIsBigEndianKey: false,
|
|
AVLinearPCMIsNonInterleaved: false,
|
|
], commonFormat: format.commonFormat, interleaved: format.isInterleaved)
|
|
let limit = Int(format.sampleRate * 13)
|
|
let complete = DispatchSemaphore(value: 0)
|
|
let state = Recording(limit)
|
|
input.installTap(onBus: 0, bufferSize: 1024, format: format) { buffer, _ in
|
|
guard state.remaining > 0 else { return }
|
|
buffer.frameLength = min(buffer.frameLength, AVAudioFrameCount(state.remaining))
|
|
do {
|
|
try output.write(from: buffer)
|
|
state.remaining -= Int(buffer.frameLength)
|
|
} catch {
|
|
state.failure = error
|
|
state.remaining = 0
|
|
}
|
|
if state.remaining == 0 { complete.signal() }
|
|
}
|
|
defer {
|
|
engine.stop()
|
|
input.removeTap(onBus: 0)
|
|
}
|
|
engine.prepare()
|
|
try engine.start()
|
|
FileHandle.standardOutput.write(Data("ready\n".utf8))
|
|
guard complete.wait(timeout: .now() + 17) == .success else {
|
|
throw NSError(domain: "RoomMic", code: 3, userInfo: [NSLocalizedDescriptionKey: "Микрофон не отдал запись вовремя."])
|
|
}
|
|
if let failure = state.failure { throw failure }
|
|
}
|
|
}
|
|
|
|
private final class Recording: @unchecked Sendable {
|
|
// The tap owns this state. The main thread reads the result after the semaphore.
|
|
var remaining: Int
|
|
var failure: Error?
|
|
|
|
init(_ remaining: Int) { self.remaining = remaining }
|
|
}
|