iOS - AudioToolbox 内存泄漏

Posted

技术标签:

【中文标题】iOS - AudioToolbox 内存泄漏【英文标题】:iOS - AudioToolbox Memory leak 【发布时间】:2019-07-15 02:54:46 【问题描述】:

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

我创建了一个按钮,按钮事件只是运行带有音频路径的 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];

音频管理器

准备定义

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;

【问题讨论】:

【参考方案1】:

最后,我找到了解决方案。我只是杀了我的队列。 所有内存都被释放。把我的方法分享给所有有同一张票的人。

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

// the other code ...

【讨论】:

以上是关于iOS - AudioToolbox 内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章

AudioToolbox 库 AVAudioPlayer 中的内存泄漏

iOS内存泄漏检测方法

iOS:内存泄漏代码

iOS 内存泄漏的检测方式

iOS 内存泄漏排查以及处理

内存泄漏,在do-catch块中。 iOS,Swift