AudioTrackAnalyzer.m 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. // _____ _
  2. // |_ _| |_ _ _ ___ ___ _ __ __ _
  3. // | | | ' \| '_/ -_) -_) ' \/ _` |_
  4. // |_| |_||_|_| \___\___|_|_|_\__,_(_)
  5. //
  6. // Threema iOS Client
  7. // Copyright (c) 2014-2020 Threema GmbH
  8. //
  9. // This program is free software: you can redistribute it and/or modify
  10. // it under the terms of the GNU Affero General Public License, version 3,
  11. // as published by the Free Software Foundation.
  12. //
  13. // This program is distributed in the hope that it will be useful,
  14. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. // GNU Affero General Public License for more details.
  17. //
  18. // You should have received a copy of the GNU Affero General Public License
  19. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  20. #import "AudioTrackAnalyzer.h"
  21. #import <AVFoundation/AVFoundation.h>
  22. #import "RootSquareMean.h"
  23. #ifdef DEBUG
  24. static const DDLogLevel ddLogLevel = DDLogLevelVerbose;
  25. #else
  26. static const DDLogLevel ddLogLevel = DDLogLevelWarning;
  27. #endif
  28. #define DECIBEL_OFFSET 15.0
  29. @interface AudioTrackAnalyzer ()
  30. @property NSInteger numberOfChanels;
  31. @property NSURL *url;
  32. @property AVAssetTrack *songTrack;
  33. @property AVAssetReader *reader;
  34. @end
  35. @implementation AudioTrackAnalyzer
  36. + (instancetype)audioTrackAnalyzerFor: (NSURL *)audioUrl {
  37. AudioTrackAnalyzer *analyzer = [[AudioTrackAnalyzer alloc] init];
  38. analyzer.url = audioUrl;
  39. if ([analyzer loadAVData]) {
  40. return analyzer;
  41. } else {
  42. return nil;
  43. }
  44. }
  45. - (BOOL)loadAVData {
  46. NSError *error = nil;
  47. NSDictionary *options = @{ AVURLAssetPreferPreciseDurationAndTimingKey:@YES };
  48. AVURLAsset * urlAsset = [AVURLAsset URLAssetWithURL:_url options:options];
  49. if ([urlAsset.tracks count] < 1) {
  50. return NO;
  51. }
  52. _reader = [AVAssetReader assetReaderWithAsset:urlAsset error:&error];
  53. _songTrack = [urlAsset.tracks objectAtIndex:0];
  54. return YES;
  55. }
  56. - (NSTimeInterval)getDuration {
  57. CMTimeRange timeRange = _songTrack.timeRange;
  58. Float64 totalDuration = CMTimeGetSeconds(timeRange.duration);
  59. return totalDuration;
  60. }
  61. - (NSArray *) reduceAudioToDecibelLevels:(NSInteger)numberOfValues {
  62. UInt32 sampleRate = 0;
  63. UInt32 channelCount = 0;
  64. NSArray* formatDesc = _songTrack.formatDescriptions;
  65. for(unsigned int i = 0; i < [formatDesc count]; ++i) {
  66. CMAudioFormatDescriptionRef item = (__bridge CMAudioFormatDescriptionRef)[formatDesc objectAtIndex:i];
  67. const AudioStreamBasicDescription* fmtDesc = CMAudioFormatDescriptionGetStreamBasicDescription (item);
  68. if(fmtDesc) {
  69. sampleRate = fmtDesc->mSampleRate;
  70. channelCount = fmtDesc->mChannelsPerFrame;
  71. } else {
  72. DDLogError(@"Cannot decode audio track format description");
  73. return nil;
  74. }
  75. }
  76. NSDictionary *outputSettingsDict = [[NSDictionary alloc] initWithObjectsAndKeys:
  77. [NSNumber numberWithInt:kAudioFormatLinearPCM],AVFormatIDKey,
  78. [NSNumber numberWithFloat:sampleRate],AVSampleRateKey,
  79. [NSNumber numberWithInt:16],AVLinearPCMBitDepthKey,
  80. [NSNumber numberWithBool:NO],AVLinearPCMIsBigEndianKey,
  81. [NSNumber numberWithBool:NO],AVLinearPCMIsFloatKey,
  82. [NSNumber numberWithBool:NO],AVLinearPCMIsNonInterleaved,
  83. nil];
  84. AVAssetReaderTrackOutput* output = [[AVAssetReaderTrackOutput alloc] initWithTrack:_songTrack outputSettings:outputSettingsDict];
  85. output.alwaysCopiesSampleData = NO;
  86. [_reader addOutput:output];
  87. [_reader startReading];
  88. NSMutableArray *result = [NSMutableArray arrayWithCapacity:numberOfValues];
  89. CMTimeValue totalTimeValue = _songTrack.timeRange.duration.value;
  90. CMTimeValue samplesPerValue = totalTimeValue / numberOfValues;
  91. CMItemCount totalSampleCount = 0;
  92. RootSquareMean *rms = [[RootSquareMean alloc] init];
  93. while (_reader.status == AVAssetReaderStatusReading){
  94. CMSampleBufferRef sampleBufferRef = [output copyNextSampleBuffer];
  95. if (sampleBufferRef == NULL) {
  96. continue;
  97. }
  98. CMBlockBufferRef blockBufferRef = CMSampleBufferGetDataBuffer(sampleBufferRef);
  99. size_t length = CMBlockBufferGetDataLength(blockBufferRef);
  100. NSMutableData * data = [NSMutableData dataWithLength:length];
  101. CMBlockBufferCopyDataBytes(blockBufferRef, 0, length, data.mutableBytes);
  102. SInt16 *samples = (SInt16 *) data.mutableBytes;
  103. CMItemCount blockSampleCount = CMSampleBufferGetNumSamples(sampleBufferRef);
  104. totalSampleCount += blockSampleCount;
  105. for (CMItemCount i=0; i<blockSampleCount; i++) {
  106. for (int j=0; j<channelCount; j++) {
  107. [rms addValue: *samples];
  108. samples++;
  109. }
  110. if ([rms count] >= samplesPerValue) {
  111. Float32 value = [rms getAndReset];
  112. Float32 decibel = (20.0f * log10(value/UINT16_MAX) + DECIBEL_OFFSET);
  113. [result addObject: [NSNumber numberWithDouble: decibel]];
  114. if (_delegate) {
  115. [_delegate trackAnalyzerNextValue: decibel];
  116. }
  117. }
  118. }
  119. CMSampleBufferInvalidate(sampleBufferRef);
  120. CFRelease(sampleBufferRef);
  121. }
  122. if ([rms count] > 0) {
  123. Float32 value = [rms getAndReset];
  124. Float32 decibel = (20.0f * log10(value/UINT16_MAX) + DECIBEL_OFFSET);
  125. [result addObject: [NSNumber numberWithDouble: decibel]];
  126. }
  127. if (_reader.status == AVAssetReaderStatusFailed || _reader.status == AVAssetReaderStatusUnknown){
  128. return nil;
  129. }
  130. if (_delegate) {
  131. [_delegate trackAnalyzerFinished];
  132. }
  133. return result;
  134. }
  135. @end