You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Xcode模拟器中视频帧仅显示最后一帧的技术问题求助

问题原因
  • 主线程RunLoop阻塞:viewDidLoad运行在主线程,你的while循环会持续占用主线程资源,而iOS的UI更新依赖主线程RunLoop的周期性刷新。循环执行期间,RunLoop没有机会处理UI绘制任务,直到循环完全结束才会一次性刷新界面,因此你只能看到最后一帧。
  • 冗余的子视图添加:循环内重复调用[self.view addSubview:imageView_]完全没必要,应该在循环启动前就把imageView_添加到父视图上。
解决方案

方案1:预读取帧+GCD延迟展示

先把需要的30帧全部读取并转换成UIImage,再通过GCD给每帧设置递增的延迟,让主线程RunLoop有时间处理UI刷新:

- (void)viewDidLoad {
    [super viewDidLoad];

    // 初始化并添加ImageView到视图(仅需一次)
    imageView_ = [[UIImageView alloc] initWithFrame:CGRectMake(100, 100, 190, 190)];
    imageView_.contentMode = UIViewContentModeScaleAspectFill;
    [self.view addSubview:imageView_];

    string data = "eyesample.mp4";
    cv::VideoCapture original_video_data;
    original_video_data.open(data);
    
    int totalFrames = original_video_data.get(cv::CAP_PROP_FRAME_COUNT);
    cout << "Total frames: " << totalFrames << endl;

    // 预读取30帧到数组
    NSMutableArray<UIImage *> *framesArray = [NSMutableArray array];
    int count = 0;
    cv::Mat frame;
    while (count < 30) {
        original_video_data >> frame;
        if (frame.empty()) break;
        
        UIImage *uiFrame = MatToUIImage(frame);
        [framesArray addObject:uiFrame];
        
        cout << "Current frame index: " << original_video_data.get(cv::CAP_PROP_POS_FRAMES) << endl;
        count++;
    }
    original_video_data.release();

    // 分帧展示(每0.1秒切换一帧,可根据需求调整间隔)
    for (int i = 0; i < framesArray.count; i++) {
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(i * 0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            imageView_.image = framesArray[i];
        });
    }
}

方案2:使用CADisplayLink(屏幕刷新同步)

CADisplayLink会和屏幕刷新频率同步,适合更精准的视频帧展示:

@interface ViewController () {
    UIImageView *imageView_;
    cv::VideoCapture original_video_data;
    int displayedFrameCount;
    CADisplayLink *frameDisplayLink;
}
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    // 初始化ImageView并添加到视图
    imageView_ = [[UIImageView alloc] initWithFrame:CGRectMake(100, 100, 190, 190)];
    imageView_.contentMode = UIViewContentModeScaleAspectFill;
    [self.view addSubview:imageView_];

    // 初始化视频捕获
    string data = "eyesample.mp4";
    original_video_data.open(data);
    
    int totalFrames = original_video_data.get(cv::CAP_PROP_FRAME_COUNT);
    cout << "Total frames: " << totalFrames << endl;

    // 设置CADisplayLink
    displayedFrameCount = 0;
    frameDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateDisplayedFrame)];
    [frameDisplayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}

- (void)updateDisplayedFrame {
    // 达到30帧或视频结束时停止
    if (displayedFrameCount >= 30) {
        [frameDisplayLink invalidate];
        frameDisplayLink = nil;
        original_video_data.release();
        return;
    }

    cv::Mat frame;
    original_video_data >> frame;
    if (frame.empty()) {
        [frameDisplayLink invalidate];
        frameDisplayLink = nil;
        original_video_data.release();
        return;
    }

    // 更新UI
    imageView_.image = MatToUIImage(frame);
    cout << "Current frame index: " << original_video_data.get(cv::CAP_PROP_POS_FRAMES) << endl;
    displayedFrameCount++;
}

@end
核心注意事项
  • 必须给主线程RunLoop留出UI刷新的时间,绝对不能在主线程的同步循环中连续更新UI。
  • cv::VideoCapture不是线程安全的对象,尽量在主线程操作它,或者提前把需要的帧全部读取到数组中。
  • 初始化视图时只添加一次UIImageView到父视图,避免冗余操作。

内容的提问来源于stack exchange,提问作者Biniyam Kahsay

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 19:10:32