简单帧数据封包缓存处理

  • 只在极端情况下才落盘(绝大多数帧在内存中,只有内存压力大/阻塞未推流的帧才写入 mmap 文件)。
  • 文件只追加写入(写入不会覆盖),读指针始终向后顺序读取。
  • 文件空间不够就自动扩容(比如翻倍增长)。
  • 支持跨页、缺页自动处理。
  • 只需简单的线程安全(比如用串行队列或简单加锁)。
#import <Foundation/Foundation.h>

typedef NS_ENUM(uint32_t, LFFrameType) {
    LFFrameTypeAudio = 0,
    LFFrameTypeVideo = 1,
};

typedef struct {
    uint32_t magic;      // 帧标识, 如 0xAA55AA55
    uint32_t type;       // 0:音频帧 1:视频帧
    uint32_t length;     // 帧数据长度
    uint64_t timestamp;  // 时间戳
} LFFrameHeader;

@interface LFFrameMmapSimpleCache : NSObject

- (instancetype)initWithPath:(NSString *)path initialSize:(size_t)size;
- (BOOL)writeFrame:(NSData *)frameData type:(LFFrameType)type timestamp:(uint64_t)timestamp;
- (BOOL)readNextFrame:(LFFrameHeader *)outHeader data:(NSData **)outData;
- (void)close;

@end


#import "LFFrameMmapSimpleCache.h"
#import <sys/mman.h>
#import <fcntl.h>
#import <unistd.h>

#define LF_FRAME_MAGIC 0xAA55AA55

@interface LFFrameMmapSimpleCache () {
    int _fd;
    void *_mmapPtr;
    size_t _mmapSize;
    size_t _writeOffset;
    size_t _readOffset;
}
@property (nonatomic, strong) NSString *filePath;
@end

@implementation LFFrameMmapSimpleCache

- (instancetype)initWithPath:(NSString *)path initialSize:(size_t)size {
    self = [super init];
    if (self) {
        _filePath = [path copy];
        _mmapSize = size;
        _writeOffset = 0;
        _readOffset = 0;
        _fd = open([path UTF8String], O_RDWR | O_CREAT, 0666);
        if (_fd < 0) {
            NSLog(@"open file failed");
            return nil;
        }
        ftruncate(_fd, size);
        _mmapPtr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, _fd, 0);
        if (_mmapPtr == MAP_FAILED) {
            NSLog(@"mmap failed");
            close(_fd);
            return nil;
        }
    }
    return self;
}

- (void)dealloc { [self close]; }

- (void)close {
    @synchronized (self) {
        if (_mmapPtr && _mmapPtr != MAP_FAILED) {
            msync(_mmapPtr, _mmapSize, MS_SYNC);
            munmap(_mmapPtr, _mmapSize);
            _mmapPtr = NULL;
        }
        if (_fd > 0) {
            close(_fd);
            _fd = -1;
        }
    }
}

// 扩容映射文件(文件大小翻倍)
- (BOOL)expandFileIfNeeded:(size_t)needMore {
    if (_writeOffset + needMore <= _mmapSize) return YES;
    size_t newSize = _mmapSize;
    while (_writeOffset + needMore > newSize) newSize *= 2;
    if (ftruncate(_fd, newSize) != 0) {
        NSLog(@"ftruncate failed");
        return NO;
    }
    void *newPtr = mremap(_mmapPtr, _mmapSize, newSize, MREMAP_MAYMOVE);
    if (newPtr == MAP_FAILED) {
        NSLog(@"mremap failed");
        return NO;
    }
    _mmapPtr = newPtr;
    _mmapSize = newSize;
    return YES;
}

- (BOOL)writeFrame:(NSData *)frameData type:(LFFrameType)type timestamp:(uint64_t)timestamp {
    @synchronized (self) {
        size_t frameLen = frameData.length;
        size_t totalLen = sizeof(LFFrameHeader) + frameLen;
        if (![self expandFileIfNeeded:totalLen]) return NO;
        LFFrameHeader header = {LF_FRAME_MAGIC, type, (uint32_t)frameLen, timestamp};
        memcpy((Byte *)_mmapPtr + _writeOffset, &header, sizeof(header));
        memcpy((Byte *)_mmapPtr + _writeOffset + sizeof(header), frameData.bytes, frameLen);
        _writeOffset += totalLen;
        return YES;
    }
}

- (BOOL)readNextFrame:(LFFrameHeader *)outHeader data:(NSData **)outData {
    @synchronized (self) {
        if (_readOffset + sizeof(LFFrameHeader) > _writeOffset) return NO;
        LFFrameHeader header = {0};
        memcpy(&header, (Byte *)_mmapPtr + _readOffset, sizeof(header));
        if (header.magic != LF_FRAME_MAGIC) {
            NSLog(@"Frame header corrupted");
            return NO;
        }
        if (_readOffset + sizeof(LFFrameHeader) + header.length > _writeOffset) return NO;
        if (outHeader) *outHeader = header;
        if (outData) {
            NSData *data = [NSData dataWithBytes:(Byte *)_mmapPtr + _readOffset + sizeof(LFFrameHeader) length:header.length];
            *outData = data;
        }
        _readOffset += sizeof(LFFrameHeader) + header.length;
        return YES;
    }
}

@end
#import <Foundation/Foundation.h>
#import "LFFrameMmapSimpleCache.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LFFrameMmapSimpleCache *cache = [[LFFrameMmapSimpleCache alloc] initWithPath:@"/tmp/lf_simple_mmap" initialSize:4096];

        // 写入测试
        for (int i=0; i<100; ++i) {
            NSString *str = [NSString stringWithFormat:@"FrameData-%d", i];
            NSData *data = [str dataUsingEncoding:NSUTF8StringEncoding];
            [cache writeFrame:data type:LFFrameTypeVideo timestamp:1000+i*10];
        }

        // 读取测试
        LFFrameHeader header;
        NSData *frameData;
        int idx = 0;
        while ([cache readNextFrame:&header data:&frameData]) {
            NSString *content = [[NSString alloc] initWithData:frameData encoding:NSUTF8StringEncoding];
            NSLog(@"Read frame %d: ts=%llu, content=%@", idx++, header.timestamp, content);
        }

        [cache close];
    }
    return 0;
}
posted @ 2025-06-27 21:11  CoderWGB  阅读(17)  评论(0)    收藏  举报