Try to optimize the time when calculate the maxBufferCount, it's cheap to get free memory so we check each time before prefetch

Also, move the memory warning handler into frame pool class (100 player may use one frame pool)
This commit is contained in:
DreamPiggy 2023-04-26 11:57:52 +08:00
parent a206229905
commit 858b64aef7
2 changed files with 42 additions and 92 deletions

View File

@ -14,7 +14,6 @@
#import "SDInternalMacros.h"
@interface SDAnimatedImagePlayer () {
// SD_LOCK_DECLARE(_lock);
NSRunLoopMode _runLoopMode;
}
@ -24,6 +23,7 @@
@property (nonatomic, assign, readwrite) NSUInteger currentFrameIndex;
@property (nonatomic, assign, readwrite) NSUInteger currentLoopCount;
@property (nonatomic, strong) id<SDAnimatedImageProvider> animatedProvider;
@property (nonatomic, assign) NSUInteger currentFrameBytes;
@property (nonatomic, assign) NSTimeInterval currentTime;
@property (nonatomic, assign) BOOL bufferMiss;
@property (nonatomic, assign) BOOL needsDisplayWhenImageBecomesAvailable;
@ -48,10 +48,6 @@
self.animatedProvider = provider;
self.playbackRate = 1.0;
self.framePool = [SDImageFramePool registerProvider:provider];
// SD_LOCK_INIT(_lock);
#if SD_UIKIT
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
}
return self;
}
@ -61,32 +57,6 @@
return player;
}
#pragma mark - Life Cycle
- (void)dealloc {
#if SD_UIKIT
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
}
- (void)didReceiveMemoryWarning:(NSNotification *)notification {
[self clearFrameBuffer];
// [_fetchQueue cancelAllOperations];
// NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
// NSNumber *currentFrameIndex = @(self.currentFrameIndex);
// SD_LOCK(self->_lock);
// NSArray *keys = self.frameBuffer.allKeys;
// // only keep the next frame for later rendering
// for (NSNumber * key in keys) {
// if (![key isEqualToNumber:currentFrameIndex]) {
// [self.frameBuffer removeObjectForKey:key];
// }
// }
// SD_UNLOCK(self->_lock);
// }];
// [_fetchQueue addOperation:operation];
}
#pragma mark - Private
- (SDDisplayLink *)displayLink {
@ -140,12 +110,11 @@
UIImage *posterFrame = [[UIImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:image.imageOrientation];
#endif
if (posterFrame) {
// Calculate max buffer size
[self calculateMaxBufferCountWithFrame:posterFrame];
// HACK: The first frame should not check duration and immediately display
self.needsDisplayWhenImageBecomesAvailable = YES;
[self.framePool setFrame:posterFrame atIndex:self.currentFrameIndex];
// SD_LOCK(self->_lock);
// self.frameBuffer[@(self.currentFrameIndex)] = posterFrame;
// SD_UNLOCK(self->_lock);
}
}
@ -163,9 +132,6 @@
- (void)clearFrameBuffer {
[self.framePool removeAllFrames];
// SD_LOCK(_lock);
// [_frameBuffer removeAllObjects];
// SD_UNLOCK(_lock);
}
#pragma mark - Animation Control
@ -173,12 +139,9 @@
[self.displayLink start];
// Setup frame
[self setupCurrentFrame];
// Calculate max buffer size
[self calculateMaxBufferCount];
}
- (void)stopPlaying {
// [_fetchQueue cancelAllOperations];
// Using `_displayLink` here because when UIImageView dealloc, it may trigger `[self stopAnimating]`, we already release the display link in SDAnimatedImageView's dealloc method.
[_displayLink stop];
// We need to reset the frame status, but not trigger any handle. This can ensure next time's playing status correct.
@ -186,7 +149,6 @@
}
- (void)pausePlaying {
// [_fetchQueue cancelAllOperations];
[_displayLink stop];
}
@ -248,29 +210,11 @@
// Check if we need to display new frame firstly
BOOL bufferFull = NO;
if (self.needsDisplayWhenImageBecomesAvailable) {
UIImage *currentFrame = [self.framePool frameAtIndex:currentFrameIndex];
// SD_LOCK(_lock);
// currentFrame = self.frameBuffer[@(currentFrameIndex)];
// SD_UNLOCK(_lock);
// Update the current frame
if (currentFrame) {
if (self.framePool.currentFrameCount == totalFrameCount) {
bufferFull = YES;
}
// SD_LOCK(_lock);
// // Remove the frame buffer if need
// if (self.frameBuffer.count > self.maxBufferCount) {
// self.frameBuffer[@(currentFrameIndex)] = nil;
// }
// // Check whether we can stop fetch
// if (self.frameBuffer.count == totalFrameCount) {
// bufferFull = YES;
// }
// SD_UNLOCK(_lock);
// Update the current frame immediately
self.currentFrame = currentFrame;
[self handleFrameChange];
@ -292,8 +236,7 @@
if (self.currentTime < currentDuration) {
// Current frame timestamp not reached, prefetch frame in advance.
[self prefetchFrameAtIndex:currentFrameIndex
nextIndex:nextFrameIndex
bufferFull:bufferFull];
nextIndex:nextFrameIndex];
return;
}
@ -329,49 +272,31 @@
}
[self prefetchFrameAtIndex:currentFrameIndex
nextIndex:nextFrameIndex
bufferFull:bufferFull];
nextIndex:nextFrameIndex];
}
// Check if we should prefetch next frame or current frame
// When buffer miss, means the decode speed is slower than render speed, we fetch current miss frame
// Or, most cases, the decode speed is faster than render speed, we fetch next frame
- (void)prefetchFrameAtIndex:(NSUInteger)currentIndex
nextIndex:(NSUInteger)nextIndex
bufferFull:(BOOL)bufferFull {
nextIndex:(NSUInteger)nextIndex {
NSUInteger fetchFrameIndex = currentIndex;
UIImage *fetchFrame = nil;
if (!self.bufferMiss) {
fetchFrameIndex = nextIndex;
fetchFrame = [self.framePool frameAtIndex:nextIndex];
// SD_LOCK(_lock);
// fetchFrame = self.frameBuffer[@(nextIndex)];
// SD_UNLOCK(_lock);
}
BOOL bufferFull = NO;
if (self.framePool.currentFrameCount == self.totalFrameCount) {
bufferFull = YES;
}
if (!fetchFrame && !bufferFull) {
// Calculate max buffer size
[self calculateMaxBufferCountWithFrame:self.currentFrame];
// Prefetch next frame
[self.framePool prefetchFrameAtIndex:fetchFrameIndex];
}
}
// if (!fetchFrame && !bufferFull && self.fetchQueue.operationCount == 0) {
// // Prefetch next frame in background queue
// id<SDAnimatedImageProvider> animatedProvider = self.animatedProvider;
// @weakify(self);
// NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
// @strongify(self);
// if (!self) {
// return;
// }
// UIImage *frame = [animatedProvider animatedImageFrameAtIndex:fetchFrameIndex];
//
// BOOL isAnimating = self.displayLink.isRunning;
// if (isAnimating) {
// SD_LOCK(self->_lock);
// self.frameBuffer[@(fetchFrameIndex)] = frame;
// SD_UNLOCK(self->_lock);
// }
// }];
// [self.fetchQueue addOperation:operation];
// }
- (void)handleFrameChange {
if (self.animationFrameHandler) {
@ -386,9 +311,17 @@
}
#pragma mark - Util
- (void)calculateMaxBufferCount {
NSUInteger bytes = CGImageGetBytesPerRow(self.currentFrame.CGImage) * CGImageGetHeight(self.currentFrame.CGImage);
if (bytes == 0) bytes = 1024;
- (void)calculateMaxBufferCountWithFrame:(nonnull UIImage *)frame {
NSUInteger bytes = self.currentFrameBytes;
if (bytes == 0) {
bytes = CGImageGetBytesPerRow(frame.CGImage) * CGImageGetHeight(frame.CGImage);
if (bytes == 0) {
bytes = 1024;
} else {
// Cache since most animated image each frame bytes is the same
self.currentFrameBytes = bytes;
}
}
NSUInteger max = 0;
if (self.maxBufferSize > 0) {
@ -406,6 +339,7 @@
maxBufferCount = 1;
}
printf("current maxBufferCount: %lu\n", (unsigned long)maxBufferCount);
self.framePool.maxBufferCount = maxBufferCount;
}

View File

@ -33,6 +33,7 @@
return providerFramePoolMap;
}
#pragma mark - Life Cycle
- (instancetype)init {
self = [super init];
if (self) {
@ -40,10 +41,23 @@
_fetchQueue = [[NSOperationQueue alloc] init];
_fetchQueue.maxConcurrentOperationCount = 1;
_fetchQueue.name = @"com.hackemist.SDImageFramePool.fetchQueue";
#if SD_UIKIT
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
}
return self;
}
- (void)dealloc {
#if SD_UIKIT
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
}
- (void)didReceiveMemoryWarning:(NSNotification *)notification {
[self removeAllFrames];
}
+ (instancetype)registerProvider:(id<SDAnimatedImageProvider>)provider {
SDImageFramePool *framePool = [self.providerFramePoolMap objectForKey:provider];
if (!framePool) {
@ -71,7 +85,9 @@
NSUInteger frameCount = self.frameBuffer.count;
if (frameCount > self.maxBufferCount) {
// Remove the frame buffer if need
self.frameBuffer[@(index)] = nil;
// TODO, use LRU or better algorithm to detect which frames to clear
self.frameBuffer[@(index - 1)] = nil;
self.frameBuffer[@(index + 1)] = nil;
}
}