Skip to content
Open
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
6 changes: 3 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ All notable changes to this project will be documented in this file.

---

## [Master](https://github.com/fulldecent/FDWaveformView/compare/4.0.1...master)
## [Master](https://github.com/fulldecent/FDWaveformView/compare/5.0.1...master)

#### Changed

---

## [4.0.1](https://github.com/fulldecent/FDWaveformView/releases/tag/4.0.1)
## [5.0.1](https://github.com/fulldecent/FDWaveformView/releases/tag/5.0.1)
Released on 2020-02-12.

#### Changed
Expand All @@ -21,7 +21,7 @@ Released on 2020-02-12.

---

## [4.0.0](https://github.com/fulldecent/FDWaveformView/releases/tag/4.0.0)
## [5.0.0](https://github.com/fulldecent/FDWaveformView/releases/tag/5.0.0)
Released on 2019-04-08.

#### Changed
Expand Down
4 changes: 2 additions & 2 deletions FDWaveformView.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'FDWaveformView'
s.version = '4.0.1'
s.version = '5.0.1'
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.summary = 'Reads an audio file and displays the waveform'
s.description = <<-DESC
Expand All @@ -9,7 +9,7 @@ DESC
s.homepage = 'https://github.com/fulldecent/FDWaveformView'
s.screenshots = 'https://camo.githubusercontent.com/8c51361597e3c150cce6f60db5055663a7a7f8f1/68747470733a2f2f692e696d6775722e636f6d2f354e376f7a6f672e706e67', 'https://camo.githubusercontent.com/3c21c8437f922ba6cb1a44b0701c02c140221d84/68747470733a2f2f692e696d6775722e636f6d2f665272486952502e706e67', 'https://camo.githubusercontent.com/771973985f42a25931bfafba291f313ba8e46e32/68747470733a2f2f692e696d6775722e636f6d2f4a514f4b51336f2e706e67', 'https://camo.githubusercontent.com/21e361bff1e2351a8f54636881c4290e4818501a/68747470733a2f2f692e696d6775722e636f6d2f386f52376370712e676966', 'https://camo.githubusercontent.com/700a0eeb4bfbf5bab688dcb11ef60784b2074eef/68747470733a2f2f692e696d6775722e636f6d2f456778586143592e676966'
s.author = { 'William Entriken' => '[email protected]' }
s.source = { :git => 'https://github.com/fulldecent/FDWaveformView.git', :tag => s.version.to_s }
s.source = { :git => 'https://github.com/fulldecent/FDWaveformView.git', :tag => "#{s.version}" }
s.social_media_url = 'https://twitter.com/fulldecent'
s.ios.deployment_target = '8.0'
s.swift_version = '5.0'
Expand Down
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version:5.1
// swift-tools-version:5.3

import PackageDescription

Expand Down
64 changes: 0 additions & 64 deletions Sources/FDWaveformView/FDAudioContext.swift

This file was deleted.

115 changes: 115 additions & 0 deletions Sources/FDWaveformView/FDAudioSource.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// Copyright 2013–2020, William Entriken and the FDWaveformView contributors.
// Released under the MIT license as part of the FDWaveformView project.

import Foundation
import AVFoundation

enum FDAudioSourceError: Error {
case FailedToReadTrackSamples
}

/// Reads samples from an audio file
final class FDAudioSource: FDWaveformViewDataSource {

public let startIndex: Int = 0

public let endIndex: Int

/// Number of samples available
public let count: Int

/// The audio asset URL used to load the context
let audioURL: URL

/// Loaded asset
let asset: AVAsset

/// Loaded assetTrack
let assetTrack: AVAssetTrack

// MARK: - Initialization

// This is private beacuse details are not known until audio is asynchronously loaded
private init(audioURL: URL, totalSamples: Int, asset: AVAsset, assetTrack: AVAssetTrack) {
self.endIndex = totalSamples
self.audioURL = audioURL
self.asset = asset
self.assetTrack = assetTrack
count = endIndex - startIndex
}

/// Attempt to create collection of samples from an auedio track inside `audioURL`. This is a static function rather than a constructor because we run asynchronously.
/// - Parameters:
/// - audioURL: A media file to load
/// - completionHandler: The asynchronous callback (can call on any thread) and may possibly be called synchronousle
/// - Returns: Void
public static func load(fromAudioURL audioURL: URL, completionHandler: @escaping (_ audioContext: FDAudioSource?) -> ()) {
let assetOptions = [AVURLAssetPreferPreciseDurationAndTimingKey: NSNumber(value: true as Bool)]
let asset = AVURLAsset(url: audioURL, options: assetOptions)
guard let assetTrack = asset.tracks(withMediaType: .audio).first else
{
NSLog("FDWaveformView failed to load AVAssetTrack audio track")
completionHandler(nil)
return
}

asset.loadValuesAsynchronously(forKeys: ["duration"]) {
var error: NSError?
if asset.statusOfValue(forKey: "duration", error: &error) == .loaded {
let totalSamples = Int(Float64(assetTrack.naturalTimeScale) * Float64(asset.duration.value) / Float64(asset.duration.timescale))
completionHandler(Self.init(audioURL: audioURL, totalSamples: totalSamples, asset: asset, assetTrack: assetTrack))
return
}
print("FDWaveformView could not load asset: \(error?.localizedDescription ?? "Unknown error")")
completionHandler(nil)
}
}

// MARK: - Features

/// Get audio sample data
/// - Parameter bounds: These are in units of the `assetTrack`'s natural timescale, as is startIndex and endIndex
/// - Throws:FDAudioSourceError
/// - Returns: 16-bit data values (2 bytes per sample)
func readSampleData(bounds: Range<Int>) throws -> Data {
//TODO: Consider outputting [Float] directly here. Then possible this could conform to RandomAccessCollection
let assetReader = try AVAssetReader(asset: asset) // AVAssetReader is a one-shot reader so we cannot make it a class property
assetReader.timeRange = CMTimeRange(start: CMTime(value: Int64(bounds.lowerBound), timescale: assetTrack.naturalTimeScale),
duration: CMTime(value: Int64(bounds.count), timescale: assetTrack.naturalTimeScale))
let outputSettingsDict: [String : Any] = [
AVFormatIDKey: Int(kAudioFormatLinearPCM),
AVLinearPCMBitDepthKey: 16,
AVLinearPCMIsBigEndianKey: false,
AVLinearPCMIsFloatKey: false, // TODO: Maybe use float here because we convert using DSP later anyway. Need to profile performance of this change.
AVLinearPCMIsNonInterleaved: false
]

let readerOutput = AVAssetReaderTrackOutput(track: assetTrack, outputSettings: outputSettingsDict)
readerOutput.alwaysCopiesSampleData = false
assetReader.add(readerOutput)

var sampleBuffer = Data() // 16-bit samples
assetReader.startReading()
defer { assetReader.cancelReading() } // Cancel reading if we exit early or if operation is cancelled

while assetReader.status == .reading {
guard let readSampleBuffer = readerOutput.copyNextSampleBuffer(),
let readBuffer = CMSampleBufferGetDataBuffer(readSampleBuffer) else {
break
}
// Append audio sample buffer into our current sample buffer
var readBufferLength: Int = 0
var readBufferPointer: UnsafeMutablePointer<Int8>?
CMBlockBufferGetDataPointer(readBuffer, atOffset: 0, lengthAtOffsetOut: &readBufferLength, totalLengthOut: nil, dataPointerOut: &readBufferPointer)
sampleBuffer.append(UnsafeBufferPointer(start: readBufferPointer, count: readBufferLength))
CMSampleBufferInvalidate(readSampleBuffer)
}

// if (reader.status == AVAssetReaderStatusFailed || reader.status == AVAssetReaderStatusUnknown)
// Something went wrong. Handle it or do not, depending on if you can get above to work
if assetReader.status == .completed {
return sampleBuffer
}
throw FDAudioSourceError.FailedToReadTrackSamples
}
}
Loading