Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
22 changes: 22 additions & 0 deletions appcast.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,28 @@
<rss xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle" version="2.0">
<channel>
<title>QuickRecorder</title>
<item>
<title>v1.5.5</title>
<pubDate>Sun, 01 Dec 2024 14:24:36 +0800</pubDate>
<sparkle:version>155</sparkle:version>
<sparkle:shortVersionString>1.5.5</sparkle:shortVersionString>
<sparkle:minimumSystemVersion>12.3</sparkle:minimumSystemVersion>
<description><![CDATA[
<style>ul{margin-top: 0;margin-bottom: 7;padding-left: 18;}</style><ul>
<li>修复了无法从多通道音频输入设备录制声音的问题.</li>
<li>修复了录制移动设备画面时迷你控制器显示不正常的问题.</li>
</ul><ul>
<li>Fixed crash when recording from multi-channel audio input devices.</li>
<li>Fixed the appearance of the mini recording controller.</li>
</ul><img src="https://github.com/lihaoyun6/QuickRecorder/raw/main/img/donate.png" width="280"/>
]]></description>
<enclosure url="https://github.com/lihaoyun6/QuickRecorder/releases/download/1.5.5/QuickRecorder_v1.5.5.dmg" length="4257710" type="application/octet-stream" sparkle:edSignature="CYAYEhCpy02+yLGrktekCXofCynJyknKsyT/BeLfT/cDzQFTB9LwGIy+3EtjvZGzAtUCDUiOTVZ1/5X8rI/rAA=="/>
<sparkle:deltas>
<enclosure url="https://github.com/lihaoyun6/QuickRecorder/releases/download/1.5.5/Update155-154.delta" sparkle:deltaFrom="154" length="311322" type="application/octet-stream" sparkle:deltaFromSparkleExecutableSize="864896" sparkle:deltaFromSparkleLocales="de,he,ar,el,ja,fa,en" sparkle:edSignature="5U7ADes6MmP43v+9CS0bNRM/qrU2V47xpjgdlrWj9ukjCrm/RoYt89IcvfrQxlVme+nc8uouLokDa2/UYopbAg=="/>
<enclosure url="https://github.com/lihaoyun6/QuickRecorder/releases/download/1.5.5/Update155-153.delta" sparkle:deltaFrom="153" length="662954" type="application/octet-stream" sparkle:deltaFromSparkleExecutableSize="864896" sparkle:deltaFromSparkleLocales="de,he,ar,el,ja,fa,en" sparkle:edSignature="MfC1eHQSSThZFqvV8Vu4iHu6pYsVpxRyalJhwfBze/86Vuq4FjEo0Rmw1qb/7r468FNsmGpNDXc5/71aYFETDg=="/>
<enclosure url="https://github.com/lihaoyun6/QuickRecorder/releases/download/1.5.5/Update155-152.delta" sparkle:deltaFrom="152" length="673122" type="application/octet-stream" sparkle:deltaFromSparkleExecutableSize="864896" sparkle:deltaFromSparkleLocales="de,he,ar,el,ja,fa,en" sparkle:edSignature="qEnaLOoVRbo5cXiobPApi7XEF4ekXaeLd7Bm8mKa0nlVOHP9o+3DMDKwPLVSGYYk1wWJVwEG7xdULrxDh64PAg=="/>
</sparkle:deltas>
</item>
<item>
<title>v1.5.4</title>
<pubDate>Mon, 25 Nov 2024 16:06:59 +0800</pubDate>
Expand Down