Skip to content

Commit

Permalink
Merge pull request #5381 from priankakariatyml:ios-audio-record-test-…
Browse files Browse the repository at this point in the history
…updates

PiperOrigin-RevId: 631205252
  • Loading branch information
copybara-github committed May 6, 2024
2 parents fff374d + e95104f commit 4824b07
Showing 1 changed file with 157 additions and 34 deletions.
191 changes: 157 additions & 34 deletions mediapipe/tasks/ios/test/audio/core/MPPAudioRecordTests.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -170,35 +175,120 @@ - (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)assertCreateAndLoadAudioRecordSucceedsWithAudioFileInfo:(MPPFileInfo *)fileInfo
sampleRate:(NSUInteger)sampleRate
bufferLength:(NSUInteger)bufferLength
channelCount:(NSUInteger)channelCount {
- (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;
[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);
}

#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
Expand Down Expand Up @@ -232,50 +322,83 @@ + (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
// calculated.
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);
Expand Down

0 comments on commit 4824b07

Please sign in to comment.