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
相关产品推荐
相关产品推荐

