Skip to content

Commit

Permalink
#12 Handle all FFmpeg sample types
Browse files Browse the repository at this point in the history
  • Loading branch information
kartik-venugopal committed Aug 19, 2024
1 parent e21842f commit 62f37fc
Show file tree
Hide file tree
Showing 11 changed files with 561 additions and 146 deletions.
16 changes: 16 additions & 0 deletions Aural.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -798,6 +798,10 @@
3E7638AB2857D88B00461F4D /* UnifiedPlayerWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3E7638AA2857D88B00461F4D /* UnifiedPlayerWindow.xib */; };
3E7638AD2857D9F600461F4D /* UnifiedPlayerWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E7638AC2857D9F600461F4D /* UnifiedPlayerWindowController.swift */; };
3E7638AF2857DA3F00461F4D /* UnifiedAppModeController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E7638AE2857DA3F00461F4D /* UnifiedAppModeController.swift */; };
3E772EC02C73F1B900DC3137 /* FFmpegReplayGainScanner+Int16.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E772EBC2C73F1B900DC3137 /* FFmpegReplayGainScanner+Int16.swift */; };
3E772EC12C73F1B900DC3137 /* FFmpegReplayGainScanner+Double.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E772EBD2C73F1B900DC3137 /* FFmpegReplayGainScanner+Double.swift */; };
3E772EC22C73F1B900DC3137 /* FFmpegReplayGainScanner+Int32.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E772EBE2C73F1B900DC3137 /* FFmpegReplayGainScanner+Int32.swift */; };
3E772EC32C73F1B900DC3137 /* FFmpegReplayGainScanner+Float.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E772EBF2C73F1B900DC3137 /* FFmpegReplayGainScanner+Float.swift */; };
3E7AF10528065441006DC98F /* PlayQueueMenuController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E7AF10428065441006DC98F /* PlayQueueMenuController.swift */; };
3E7C35FE26AADD2A00B3552D /* FilterPresetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E7C35FD26AADD2A00B3552D /* FilterPresetView.swift */; };
3E7C360126AADFA000B3552D /* FilterBandView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E7C360026AADFA000B3552D /* FilterBandView.swift */; };
Expand Down Expand Up @@ -1908,6 +1912,10 @@
3E7638AA2857D88B00461F4D /* UnifiedPlayerWindow.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = UnifiedPlayerWindow.xib; sourceTree = "<group>"; };
3E7638AC2857D9F600461F4D /* UnifiedPlayerWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnifiedPlayerWindowController.swift; sourceTree = "<group>"; };
3E7638AE2857DA3F00461F4D /* UnifiedAppModeController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnifiedAppModeController.swift; sourceTree = "<group>"; };
3E772EBC2C73F1B900DC3137 /* FFmpegReplayGainScanner+Int16.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "FFmpegReplayGainScanner+Int16.swift"; sourceTree = "<group>"; };
3E772EBD2C73F1B900DC3137 /* FFmpegReplayGainScanner+Double.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "FFmpegReplayGainScanner+Double.swift"; sourceTree = "<group>"; };
3E772EBE2C73F1B900DC3137 /* FFmpegReplayGainScanner+Int32.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "FFmpegReplayGainScanner+Int32.swift"; sourceTree = "<group>"; };
3E772EBF2C73F1B900DC3137 /* FFmpegReplayGainScanner+Float.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "FFmpegReplayGainScanner+Float.swift"; sourceTree = "<group>"; };
3E7AF10428065441006DC98F /* PlayQueueMenuController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayQueueMenuController.swift; sourceTree = "<group>"; };
3E7C35FD26AADD2A00B3552D /* FilterPresetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterPresetView.swift; sourceTree = "<group>"; };
3E7C360026AADFA000B3552D /* FilterBandView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterBandView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -5360,6 +5368,10 @@
children = (
3EE882792C72A0DF00E270B8 /* AVFReplayGainScanner.swift */,
3EE8827A2C72A0DF00E270B8 /* FFmpegReplayGainScanner.swift */,
3E772EBD2C73F1B900DC3137 /* FFmpegReplayGainScanner+Double.swift */,
3E772EBF2C73F1B900DC3137 /* FFmpegReplayGainScanner+Float.swift */,
3E772EBC2C73F1B900DC3137 /* FFmpegReplayGainScanner+Int16.swift */,
3E772EBE2C73F1B900DC3137 /* FFmpegReplayGainScanner+Int32.swift */,
);
path = LoudnessScan;
sourceTree = "<group>";
Expand Down Expand Up @@ -5992,6 +6004,7 @@
3E6C12A525CEBE2700BF0D07 /* EffectsUnitSlider.swift in Sources */,
3E0218B72C23490E00865AC2 /* PredictiveTrackPreparationAction.swift in Sources */,
3EBF292D2686961300D87021 /* EffectsUnitTabButton.swift in Sources */,
3E772EC32C73F1B900DC3137 /* FFmpegReplayGainScanner+Float.swift in Sources */,
3E2549432B7A7E0B00FD83D1 /* ModularPlayerViewController.swift in Sources */,
3E0219402C23490E00865AC2 /* MusicBrainzArtistCredit.swift in Sources */,
3EF72D6C2B71AAF5005166BF /* DiscreteCircularSlider.swift in Sources */,
Expand Down Expand Up @@ -6032,6 +6045,7 @@
3E25B65B27F78EE100D10A5F /* PlayQueueSimpleViewController.swift in Sources */,
3E6C12B325CEBE5800BF0D07 /* FavoritesMenuController.swift in Sources */,
3E5F942E25E57190002DEF80 /* FFT.swift in Sources */,
3E772EC02C73F1B900DC3137 /* FFmpegReplayGainScanner+Int16.swift in Sources */,
3EFAA8D52B7146AC001A6682 /* ChaptersListSearchFieldCell.swift in Sources */,
3E0219432C23490E00865AC2 /* MusicBrainzRelease.swift in Sources */,
3E0218502C23490E00865AC2 /* PlaylistFileHistoryItem.swift in Sources */,
Expand All @@ -6045,6 +6059,7 @@
3E0217EE2C23490E00865AC2 /* HostedAudioUnitDelegateProtocol.swift in Sources */,
3E6C130B25CEBE8F00BF0D07 /* FileSystemTrackInfoSource.swift in Sources */,
3E0219882C23490E00865AC2 /* NSApplicationExtensions.swift in Sources */,
3E772EC12C73F1B900DC3137 /* FFmpegReplayGainScanner+Double.swift in Sources */,
3E0218FA2C23490E00865AC2 /* PlaylistsManager.swift in Sources */,
3E02196D2C23490E00865AC2 /* PlaylistViewSelector.swift in Sources */,
3EAFB615267FF47200F0DC96 /* FilterBandSlider.swift in Sources */,
Expand Down Expand Up @@ -6459,6 +6474,7 @@
3E0219172C23490E00865AC2 /* Preferences.swift in Sources */,
3E0218F12C23490E00865AC2 /* TracksSort.swift in Sources */,
3EB88E772629DA9500C8D230 /* ThemePopupMenuController.swift in Sources */,
3E772EC22C73F1B900DC3137 /* FFmpegReplayGainScanner+Int32.swift in Sources */,
3E9C12152622259500D90EA4 /* ThemePreset.swift in Sources */,
3E0218BA2C23490E00865AC2 /* CloseFileHandlesAction.swift in Sources */,
3E7204C02B853DC800E40D87 /* FlatPlaylistTableViews.swift in Sources */,
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,40 @@ class AVFReplayGainScanner: ReplayGainScanner {

let file: URL

private let audioFile: AVAudioFile

private let audioFormat: AVAudioFormat
private let analysisFormat: AVAudioFormat

private let channelLayout: AVAudioChannelLayout
private let channelCount: AVAudioChannelCount
private let sampleRate: Double
private let totalSamples: AVAudioFramePosition

private let ebur128: EBUR128State

private static let analysisSampleFormat: AVAudioCommonFormat = .pcmFormatFloat32
private static let chunkSize: AVAudioFrameCount = 5 * 44100

init(file: URL) {
init(file: URL) throws {

self.file = file

self.audioFile = try AVAudioFile(forReading: file)

self.totalSamples = audioFile.length
self.audioFormat = audioFile.processingFormat
self.channelCount = audioFormat.channelCount
self.sampleRate = audioFormat.sampleRate

ebur128 = try EBUR128State(channelCount: Int(channelCount), sampleRate: Int(sampleRate), mode: .samplePeak)

self.channelLayout = audioFormat.channelLayout ?? .defaultLayoutForChannelCount(channelCount)

self.analysisFormat = .init(commonFormat: Self.analysisSampleFormat,
sampleRate: sampleRate,
interleaved: true,
channelLayout: channelLayout)
}

func scan(_ completionHandler: @escaping (EBUR128AnalysisResult) -> Void) {
Expand Down Expand Up @@ -45,27 +75,8 @@ class AVFReplayGainScanner: ReplayGainScanner {

do {

var time: Double = 0, st: Double = 0
var ebur128: EBUR128State?

let audioFile = try AVAudioFile(forReading: file)

let totalSamples = audioFile.length
var samplesRead: AVAudioFramePosition = 0

let audioFormat = audioFile.processingFormat
let channelCount = audioFormat.channelCount
let sampleRate = audioFormat.sampleRate

ebur128 = try EBUR128State(channelCount: Int(channelCount), sampleRate: Int(sampleRate), mode: .samplePeak)

let channelLayout = audioFormat.channelLayout ?? .defaultLayoutForChannelCount(channelCount)

let analysisFormat: AVAudioFormat = .init(commonFormat: .pcmFormatInt16,
sampleRate: sampleRate,
interleaved: true,
channelLayout: channelLayout)

guard let readBuffer = AVAudioPCMBuffer(pcmFormat: audioFormat, frameCapacity: Self.chunkSize),
let analyzeBuffer = AVAudioPCMBuffer(pcmFormat: analysisFormat, frameCapacity: Self.chunkSize) else {return nil}

Expand All @@ -79,25 +90,20 @@ class AVFReplayGainScanner: ReplayGainScanner {

try converter.convert(to: analyzeBuffer, from: readBuffer)

guard let int16Buffer = analyzeBuffer.int16ChannelData else {return nil}

st = CFAbsoluteTimeGetCurrent()
guard let floatBuffer = analyzeBuffer.floatChannelData else {return nil}

do {

try ebur128?.addFramesAsInt16(framesPointer: int16Buffer[0], frameCount: Int(analyzeBuffer.frameLength))
try ebur128.addFramesAsFloat(framesPointer: floatBuffer[0], frameCount: Int(analyzeBuffer.frameLength))

} catch let err as EBUR128Error {
print(err.description)

} catch {
print("Unknown error: \(error.localizedDescription)")
}

time += CFAbsoluteTimeGetCurrent() - st
}
return try ebur128?.analyze()

return try ebur128.analyze()

} catch {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
//
// FFmpegReplayGainScanner+Double.swift
// Aural
//
// Copyright © 2024 Kartik Venugopal. All rights reserved.
//
// This software is licensed under the MIT software license.
// See the file "LICENSE" in the project root directory for license terms.
//

import Foundation

extension FFmpegReplayGainScanner {

func scanAsDouble() throws -> EBUR128AnalysisResult? {

defer {
self.cleanUpAfterScan()
}

do {

var curSize: Int = 0
let sizeOfAFrame = MemoryLayout<Double>.size * channelCount

while !eof, consecutiveErrors < 3 {

do {

guard let pkt = try ctx.readPacket(from: stream) else {

consecutiveErrors += 1
continue
}

let frames = try codec.decode(packet: pkt)

for frame in frames.frames {

// Only 1 buffer since interleaved. Capacity = sampleCount * number of bytes in Int16 * channelCount
let newSize = frame.intSampleCount * sizeOfAFrame

if newSize > curSize {

outputData?[0] = .allocate(capacity: newSize)
curSize = newSize
}

swr?.convertFrame(frame, andStoreIn: outputData)

let pointer: UnsafeMutablePointer<UInt8>? = outputData?[0] ?? frame.dataPointers[0]

pointer?.withMemoryRebound(to: Double.self, capacity: frame.intSampleCount) {floatPtr in

do {

try ebur128.addFramesAsDouble(framesPointer: floatPtr, frameCount: frame.intSampleCount)
consecutiveErrors = 0

} catch let err as EBUR128Error {
print(err.description)

} catch {
print("Unknown error: \(error.localizedDescription)")
}
}
}

} catch let err as DecoderError {

eof = err.isEOF

if !err.isEOF {

consecutiveErrors += 1
print("Error: \(err.code.errorDescription)")
}

} catch let err as PacketReadError {

eof = err.isEOF

if !err.isEOF {

consecutiveErrors += 1
print("Error: \(err.code.errorDescription)")
}
}
}

} catch {
print("Error: \(error)")
}

return consecutiveErrors >= 3 ? nil : try ebur128.analyze()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
//
// FFmpegReplayGainScanner+Float.swift
// Aural
//
// Copyright © 2024 Kartik Venugopal. All rights reserved.
//
// This software is licensed under the MIT software license.
// See the file "LICENSE" in the project root directory for license terms.
//

import Foundation

extension FFmpegReplayGainScanner {

func scanAsFloat() throws -> EBUR128AnalysisResult? {

defer {
self.cleanUpAfterScan()
}

do {

var curSize: Int = 0
let sizeOfAFrame = MemoryLayout<Float>.size * channelCount

while !eof, consecutiveErrors < 3 {

do {

guard let pkt = try ctx.readPacket(from: stream) else {

consecutiveErrors += 1
continue
}

let frames = try codec.decode(packet: pkt)

for frame in frames.frames {

// Only 1 buffer since interleaved. Capacity = sampleCount * number of bytes in Int16 * channelCount
let newSize = frame.intSampleCount * sizeOfAFrame

if newSize > curSize {

outputData?[0] = .allocate(capacity: newSize)
curSize = newSize
}

swr?.convertFrame(frame, andStoreIn: outputData)

let pointer: UnsafeMutablePointer<UInt8>? = outputData?[0] ?? frame.dataPointers[0]

pointer?.withMemoryRebound(to: Float.self, capacity: frame.intSampleCount) {floatPtr in

do {

try ebur128.addFramesAsFloat(framesPointer: floatPtr, frameCount: frame.intSampleCount)
consecutiveErrors = 0

} catch let err as EBUR128Error {
print(err.description)

} catch {
print("Unknown error: \(error.localizedDescription)")
}
}
}

} catch let err as DecoderError {

eof = err.isEOF

if !err.isEOF {

consecutiveErrors += 1
print("Error: \(err.code.errorDescription)")
}

} catch let err as PacketReadError {

eof = err.isEOF

if !err.isEOF {

consecutiveErrors += 1
print("Error: \(err.code.errorDescription)")
}
}
}

} catch {
print("Error: \(error)")
}

return consecutiveErrors >= 3 ? nil : try ebur128.analyze()
}
}
Loading

0 comments on commit 62f37fc

Please sign in to comment.