// _____ _ // |_ _| |_ _ _ ___ ___ _ __ __ _ // | | | ' \| '_/ -_) -_) ' \/ _` |_ // |_| |_||_|_| \___\___|_|_|_\__,_(_) // // Threema iOS Client // Copyright (c) 2014-2020 Threema GmbH // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License, version 3, // as published by the Free Software Foundation. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . #import "AudioTrackAnalyzer.h" #import #import "RootSquareMean.h" #ifdef DEBUG static const DDLogLevel ddLogLevel = DDLogLevelVerbose; #else static const DDLogLevel ddLogLevel = DDLogLevelWarning; #endif #define DECIBEL_OFFSET 15.0 @interface AudioTrackAnalyzer () @property NSInteger numberOfChanels; @property NSURL *url; @property AVAssetTrack *songTrack; @property AVAssetReader *reader; @end @implementation AudioTrackAnalyzer + (instancetype)audioTrackAnalyzerFor: (NSURL *)audioUrl { AudioTrackAnalyzer *analyzer = [[AudioTrackAnalyzer alloc] init]; analyzer.url = audioUrl; if ([analyzer loadAVData]) { return analyzer; } else { return nil; } } - (BOOL)loadAVData { NSError *error = nil; NSDictionary *options = @{ AVURLAssetPreferPreciseDurationAndTimingKey:@YES }; AVURLAsset * urlAsset = [AVURLAsset URLAssetWithURL:_url options:options]; if ([urlAsset.tracks count] < 1) { return NO; } _reader = [AVAssetReader assetReaderWithAsset:urlAsset error:&error]; _songTrack = [urlAsset.tracks objectAtIndex:0]; return YES; } - (NSTimeInterval)getDuration { CMTimeRange timeRange = _songTrack.timeRange; Float64 totalDuration = CMTimeGetSeconds(timeRange.duration); return totalDuration; } - (NSArray *) reduceAudioToDecibelLevels:(NSInteger)numberOfValues { UInt32 sampleRate = 0; UInt32 channelCount = 0; NSArray* formatDesc = _songTrack.formatDescriptions; for(unsigned int i = 0; i < [formatDesc count]; ++i) { CMAudioFormatDescriptionRef item = (__bridge CMAudioFormatDescriptionRef)[formatDesc objectAtIndex:i]; const AudioStreamBasicDescription* fmtDesc = CMAudioFormatDescriptionGetStreamBasicDescription (item); if(fmtDesc) { sampleRate = fmtDesc->mSampleRate; channelCount = fmtDesc->mChannelsPerFrame; } else { DDLogError(@"Cannot decode audio track format description"); return nil; } } NSDictionary *outputSettingsDict = [[NSDictionary alloc] initWithObjectsAndKeys: [NSNumber numberWithInt:kAudioFormatLinearPCM],AVFormatIDKey, [NSNumber numberWithFloat:sampleRate],AVSampleRateKey, [NSNumber numberWithInt:16],AVLinearPCMBitDepthKey, [NSNumber numberWithBool:NO],AVLinearPCMIsBigEndianKey, [NSNumber numberWithBool:NO],AVLinearPCMIsFloatKey, [NSNumber numberWithBool:NO],AVLinearPCMIsNonInterleaved, nil]; AVAssetReaderTrackOutput* output = [[AVAssetReaderTrackOutput alloc] initWithTrack:_songTrack outputSettings:outputSettingsDict]; output.alwaysCopiesSampleData = NO; [_reader addOutput:output]; [_reader startReading]; NSMutableArray *result = [NSMutableArray arrayWithCapacity:numberOfValues]; CMTimeValue totalTimeValue = _songTrack.timeRange.duration.value; CMTimeValue samplesPerValue = totalTimeValue / numberOfValues; CMItemCount totalSampleCount = 0; RootSquareMean *rms = [[RootSquareMean alloc] init]; while (_reader.status == AVAssetReaderStatusReading){ CMSampleBufferRef sampleBufferRef = [output copyNextSampleBuffer]; if (sampleBufferRef == NULL) { continue; } CMBlockBufferRef blockBufferRef = CMSampleBufferGetDataBuffer(sampleBufferRef); size_t length = CMBlockBufferGetDataLength(blockBufferRef); NSMutableData * data = [NSMutableData dataWithLength:length]; CMBlockBufferCopyDataBytes(blockBufferRef, 0, length, data.mutableBytes); SInt16 *samples = (SInt16 *) data.mutableBytes; CMItemCount blockSampleCount = CMSampleBufferGetNumSamples(sampleBufferRef); totalSampleCount += blockSampleCount; for (CMItemCount i=0; i= samplesPerValue) { Float32 value = [rms getAndReset]; Float32 decibel = (20.0f * log10(value/UINT16_MAX) + DECIBEL_OFFSET); [result addObject: [NSNumber numberWithDouble: decibel]]; if (_delegate) { [_delegate trackAnalyzerNextValue: decibel]; } } } CMSampleBufferInvalidate(sampleBufferRef); CFRelease(sampleBufferRef); } if ([rms count] > 0) { Float32 value = [rms getAndReset]; Float32 decibel = (20.0f * log10(value/UINT16_MAX) + DECIBEL_OFFSET); [result addObject: [NSNumber numberWithDouble: decibel]]; } if (_reader.status == AVAssetReaderStatusFailed || _reader.status == AVAssetReaderStatusUnknown){ return nil; } if (_delegate) { [_delegate trackAnalyzerFinished]; } return result; } @end