Skip to content

Commit

Permalink
fix multi-channel audio input; fix mini controller
Browse files Browse the repository at this point in the history
  • Loading branch information
lihaoyun6 committed Dec 1, 2024
1 parent a9e0af9 commit aa7ad99
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 53 deletions.
8 changes: 4 additions & 4 deletions QuickRecorder.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,7 @@
CODE_SIGN_ENTITLEMENTS = QuickRecorder/QuickRecorder.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 154;
CURRENT_PROJECT_VERSION = 155;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_ASSET_PATHS = "\"QuickRecorder/Preview Content\"";
DEVELOPMENT_TEAM = L4T783637F;
Expand All @@ -470,7 +470,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.3;
MARKETING_VERSION = 1.5.4;
MARKETING_VERSION = 1.5.5;
PRODUCT_BUNDLE_IDENTIFIER = com.lihaoyun6.QuickRecorder;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
Expand All @@ -486,7 +486,7 @@
CODE_SIGN_ENTITLEMENTS = QuickRecorder/QuickRecorder.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 154;
CURRENT_PROJECT_VERSION = 155;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_ASSET_PATHS = "\"QuickRecorder/Preview Content\"";
DEVELOPMENT_TEAM = L4T783637F;
Expand All @@ -504,7 +504,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.3;
MARKETING_VERSION = 1.5.4;
MARKETING_VERSION = 1.5.5;
PRODUCT_BUNDLE_IDENTIFIER = com.lihaoyun6.QuickRecorder;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
Expand Down
42 changes: 16 additions & 26 deletions QuickRecorder/RecordEngine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -120,14 +120,15 @@ extension AppDelegate {
}
}
if SCContext.streamType == .systemaudio { prepareAudioRecording() }
Task { await record(audioOnly: SCContext.streamType == .systemaudio, filter: SCContext.filter!, fastStart: fastStart) }
Task { await record(filter: SCContext.filter!, fastStart: fastStart) }
}

func record(audioOnly: Bool, filter: SCContentFilter, fastStart: Bool = true) async {
func record(filter: SCContentFilter, fastStart: Bool = true) async {
SCContext.timeOffset = CMTimeMake(value: 0, timescale: 0)
SCContext.isPaused = false
SCContext.isResume = false

let audioOnly = SCContext.streamType == .systemaudio
let recordHDR = ud.bool(forKey: "recordHDR")
let encoderIsH265 = (ud.string(forKey: "encoder") == Encoder.h265.rawValue) || recordHDR

Expand Down Expand Up @@ -254,15 +255,12 @@ extension AppDelegate {
try? FileManager.default.createDirectory(at: URL(fileURLWithPath: SCContext.filePath), withIntermediateDirectories: true, attributes: nil)
try? jsonString.write(to: infoJsonURL, atomically: true, encoding: .utf8)
SCContext.audioFile = try! AVAudioFile(forWriting: URL(fileURLWithPath: SCContext.filePath1), settings: SCContext.updateAudioSettings(), commonFormat: .pcmFormatFloat32, interleaved: false)
if let device = SCContext.getCurrentMic() {
var settings = SCContext.updateAudioSettings(rate: SCContext.getSampleRate(for: device) ?? 48000)
settings[AVNumberOfChannelsKey] = 1
SCContext.audioFile2 = try! AVAudioFile(forWriting: URL(fileURLWithPath: SCContext.filePath2), settings: settings, commonFormat: .pcmFormatFloat32, interleaved: false)
} else {
var settings = SCContext.updateAudioSettings(rate: SCContext.getDefaultSampleRate() ?? 48000)
settings[AVNumberOfChannelsKey] = 1
SCContext.audioFile2 = try! AVAudioFile(forWriting: URL(fileURLWithPath: SCContext.filePath2), settings: settings, commonFormat: .pcmFormatFloat32, interleaved: false)
}

let channels = SCContext.getChannelCount() ?? 1
let sampleRate = SCContext.getSampleRate() ?? 48000
var settings = SCContext.updateAudioSettings(rate: sampleRate)
settings[AVNumberOfChannelsKey] = channels
SCContext.audioFile2 = try! AVAudioFile(forWriting: URL(fileURLWithPath: SCContext.filePath2), settings: settings, commonFormat: .pcmFormatFloat32, interleaved: false)
} else {
SCContext.filePath = "\(path).\(fileEnding)"
SCContext.filePath1 = SCContext.filePath
Expand Down Expand Up @@ -352,11 +350,12 @@ extension AppDelegate {
}

if ud.bool(forKey: "recordMic") {
if let device = SCContext.getCurrentMic() {
SCContext.micInput = AVAssetWriterInput(mediaType: AVMediaType.audio, outputSettings: SCContext.updateAudioSettings(rate: SCContext.getSampleRate(for: device) ?? 48000))
} else {
SCContext.micInput = AVAssetWriterInput(mediaType: AVMediaType.audio, outputSettings: SCContext.updateAudioSettings(rate: SCContext.getDefaultSampleRate() ?? 48000))
}
let channels = SCContext.getChannelCount() ?? 1
let sampleRate = SCContext.getSampleRate() ?? 48000
var settings = SCContext.updateAudioSettings(rate: sampleRate)
settings[AVNumberOfChannelsKey] = channels

SCContext.micInput = AVAssetWriterInput(mediaType: AVMediaType.audio, outputSettings: settings)
SCContext.micInput.expectsMediaDataInRealTime = true
if SCContext.vW.canAdd(SCContext.micInput) { SCContext.vW.add(SCContext.micInput) }
startMicRecording()
Expand Down Expand Up @@ -518,20 +517,11 @@ class AudioRecorder: NSObject, AVCaptureAudioDataOutputSampleBufferDelegate {
captureSession = AVCaptureSession()

// Get the default audio device (microphone)
guard let audioDevice = SCContext.getMicrophone().first(where: { $0.localizedName == ud.string(forKey: "micDevice") }) else {
guard let audioDevice = SCContext.getCurrentMic() else {
print("Unable to access microphone")
return
}

let channels = audioDevice.formats.first?.formatDescription.audioChannelLayout?.numberOfChannels ?? 0
if channels < 1 { return }
if channels != 1 {
let sampleRate = SCContext.getSampleRate(for: audioDevice) ?? 48000
var settings = SCContext.updateAudioSettings(rate: sampleRate)
settings[AVNumberOfChannelsKey] = channels
SCContext.audioFile2 = try! AVAudioFile(forWriting: URL(fileURLWithPath: SCContext.filePath2), settings: settings, commonFormat: .pcmFormatFloat32, interleaved: false)
}
//print(audioDevice.localizedName)
// Create audio input
do {
audioInput = try AVCaptureDeviceInput(device: audioDevice)
Expand Down
96 changes: 87 additions & 9 deletions QuickRecorder/SCContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -497,22 +497,100 @@ class SCContext {

static func getCurrentMic() -> AVCaptureDevice? {
let deviceName = ud.string(forKey: "micDevice")
if deviceName == "Default" {
return getMicrophone().first(where: { $0.localizedName == deviceName })
}

static func getChannelCount() -> Int? {
if let device = getCurrentMic() {
if let channels = device.formats.first?.formatDescription.audioChannelLayout?.numberOfChannels {
return channels
}

let activeFormat = device.activeFormat
let description = activeFormat.formatDescription
if let audioStreamBasicDescription = CMAudioFormatDescriptionGetStreamBasicDescription(description)?.pointee {
let channelCount = audioStreamBasicDescription.mChannelsPerFrame
return Int(channelCount)
}
}
return getDefaultChannelCount()
}

static func getSampleRate() -> Int? {
if let device = getCurrentMic() {
let activeFormat = device.activeFormat
let description = activeFormat.formatDescription

if let audioStreamBasicDescription = CMAudioFormatDescriptionGetStreamBasicDescription(description)?.pointee {
let sampleRate = audioStreamBasicDescription.mSampleRate
return Int(sampleRate)
}
}
return getMicrophone().first(where: { $0.localizedName == ud.string(forKey: "micDevice") })
return getDefaultSampleRate()
}

static func getSampleRate(for device: AVCaptureDevice) -> Int? {
let activeFormat = device.activeFormat
let description = activeFormat.formatDescription
static func getDefaultChannelCount() -> Int? {
var deviceID = AudioObjectID(0)
var propertySize = UInt32(MemoryLayout.size(ofValue: deviceID))

if let audioStreamBasicDescription = CMAudioFormatDescriptionGetStreamBasicDescription(description)?.pointee {
let sampleRate = audioStreamBasicDescription.mSampleRate
return Int(sampleRate)
} else {
// 获取默认音频输入设备
var address = AudioObjectPropertyAddress(
mSelector: kAudioHardwarePropertyDefaultInputDevice,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMain
)

let status = AudioObjectGetPropertyData(
AudioObjectID(kAudioObjectSystemObject),
&address,
0,
nil,
&propertySize,
&deviceID
)

guard status == noErr else {
print("Failed to get default audio input device")
return nil
}

// 获取通道数
address = AudioObjectPropertyAddress(
mSelector: kAudioDevicePropertyStreamConfiguration,
mScope: kAudioDevicePropertyScopeInput,
mElement: kAudioObjectPropertyElementMain
)

// 查询流配置信息
var streamConfig: UnsafeMutableAudioBufferListPointer?
propertySize = 0

// 先获取属性大小
let sizeStatus = AudioObjectGetPropertyDataSize(deviceID, &address, 0, nil, &propertySize)
guard sizeStatus == noErr else {
print("Failed to get size for stream configuration")
return nil
}

// 分配内存以存储音频流配置
let bufferList = UnsafeMutablePointer<AudioBufferList>.allocate(capacity: Int(propertySize))
defer { bufferList.deallocate() }

let configStatus = AudioObjectGetPropertyData(deviceID, &address, 0, nil, &propertySize, bufferList)
guard configStatus == noErr else {
print("Failed to get stream configuration")
return nil
}

streamConfig = UnsafeMutableAudioBufferListPointer(bufferList)

// 计算通道总数
var totalChannels = 0
for buffer in streamConfig! {
totalChannels += Int(buffer.mNumberChannels)
}

return totalChannels
}

static func getDefaultSampleRate() -> Int? {
Expand Down
27 changes: 13 additions & 14 deletions QuickRecorder/ViewModel/StatusBar.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,19 @@ struct StatusBarItem: View {
.foregroundStyle(.white)
.frame(width: 16, alignment: .center)
}).buttonStyle(.plain)
} else {
Button(action:{
DispatchQueue.main.async {
if deviceWindow.isVisible { deviceWindow.close() } else { deviceWindow.orderFront(nil) }
deviceWindowIsShowing = deviceWindow.isVisible
}
}, label: {
Image(systemName: "eye.circle.fill")
.font(.system(size: 16))
.foregroundStyle(.white)
.frame(width: 16, alignment: .center)
.opacity(deviceWindowIsShowing ? 1 : 0.7)
}).buttonStyle(.plain)
}
} else {
Text(recordingLength)
Expand All @@ -84,20 +97,6 @@ struct StatusBarItem: View {
.popover(isPresented: $popoverState.isShowing, arrowEdge: .bottom) {
CameraPopoverView(closePopover: { popoverState.isShowing = false })
}
} else {
Button(action:{
DispatchQueue.main.async {
if deviceWindow.isVisible { deviceWindow.close() } else { deviceWindow.orderFront(nil) }
deviceWindowIsShowing = deviceWindow.isVisible
}
}, label: {
Image(systemName: "eye.circle.fill")
.font(.system(size: 16))
.foregroundStyle(.white)
.frame(width: 16, alignment: .center)
.opacity(deviceWindowIsShowing ? 1 : 0.7)
})
.buttonStyle(.plain)
}
}
} else {
Expand Down

0 comments on commit aa7ad99

Please sign in to comment.