iOS - AudioToolbox 内存泄漏

iOS - AudioToolbox Memory leak

我想实现一个音频管理器。但是我遇到了内存泄漏。 我不知道为什么和会发生什么。有人可以帮助我吗?

我创建了一个按钮,按钮事件只是运行带有音频路径的 playAudio。然后,我单击按钮,单击,单击,单击,...,单击(多次)。 内存使用量增加。我尝试在每次播放前关闭音频文件并清理内存,但没有用。


请帮助或尝试提供一些想法如何实现这一目标。谢谢!
很多细节你可以在 Github

中看到我的演示项目




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

准备定义

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


全局变量

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


我的代码

- (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);
}


音频队列输出方式

- (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;
}


回调函数

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


关闭音频文件

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

    return status;
}


可用内存

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

终于找到解决办法了。我只是杀了我的队列。 所有内存都被释放。把我的方法分享给所有同票的人。

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

// the other code ...
}