// // Copyright (c) 2017 Open Whisper Systems. All rights reserved. // #import "AudioStretcher.h" #import "Util.h" #import "time_scale.h" #define MIN_STRETCH_FACTOR 0.1 #define MAX_STRETCH_FACTOR 10 @implementation AudioStretcher + (AudioStretcher *)audioStretcher { AudioStretcher *s = [AudioStretcher new]; checkOperation(time_scale_init(&s->timeScaleState, SAMPLE_RATE, 1.0) != NULL); return s; } - (NSData *)stretchAudioData:(NSData *)audioData stretchFactor:(double)stretchFactor { ows_require(stretchFactor > MIN_STRETCH_FACTOR); ows_require(stretchFactor < MAX_STRETCH_FACTOR); if (audioData == nil) return nil; checkOperationDescribe(time_scale_rate(&timeScaleState, (float)stretchFactor) == 0, @"Changing time scaling"); int inputSampleCount = (unsigned int)audioData.length / sizeof(int16_t); int maxOutputSampleCount = [self getSafeMaxOutputSampleCountFromInputSampleCount:inputSampleCount]; int16_t *input = (int16_t *)[audioData bytes]; NSMutableData *d = [NSMutableData dataWithLength:(NSUInteger)maxOutputSampleCount * sizeof(int16_t)]; int outputSampleCount = time_scale(&timeScaleState, [d mutableBytes], input, inputSampleCount); checkOperationDescribe(outputSampleCount >= 0 && outputSampleCount <= maxOutputSampleCount, @"Scaling audio"); return [d take:(NSUInteger)outputSampleCount * sizeof(int16_t)]; } - (int)getSafeMaxOutputSampleCountFromInputSampleCount:(int)inputSampleCount { // WARNING: In some cases SpanDSP (time_scale.h v 1.20) underestimates how much buffer it will need, so we must pad // the result to be safe. // Issues has been notified upstream and once it is patched the padding can be removed int unsafe_maxOutputSampleCount = time_scale_max_output_len(&timeScaleState, inputSampleCount); const int BUFFER_OVERFLOW_PROTECTION_PAD = 2048; const int BUFFER_OVERFLOW_PROPORTIONAL_MULTIPLIER = 2; int expandedMaxCountToAvoidBufferOverflows = BUFFER_OVERFLOW_PROTECTION_PAD + (unsafe_maxOutputSampleCount * BUFFER_OVERFLOW_PROPORTIONAL_MULTIPLIER); checkOperation(expandedMaxCountToAvoidBufferOverflows >= 0); return expandedMaxCountToAvoidBufferOverflows; } - (void)dealloc { checkOperation(time_scale_release(&timeScaleState) == 0); } @end