From c268c8d9ca1e7e8b4b41f7fa6d29580091d2517f Mon Sep 17 00:00:00 2001 From: Zach Nagengast Date: Sat, 10 Aug 2024 15:41:29 -0700 Subject: [PATCH] Fix start time logic for file loading (#195) * Fix start time logic for file loading and resampling * Add test file --- .swiftpm/configuration/Package.resolved | 23 +++++++++ Sources/WhisperKit/Core/AudioProcessor.swift | 32 +++++++++--- Sources/WhisperKit/Core/Models.swift | 3 ++ Sources/WhisperKit/Core/Utils.swift | 17 ++++--- .../WhisperKitTests/Resources/jfk_441khz.m4a | Bin 0 -> 101547 bytes Tests/WhisperKitTests/UnitTests.swift | 46 +++++++++++++++--- 6 files changed, 99 insertions(+), 22 deletions(-) create mode 100644 .swiftpm/configuration/Package.resolved create mode 100644 Tests/WhisperKitTests/Resources/jfk_441khz.m4a diff --git a/.swiftpm/configuration/Package.resolved b/.swiftpm/configuration/Package.resolved new file mode 100644 index 00000000..6cccf250 --- /dev/null +++ b/.swiftpm/configuration/Package.resolved @@ -0,0 +1,23 @@ +{ + "pins" : [ + { + "identity" : "swift-argument-parser", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-argument-parser.git", + "state" : { + "revision" : "c8ed701b513cf5177118a175d85fbbbcd707ab41", + "version" : "1.3.0" + } + }, + { + "identity" : "swift-transformers", + "kind" : "remoteSourceControl", + "location" : "https://github.com/huggingface/swift-transformers.git", + "state" : { + "revision" : "74b94211bdc741694ed7e700a1104c72e5ba68fe", + "version" : "0.1.7" + } + } + ], + "version" : 2 +} diff --git a/Sources/WhisperKit/Core/AudioProcessor.swift b/Sources/WhisperKit/Core/AudioProcessor.swift index 4b5fcdfe..c3958cb0 100644 --- a/Sources/WhisperKit/Core/AudioProcessor.swift +++ b/Sources/WhisperKit/Core/AudioProcessor.swift @@ -25,7 +25,7 @@ public protocol AudioProcessing { /// - startTime: Optional start time in seconds to read from /// - endTime: Optional end time in seconds to read until /// - Returns: `AVAudioPCMBuffer` containing the audio data. - static func loadAudio(fromPath audioFilePath: String, startTime: Double?, endTime: Double?) throws -> AVAudioPCMBuffer + static func loadAudio(fromPath audioFilePath: String, startTime: Double?, endTime: Double?, maxReadFrameSize: AVAudioFrameCount?) throws -> AVAudioPCMBuffer /// Loads and converts audio data from a specified file paths. /// - Parameter audioPaths: The file paths of the audio files. @@ -185,7 +185,12 @@ public class AudioProcessor: NSObject, AudioProcessing { // MARK: - Loading and conversion - public static func loadAudio(fromPath audioFilePath: String, startTime: Double? = 0, endTime: Double? = nil) throws -> AVAudioPCMBuffer { + public static func loadAudio( + fromPath audioFilePath: String, + startTime: Double? = 0, + endTime: Double? = nil, + maxReadFrameSize: AVAudioFrameCount? = nil + ) throws -> AVAudioPCMBuffer { guard FileManager.default.fileExists(atPath: audioFilePath) else { throw WhisperError.loadAudioFailed("Resource path does not exist \(audioFilePath)") } @@ -222,7 +227,8 @@ public class AudioProcessor: NSObject, AudioProcessing { outputBuffer = buffer } else { // Audio needs resampling to 16khz - outputBuffer = resampleAudio(fromFile: audioFile, toSampleRate: 16000, channelCount: 1, frameCount: frameCount) + let maxReadFrameSize = maxReadFrameSize ?? Constants.defaultAudioReadFrameSize + outputBuffer = resampleAudio(fromFile: audioFile, toSampleRate: 16000, channelCount: 1, frameCount: frameCount, maxReadFrameSize: maxReadFrameSize) } if let outputBuffer = outputBuffer { @@ -272,11 +278,13 @@ public class AudioProcessor: NSObject, AudioProcessing { toSampleRate sampleRate: Double, channelCount: AVAudioChannelCount, frameCount: AVAudioFrameCount? = nil, - maxReadFrameSize: AVAudioFrameCount = 1_323_000 // 30s of audio at commonly found 44.1khz sample rate + maxReadFrameSize: AVAudioFrameCount = Constants.defaultAudioReadFrameSize ) -> AVAudioPCMBuffer? { let inputFormat = audioFile.fileFormat + let inputStartFrame = audioFile.framePosition let inputFrameCount = frameCount ?? AVAudioFrameCount(audioFile.length) let inputDuration = Double(inputFrameCount) / inputFormat.sampleRate + let endFramePosition = min(inputStartFrame + AVAudioFramePosition(inputFrameCount), audioFile.length + 1) guard let outputFormat = AVAudioFormat(standardFormatWithSampleRate: sampleRate, channels: channelCount) else { Logging.error("Failed to create output audio format") @@ -293,8 +301,8 @@ public class AudioProcessor: NSObject, AudioProcessing { let inputBuffer = AVAudioPCMBuffer(pcmFormat: audioFile.processingFormat, frameCapacity: maxReadFrameSize)! - while audioFile.framePosition < inputFrameCount { - let remainingFrames = inputFrameCount - AVAudioFrameCount(audioFile.framePosition) + while audioFile.framePosition < endFramePosition { + let remainingFrames = AVAudioFrameCount(endFramePosition - audioFile.framePosition) let framesToRead = min(remainingFrames, maxReadFrameSize) let currentPositionInSeconds = Double(audioFile.framePosition) / inputFormat.sampleRate @@ -357,9 +365,19 @@ public class AudioProcessor: NSObject, AudioProcessing { /// - Returns: Resampled audio as an AVAudioPCMBuffer. /// - Throws: WhisperError if resampling fails. public static func resampleBuffer(_ buffer: AVAudioPCMBuffer, with converter: AVAudioConverter) throws -> AVAudioPCMBuffer { + var capacity = converter.outputFormat.sampleRate * Double(buffer.frameLength) / converter.inputFormat.sampleRate + + // Check if the capacity is a whole number + if capacity.truncatingRemainder(dividingBy: 1) != 0 { + // Round to the nearest whole number + let roundedCapacity = capacity.rounded(.toNearestOrEven) + Logging.debug("Rounding buffer frame capacity from \(capacity) to \(roundedCapacity) to better fit new sample rate") + capacity = roundedCapacity + } + guard let convertedBuffer = AVAudioPCMBuffer( pcmFormat: converter.outputFormat, - frameCapacity: AVAudioFrameCount(converter.outputFormat.sampleRate * Double(buffer.frameLength) / converter.inputFormat.sampleRate) + frameCapacity: AVAudioFrameCount(capacity) ) else { throw WhisperError.audioProcessingFailed("Failed to create converted buffer") } diff --git a/Sources/WhisperKit/Core/Models.swift b/Sources/WhisperKit/Core/Models.swift index b564695c..701f5482 100644 --- a/Sources/WhisperKit/Core/Models.swift +++ b/Sources/WhisperKit/Core/Models.swift @@ -2,6 +2,7 @@ // Copyright © 2024 Argmax, Inc. All rights reserved. import Accelerate +import AVFAudio import CoreML import Hub import NaturalLanguage @@ -1460,4 +1461,6 @@ public enum Constants { public static let languageCodes: Set = Set(languages.values) public static let defaultLanguageCode: String = "en" + + public static let defaultAudioReadFrameSize: AVAudioFrameCount = 1_323_000 // 30s of audio at commonly found 44.1khz sample rate } diff --git a/Sources/WhisperKit/Core/Utils.swift b/Sources/WhisperKit/Core/Utils.swift index 561ddd9b..87135100 100644 --- a/Sources/WhisperKit/Core/Utils.swift +++ b/Sources/WhisperKit/Core/Utils.swift @@ -201,27 +201,28 @@ extension AVAudioPCMBuffer { } guard startingFrame + AVAudioFramePosition(frameCount) <= AVAudioFramePosition(buffer.frameLength) else { - Logging.debug("Insufficient audio in buffer") + Logging.error("Insufficient audio in buffer") return false } - guard frameLength + frameCount <= frameCapacity else { - Logging.debug("Insufficient space in buffer") + guard let destination = floatChannelData, let source = buffer.floatChannelData else { + Logging.error("Failed to access float channel data") return false } - guard let destination = floatChannelData, let source = buffer.floatChannelData else { - Logging.debug("Failed to access float channel data") - return false + var calculatedFrameCount = frameCount + if frameLength + frameCount > frameCapacity { + Logging.debug("Insufficient space in buffer, reducing frame count to fit") + calculatedFrameCount = frameCapacity - frameLength } let calculatedStride = stride let destinationPointer = destination.pointee.advanced(by: calculatedStride * Int(frameLength)) let sourcePointer = source.pointee.advanced(by: calculatedStride * Int(startingFrame)) - memcpy(destinationPointer, sourcePointer, Int(frameCount) * calculatedStride * MemoryLayout.size) + memcpy(destinationPointer, sourcePointer, Int(calculatedFrameCount) * calculatedStride * MemoryLayout.size) - frameLength += frameCount + frameLength += calculatedFrameCount return true } diff --git a/Tests/WhisperKitTests/Resources/jfk_441khz.m4a b/Tests/WhisperKitTests/Resources/jfk_441khz.m4a new file mode 100644 index 0000000000000000000000000000000000000000..b8a1756ac1506818b1991333f827a9744cffef64 GIT binary patch literal 101547 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ɾe$q>^6@P28VOWC6{w>l!gxYI#r&Di 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 literal 0 HcmV?d00001 diff --git a/Tests/WhisperKitTests/UnitTests.swift b/Tests/WhisperKitTests/UnitTests.swift index 948c53c4..33e902d7 100644 --- a/Tests/WhisperKitTests/UnitTests.swift +++ b/Tests/WhisperKitTests/UnitTests.swift @@ -1,8 +1,8 @@ // For licensing see accompanying LICENSE.md file. // Copyright © 2024 Argmax, Inc. All rights reserved. -import Combine import AVFoundation +import Combine import CoreML import Hub import NaturalLanguage @@ -12,6 +12,10 @@ import XCTest @available(macOS 13, iOS 16, watchOS 10, visionOS 1, *) final class UnitTests: XCTestCase { + override func setUp() async throws { + Logging.shared.logLevel = .debug + } + // MARK: - Model Loading Test func testInit() async throws { @@ -39,11 +43,11 @@ final class UnitTests: XCTestCase { XCTAssertNotNil(audioBuffer, "Failed to load audio file at path: \(audioFilePath)") XCTAssertEqual(audioBuffer.format.sampleRate, 16000) XCTAssertEqual(audioBuffer.format.channelCount, 1) - XCTAssertEqual(audioBuffer.frameLength, 176000) + XCTAssertEqual(audioBuffer.frameLength, 176_000) XCTAssertEqual(audioBuffer.frameLength, 11 * 16000) let audioBufferWithStartTime = try AudioProcessor.loadAudio(fromPath: audioFilePath, startTime: 1.2) - XCTAssertEqual(audioBufferWithStartTime.frameLength, AVAudioFrameCount(156800)) + XCTAssertEqual(audioBufferWithStartTime.frameLength, AVAudioFrameCount(156_800)) XCTAssertEqual(audioBufferWithStartTime.frameLength, AVAudioFrameCount(16000 * (11 - 1.2))) let audioBufferWithStartTimeAndEndTime = try AudioProcessor.loadAudio(fromPath: audioFilePath, startTime: 1.2, endTime: 3.4) @@ -51,6 +55,37 @@ final class UnitTests: XCTestCase { XCTAssertEqual(audioBufferWithStartTimeAndEndTime.frameLength, AVAudioFrameCount(16000 * (3.4 - 1.2))) } + func testAudioFileLoadingWithResampling() throws { + let audioFilePath = try XCTUnwrap( + Bundle.module.path(forResource: "jfk_441khz", ofType: "m4a"), + "Audio file not found" + ) + let audioBuffer = try AudioProcessor.loadAudio(fromPath: audioFilePath) + XCTAssertNotNil(audioBuffer, "Failed to load audio file at path: \(audioFilePath)") + XCTAssertEqual(audioBuffer.format.sampleRate, 16000) + XCTAssertEqual(audioBuffer.format.channelCount, 1) + XCTAssertEqual(audioBuffer.frameLength, 176_000) + + // Test start time and end time with varying max frame sizes + let audioBufferWithStartTime1 = try AudioProcessor.loadAudio(fromPath: audioFilePath, startTime: 1.2) + XCTAssertEqual(audioBufferWithStartTime1.frameLength, AVAudioFrameCount(156_800)) + XCTAssertEqual(audioBufferWithStartTime1.frameLength, AVAudioFrameCount(16000 * (11 - 1.2))) + + let audioBufferWithStartTimeAndEndTime1 = try AudioProcessor.loadAudio(fromPath: audioFilePath, startTime: 1.2, endTime: 3.4) + XCTAssertEqual(audioBufferWithStartTimeAndEndTime1.frameLength, AVAudioFrameCount(35200)) + XCTAssertEqual(audioBufferWithStartTimeAndEndTime1.frameLength, AVAudioFrameCount(16000 * (3.4 - 1.2))) + + // NOTE: depending on frameSize, the final frame lengths will match due to integer division between sample rates + let frameSize = AVAudioFrameCount(10024) + let audioBufferWithStartTime2 = try AudioProcessor.loadAudio(fromPath: audioFilePath, startTime: 1.2, maxReadFrameSize: frameSize) + XCTAssertEqual(audioBufferWithStartTime2.frameLength, AVAudioFrameCount(156_800)) + XCTAssertEqual(audioBufferWithStartTime2.frameLength, AVAudioFrameCount(16000 * (11 - 1.2))) + + let audioBufferWithStartTimeAndEndTime2 = try AudioProcessor.loadAudio(fromPath: audioFilePath, startTime: 1.2, endTime: 3.4, maxReadFrameSize: frameSize) + XCTAssertEqual(audioBufferWithStartTimeAndEndTime2.frameLength, AVAudioFrameCount(35200)) + XCTAssertEqual(audioBufferWithStartTimeAndEndTime2.frameLength, AVAudioFrameCount(16000 * (3.4 - 1.2))) + } + func testAudioPad() { let audioSamples = [Float](repeating: 0.0, count: 1000) let paddedSamples = AudioProcessor.padOrTrimAudio(fromArray: audioSamples, startAt: 0, toLength: 1600) @@ -85,8 +120,6 @@ final class UnitTests: XCTestCase { } func testAudioResampleFromFile() throws { - Logging.shared.logLevel = .debug - let audioFileURL = try XCTUnwrap( Bundle.module.url(forResource: "jfk", withExtension: "wav"), "Audio file not found" @@ -95,7 +128,7 @@ final class UnitTests: XCTestCase { let targetSampleRate = 16000.0 let targetChannelCount: AVAudioChannelCount = 1 - let smallMaxReadFrameSize: AVAudioFrameCount = 10_000 // Small chunk size to test chunking logic + let smallMaxReadFrameSize: AVAudioFrameCount = 10000 // Small chunk size to test chunking logic let resampledAudio = AudioProcessor.resampleAudio( fromFile: audioFile, @@ -1187,7 +1220,6 @@ final class UnitTests: XCTestCase { func testVADAudioChunker() async throws { let chunker = VADAudioChunker() - Logging.shared.logLevel = .debug let singleChunkPath = try XCTUnwrap( Bundle.module.path(forResource: "jfk", ofType: "wav"),