Phineas Huang
Phineas Huang

Reputation: 833

iOS - AudioToolbox Memory leak

I want to implement an audio manager. But I got a memory leak. I don't know why & what happen. Could someone help me?

I create a button, the button event just runs the playAudio with audio path. Then, I click the button, click, click, click, ..., click(many times). The memory usage is increased. I try to close the audio file and clean the memory before each play time, but no use.


Please help or try to give some ideas how to achieve this. Thanks!
Much detail you could see my demo project in Github


enter image description here



UIView

- (void)viewDidLoad {
    [super viewDidLoad];

    // Create an audio manager
    self.audio1 = [AudioPlayerManager new];
}

  // This is a button click event
- (IBAction)actionAudioPlay:(id)sender {
     NSString *path1 = [NSString stringWithFormat:@"%@", [[NSBundle 
           mainBundle] pathForResource:@"success-notification- 
           alert_A_major" ofType:@"wav"]];
     [self.audio1 playAudio:path1];
}


AudioManager

Prepare define

static const UInt32 maxBufferSize = 0x10000;
static const UInt32 minBufferSize = 0x4000;
static const UInt32 maxBufferNum = 3;


Global Variable

AudioFileID _audioFile;
AudioStreamBasicDescription _dataFormat;
AudioQueueRef _queue;
UInt32 numPacketsToRead;
AudioStreamPacketDescription *packetDescs;
AudioQueueBufferRef buffers[maxBufferNum];
SInt64 packetIndex;
UInt32 maxPacketSize;
UInt32 outBufferSize;


My code

- (void)playAudio:(NSString *)audioFileName {
    // Step 1: Open the audio file
    OSStatus status = AudioFileOpenURL(
            (__bridge CFURLRef _Nonnull)([NSURL 
            fileURLWithPath:audioPath]),
            kAudioFileReadPermission,
            0,
            &_audioFile);

    // Step 2: Read the meta-data of this audio file 
    UInt32 formatSize = sizeof(AudioStreamBasicDescription);
    status = AudioFileGetProperty(audioFileID, 
         kAudioFilePropertyDataFormat, &formatSize, &_dataFormat);

    // Step 3: Register the callback function
    status = AudioQueueNewOutput(
                    &dataFormat,
                    BufferCallback,
                    (__bridge void * _Nullable)(self),
                    nil,
                    nil,
                    0,
                    &_queue
                    );
    if (status != noErr) NSLog(@"AudioQueueNewOutput bitrate failed %d", status);

    // Step 4: Read the package size
    UInt32 size = sizeof(maxPacketSize);
    AudioFileGetProperty(
                     audioFileID,
                     kAudioFilePropertyPacketSizeUpperBound,
                     &size,
                     &maxPacketSize);
    if (status != noErr) NSLog(@"kAudioFilePropertyPacketSizeUpperBound failed %d", status);

    if (dataFormat.mFramesPerPacket != 0) {
        Float64 numPacketsPersecond = dataFormat.mSampleRate / dataFormat.mFramesPerPacket;
        outBufferSize = numPacketsPersecond * maxPacketSize;

    } else {
        outBufferSize = (maxBufferSize > maxPacketSize) ? maxBufferSize : maxPacketSize;
    }

    if (outBufferSize > maxBufferSize &&
        outBufferSize > maxPacketSize) {
        outBufferSize = maxBufferSize;

    } else {
        if (outBufferSize < minBufferSize) {
            outBufferSize = minBufferSize;
        }
    }

    // Step 5: Calculate the package count
    numPacketsToRead = outBufferSize / maxPacketSize;

    // Step 6: Alloc AudioStreamPacketDescription buffers
    packetDescs = (AudioStreamPacketDescription *)malloc(numPacketsToRead * sizeof (AudioStreamPacketDescription));

    // Step 7: Reset the packet index
    packetIndex = 0;

    // Step 8: Allocate buffer
    for (int i = 0; i < maxBufferNum; i++) {
        // Step 8.1: allock the buffer
        status = AudioQueueAllocateBuffer(
                                      _queue,
                                      outBufferSize,
                                      &buffers[i]
                                      );
        if (status != noErr) NSLog(@"AudioQueueAllocateBuffer failed %d", status);

        // Step 8.2: Fill the audio data to buffer
        [self audioQueueOutputWithQueue:_queue
                        queueBuffer:buffers[i]];
    }

    // Step 9: Start
    status = AudioQueueStart(_queue, nil);
    if (status != noErr) NSLog(@"AudioQueueStart failed %d", status);
}


Audio queue output method

- (void)audioQueueOutputWithQueue:(AudioQueueRef)audioQueue
                  queueBuffer:(AudioQueueBufferRef)audioQueueBuffer {
    OSStatus status;

    // Step 1: load audio data
    // If the packetIndex is out of range, the ioNumPackets will be 0
    UInt32 ioNumBytes = outBufferSize;
    UInt32 ioNumPackets = numPacketsToRead;
    status = AudioFileReadPacketData(
                        _audioFile,
                        NO,
                        &ioNumBytes,
                        packetDescs,
                        packetIndex,
                        &ioNumPackets,
                        audioQueueBuffer->mAudioData
                        );
    if (status != noErr) NSLog(@"AudioQueueSetParameter failed %d", status);

    // Step 2: prevent load audio data failed
    if (ioNumPackets <= 0) {
        return;
    }

    // Step 3: re-assign the data size
    audioQueueBuffer->mAudioDataByteSize = ioNumBytes;

    // Step 4: fill the buffer to AudioQueue
    status = AudioQueueEnqueueBuffer(
                        audioQueue,
                        audioQueueBuffer,
                        ioNumPackets,
                        packetDescs
                        );
    if (status != noErr) NSLog(@"AudioQueueEnqueueBuffer failed %d", status);

    // Step 5: Shift to followed index
    packetIndex += ioNumPackets;
}


Callback function

static void BufferCallback(void *inUserData,AudioQueueRef inAQ,
                       AudioQueueBufferRef buffer) {
    AudioPlayerManager *manager = (__bridge AudioPlayerManager *)inUserData;
    [manager audioQueueOutputWithQueue:inAQ queueBuffer:buffer];
}


Close audio file

- (OSStatus)close:(AudioFileID)audioFileID {
    OSStatus status = AudioFileClose( audioFileID );
    if (status != noErr) NSLog(@"AudioFileClose failed %d", status);

    return status;
}


Free Memory

- (void)freeMemory {
    if (packetDescs) {
        free(packetDescs);
    }
    packetDescs = NULL;
}

Upvotes: 3

Views: 513

Answers (1)

Phineas Huang
Phineas Huang

Reputation: 833

Finally, I find out the solution. I just kill out my queue. All of memory are released. Share my method to everyone who has the same ticket.

- (void)playAudio:(NSString *)audioFileName {
// Add these code
    if (_queue) {
        AudioFileClose(_audioFile);
        [self freeMemory];
        AudioQueueStop(_queue, true);
        AudioQueueDispose(_queue, true);
        _queue = nil;
    }

// the other code ...
}

Upvotes: 1

Related Questions