From e95104f10eef50e178c12d5f19b1c81e60c47ea5 Mon Sep 17 00:00:00 2001 From: Prianka Liz Kariat Date: Mon, 6 May 2024 19:41:34 +0530 Subject: [PATCH] Added read at offset tests to MPPAudioRecordTests --- .../test/audio/core/MPPAudioRecordTests.mm | 192 ++++++++++++++---- 1 file changed, 158 insertions(+), 34 deletions(-) diff --git a/mediapipe/tasks/ios/test/audio/core/MPPAudioRecordTests.mm b/mediapipe/tasks/ios/test/audio/core/MPPAudioRecordTests.mm index fe640db3a3..fb4d6a3d75 100644 --- a/mediapipe/tasks/ios/test/audio/core/MPPAudioRecordTests.mm +++ b/mediapipe/tasks/ios/test/audio/core/MPPAudioRecordTests.mm @@ -25,6 +25,9 @@ static MPPFileInfo *const kSpeech16KHzMonoFileInfo = [[MPPFileInfo alloc] initWithName:@"speech_16000_hz_mono" type:@"wav"]; +static MPPFileInfo *const kSpeech48KHzMonoFileInfo = + [[MPPFileInfo alloc] initWithName:@"speech_48000_hz_mono" type:@"wav"]; + static AVAudioFormat *const kAudioEngineFormat = [[AVAudioFormat alloc] initWithCommonFormat:AVAudioPCMFormatFloat32 sampleRate:48000 @@ -63,6 +66,8 @@ - (BOOL)loadAudioPCMBuffer:(AVAudioPCMBuffer *)pcmBuffer error:(NSError **)error @implementation MPPAudioRecordTests +#pragma mark Tests + - (void)testInitAudioRecordFailsWithInvalidChannelCount { const NSInteger channelCount = 3; const NSInteger sampleRate = 8000; @@ -170,35 +175,121 @@ - (void)testConvertAndLoadAudioRecordWithMonoFormatAndLongBufferLengthSucceeds { channelCount:channelCount]; } -#pragma mark helpers -+ (MPPAudioRecord *)createAudioRecordWithChannelCount:(const NSInteger)channelCount - sampleRate:(const NSInteger)sampleRate - bufferLength:(const NSInteger)bufferLength { - MPPAudioDataFormat *audioDataFormat = - [[MPPAudioDataFormat alloc] initWithChannelCount:channelCount sampleRate:sampleRate]; +- (void)testConvertAndRepeatedlyLoadAudioRecordWithMonoFormatSucceeds { + const NSUInteger channelCount = 1; + const NSUInteger sampleRate = 8000; - MPPAudioRecord *audioRecord = [[MPPAudioRecord alloc] initWithAudioDataFormat:audioDataFormat - bufferLength:bufferLength - error:nil]; - XCTAssertNotNil(audioRecord); + // Buffer length is longer than the interim buffer produced by `MPPAudioRecord` after coversion of + // the audio samples to its format. Test ensures that the entire buffer is loaded as the most + // recent samples of the ring buffer of audio record by pushing out the oldest samples in this + // scenario. After loading the ring buffer, the earliest samples should be all zeroes since we + // start with a fresh audio record. + const NSUInteger bufferLength = 40000; - return audioRecord; + MPPAudioRecord *audioRecord = [MPPAudioRecordTests + assertCreateAndLoadAudioRecordSucceedsWithAudioFileInfo:kSpeech16KHzMonoFileInfo + sampleRate:sampleRate + bufferLength:bufferLength + channelCount:channelCount]; + + // Loads audio record with a second audio file to verify that older samples are shifted out to + // make room for new samples when th audio record is already loaded. + AVAudioPCMBuffer *bufferInAudioRecordFormat = + [MPPAudioRecordTests bufferFromAudioFileWithInfo:kSpeech16KHzMonoFileInfo + withFormatOfAudioRecord:audioRecord]; + [MPPAudioRecordTests assertSuccessOfLoadAudioRecord:audioRecord + withPCMBuffer:bufferInAudioRecordFormat]; +} + +- (void)testReadAudioRecordAtOffsetSucceeds { + const NSUInteger channelCount = 1; + const NSUInteger sampleRate = 8000; + + // Buffer length is equal to the interim buffer produced by `MPPAudioRecord` after coversion of + // the audio samples to its format. Test ensures that the entire buffer is loaded into the ring + // buffer of audio record in this scenario. + const NSUInteger expectedBufferLengthOfInternalConvertedAudioBuffer = 34180; + + // Buffer length is equal to the interim buffer produced by `MPPAudioRecord` after coversion of + // the audio samples to its format. Test ensures that the entire buffer is loaded into the ring + // buffer of audio record in this scenario. + MPPAudioRecord *audioRecord = [MPPAudioRecordTests + assertCreateAndLoadAudioRecordSucceedsWithAudioFileInfo:kSpeech16KHzMonoFileInfo + sampleRate:sampleRate + bufferLength: + expectedBufferLengthOfInternalConvertedAudioBuffer + channelCount:channelCount]; + + const NSUInteger offset = 4; + const NSUInteger length = 4000; + [MPPAudioRecordTests assertSuccessOfReadAudioRecord:audioRecord atOffset:offset length:length]; +} + +- (void)testReadAudioRecordAtOffsetFailsWithIndexOutOfBounds { + const NSInteger channelCount = 1; + const NSInteger sampleRate = 16000; + const NSInteger bufferLength = 100; + + MPPAudioRecord *audioRecord = [MPPAudioRecordTests + assertCreateAndLoadAudioRecordSucceedsWithAudioFileInfo:kSpeech16KHzMonoFileInfo + sampleRate:sampleRate + bufferLength:bufferLength + channelCount:channelCount]; + + const NSUInteger offset = 4; + const NSUInteger length = 100; + NSError *error; + MPPFloatBuffer *audioRecordBuffer = [audioRecord readAtOffset:offset + withLength:length + error:&error]; + NSError *expectedError = [NSError + errorWithDomain:kExpectedErrorDomain + code:MPPTasksErrorCodeInvalidArgumentError + userInfo:@{ + NSLocalizedDescriptionKey : + [NSString stringWithFormat: + @"Index out of range. `offset` (%lu) + `length` (%lu) must be <= " + @"`length` (%lu)", + offset, length, audioRecord.bufferLength] + }]; + + AssertEqualErrors(error, expectedError); } -+ (void)assertCreateAndLoadAudioRecordSucceedsWithAudioFileInfo:(MPPFileInfo *)fileInfo - sampleRate:(NSUInteger)sampleRate - bufferLength:(NSUInteger)bufferLength - channelCount:(NSUInteger)channelCount { +#pragma mark Create and Load Audio Record Assertions + ++ (MPPAudioRecord *)assertCreateAndLoadAudioRecordSucceedsWithAudioFileInfo:(MPPFileInfo *)fileInfo + sampleRate:(NSUInteger)sampleRate + bufferLength:(NSUInteger)bufferLength + channelCount: + (NSUInteger)channelCount { MPPAudioRecord *audioRecord = [MPPAudioRecordTests createAudioRecordWithChannelCount:channelCount sampleRate:sampleRate bufferLength:bufferLength]; AVAudioPCMBuffer *bufferInAudioRecordFormat = - [MPPAudioRecordTests bufferFromAudioFileWithInfo:fileInfo + [MPPAudioRecordTests bufferFromAudioFileWithInfo:kSpeech48KHzMonoFileInfo withFormatOfAudioRecord:audioRecord]; + [MPPAudioRecordTests assertSuccessOfLoadAudioRecord:audioRecord withPCMBuffer:bufferInAudioRecordFormat]; + + return audioRecord; +} + ++ (MPPAudioRecord *)createAudioRecordWithChannelCount:(const NSInteger)channelCount + sampleRate:(const NSInteger)sampleRate + bufferLength:(const NSInteger)bufferLength { + MPPAudioDataFormat *audioDataFormat = + [[MPPAudioDataFormat alloc] initWithChannelCount:channelCount sampleRate:sampleRate]; + + MPPAudioRecord *audioRecord = [[MPPAudioRecord alloc] initWithAudioDataFormat:audioDataFormat + bufferLength:bufferLength + error:nil]; + XCTAssertNotNil(audioRecord); + + return audioRecord; } + (AVAudioPCMBuffer *)bufferFromAudioFileWithInfo:(MPPFileInfo *)fileInfo @@ -232,40 +323,75 @@ + (AVAudioPCMBuffer *)bufferFromAudioFileWithInfo:(MPPFileInfo *)fileInfo + (void)assertSuccessOfLoadAudioRecord:(MPPAudioRecord *)audioRecord withPCMBuffer:(AVAudioPCMBuffer *)bufferInAudioRecordFormat { + MPPFloatBuffer *previousAudioRecordBuffer = [audioRecord readAtOffset:0 + withLength:audioRecord.bufferLength + error:nil]; + XCTAssertTrue([audioRecord loadAudioPCMBuffer:bufferInAudioRecordFormat error:nil]); + MPPFloatBuffer *audioRecordBuffer = [MPPAudioRecordTests readFullLengthBufferOfAudioRecord:audioRecord]; [MPPAudioRecordTests assertFloatBuffer:audioRecordBuffer - containsInOrderSamplesFromPCMBuffer:bufferInAudioRecordFormat]; + containsInOrderSamplesFromPreviousStateOfFloatBuffer:previousAudioRecordBuffer + containsInOrderSamplesFromPCMBuffer:bufferInAudioRecordFormat]; +} + +#pragma mark Read AudioRecord Assertion + ++ (void)assertSuccessOfReadAudioRecord:(MPPAudioRecord *)audioRecord + atOffset:(NSUInteger)offset + length:(NSUInteger)length { + MPPFloatBuffer *floatBuffer = [MPPAudioRecordTests readAudioRecord:audioRecord + atOffset:offset + length:length]; + MPPFloatBuffer *fullLengthAudioRecordFloatBuffer = + [MPPAudioRecordTests readFullLengthBufferOfAudioRecord:audioRecord]; + for (int i = 0; i < floatBuffer.length; i++) { + XCTAssertEqualWithAccuracy(floatBuffer.data[i], + fullLengthAudioRecordFloatBuffer.data[offset + i], FLT_EPSILON); + } } + (MPPFloatBuffer *)readFullLengthBufferOfAudioRecord:(MPPAudioRecord *)audioRecord { - MPPFloatBuffer *audioRecordBuffer = [audioRecord readAtOffset:0 - withLength:audioRecord.bufferLength - error:nil]; + return [MPPAudioRecordTests readAudioRecord:audioRecord + atOffset:0 + length:audioRecord.bufferLength]; +} + ++ (MPPFloatBuffer *)readAudioRecord:(MPPAudioRecord *)audioRecord + atOffset:(NSUInteger)offset + length:(NSUInteger)length { + MPPFloatBuffer *audioRecordBuffer = [audioRecord readAtOffset:offset withLength:length error:nil]; XCTAssertNotNil(audioRecordBuffer); - XCTAssertEqual(audioRecordBuffer.length, audioRecord.bufferLength); + XCTAssertEqual(audioRecordBuffer.length, length); return audioRecordBuffer; } +#pragma mark Helper Assertions + +// Verifies if the current float buffer is created by shifting out old samples to make room for +// samples from the `pcmBuffer`. + (void)assertFloatBuffer:(MPPFloatBuffer *)floatBuffer - containsInOrderSamplesFromPCMBuffer:(AVAudioPCMBuffer *)pcmBuffer { - // The float buffer read from `MPPAudioRecord` is compared with samples in the pcmBuffer. + containsInOrderSamplesFromPreviousStateOfFloatBuffer: + (MPPFloatBuffer *)previousStateOfFloatBuffer + containsInOrderSamplesFromPCMBuffer:(AVAudioPCMBuffer *)pcmBuffer { + // The float buffer read from `MPPAudioRecord` is compared with samples in the `pcmBuffer`. // The float buffer is compared in 2 chunks. If the float buffer is shorter than the pcmBuffer, - // the length of the first chunk = 0 and length of second chunk = full length of float buffer i.e, - // the entire float buffer is compared with the most recent samples of length = float buffer - // length in the pcmBuffer. If the float buffer is longer than the `pcmBuffer`, the first chunk - // (oldest samples) must be qual to zeros and the second chunks length = length of the - // `pcmBuffer`. All samples of pcmBuffer must be present in the second chunk of the float buffer - // in this case. + // the len(first chunk) = 0 and len(second chunk) = full length of float buffer i.e, the entire + // float buffer is compared with the most recent samples of length = float buffer length in the + // `pcmBuffer`. If the float buffer is longer than the `pcmBuffer`, the first chunk (oldest + // samples) must be equal to its previous state offset from the len(pcmBuffer) to verify that + // older elements are shifted out to make room for new elements from the `pcmBuffer`. len(second + // chunk) = len(`pcmBuffer`). All samples of `pcmBuffer` must replace the samples in the second + // chunk of the float buffer. const NSInteger secondChunkLength = std::min((NSInteger)floatBuffer.length, (NSInteger)pcmBuffer.frameLength); const NSInteger firstChunkLength = floatBuffer.length - secondChunkLength; - NSInteger firstChunkSamples = 0; for (int i = 0; i < firstChunkLength; i++) { - firstChunkSamples = firstChunkSamples + 1; - XCTAssertEqualWithAccuracy(floatBuffer.data[i], 0.00f, FLT_EPSILON); + XCTAssertEqualWithAccuracy(floatBuffer.data[i], + previousStateOfFloatBuffer.data[i + pcmBuffer.frameLength], + FLT_EPSILON); } // Starting indices for comparison of the second chunks in float bufer and `pcmBuffer` are @@ -273,9 +399,7 @@ + (void)assertFloatBuffer:(MPPFloatBuffer *)floatBuffer const NSInteger startIndexForComparisonInFloatBuffer = firstChunkLength; const NSInteger startIndexForComparisonInPCMBuffer = pcmBuffer.frameLength - secondChunkLength; - NSInteger samples = 0; for (int i = 0; i < secondChunkLength; i++) { - samples = samples + 1; XCTAssertEqualWithAccuracy( floatBuffer.data[startIndexForComparisonInFloatBuffer + i], pcmBuffer.floatChannelData[0][startIndexForComparisonInPCMBuffer + i], FLT_EPSILON);