Việc áp dụng các bộ lọc tuyệt đẹp cho video đòi hỏi các phương pháp khác nhau so với việc áp dụng chúng cho các tệp trong iOS. Hãy cũng tìm hiểu cách làm nào !

Sử dụng bộ lọc và hiệu ứng vào Video Stream đòi hỏi các phương thức khác nhau so với việc áp dụng chúng cho các tệp. Trong hướng dẫn này, chúng ta sẽ thấy cách áp dụng hiệu ứng và bộ lọc cho Video Stream theo thời gian thực. Code trong hướng dẫn này được biên dịch với Xcode 13.
Giống như hầu hết framework AVFoundation, máy ảo trên Xcode không phải là nền tảng tốt nhất để chạy thử. Hãy kiểm tra trên một thiết bị thực tế. Một dự án với mã hỗ trợ hướng dẫn này có thể được tìm thấy trên GitHub.

Video Stream hoặc các tệp.

Đối với các tệp video được lưu trữ trên thiết bị, chúng ta có thể sử dụng AVMutableVideoComposition để áp dụng bộ lọc. Khi phát video từ xa, cách này sẽ không hoạt động. Các lớp AVMutuableVideoComposition không được thiết kế để hoạt động theo thời gian thực với luồng. Apple cung cấp một cách để trích xuất các pixel từ luồng theo các khoảng thời gian đều đặn. Bạn có thể thao tác các pixel và sau đó hiển thị chúng lên màn hình. Một đối tượng AVPlayerItemVideoOutput cho phép truy cập vào các pixel tạo thành một khung video. Apple cũng cung cấp một đối tượng CADisplayLink để đảm bảo chúng ta đang trích xuất các pixel đúng vào thời điểm đúng. Liên kết hiển thị là một bộ đếm giờ chuyên dụng sẽ kích hoạt đồng bộ với tốc độ vẽ lại của màn hình hiện tại.

Vì vậy, để lọc một luồng video, hãy làm cách sau:

  • Thiết lập AVPlayer như bình thường và gán URL của luồng
  • Gắn một đối tượng AVPlayerItemVideoOutput vào luồng
  • Gắn kết một đối tượng CADisplayLink vào vòng lặp chạy
  • Trích xuất bộ đệm pixel hiện tại từ AVPlayerItem mỗi khi màn hình vẽ lại
  • Chuyển đổi bộ đệm pixel thành một đối tượng CoreImage
  • Áp dụng bộ lọc
  • Hiển thị hình ảnh

Thiết lập AVPlayer

Chúng ta sẽ không sử dụng AVPlayerLayer hoặc AVPlayerViewController để hiển thị video, nhưng AVPlayer đóng một vai trò trung tâm. AVPlayer giữ video đồng bộ với âm thanh, xử lý giải mã bất kỳ định dạng nào mà luồng sử dụng, kiểm soát đệm, vvv . Cách cài đặt cơ bản giống như với bất kỳ dự án nào khác:

//tạo trình phát
let videoItem = AVPlayerItem(url: streamURL)
self.player = AVPlayer(playerItem: videoItem)

URL có thể chỉ đến một dữ liệu cục bộ hoặc một luồng

Sử dụng AVPlayerItemVideoOutput

AVPlayerItemVideoOutput cho phép chúng ta truy vấn AVPlayerItem cho bộ đệm pixel (một màn hình đầy đủ các pixel) vào bất kỳ thời điểm nào. Chúng ta có thể chỉ định các định dạng pixel khác nhau và các tùy chọn khác cho đầu ra. Trong hướng dẫn này, Chúng ta sẽ tạo đối tượng với một sự khởi tạo đơn giản sử dụng các định dạng mặc định.

let playerItemVideoOutput = AVPlayerItemVideoOutput()

Sau này, chúng ta sẽ thêm đầu ra video này vào mục phát video của chúng ta. Sau đó, chúng ta có thể yêu cầu nó tạo các bộ đệm pixel theo nhu cầu. Trong hướng dẫn này, chúng ta sẽ yêu cầu bộ đệm pixel của frame hiện tại. Tuy nhiên, chúng ta có thể yêu cầu frame cho bất kỳ mốc thời gian nào.

Tạo liên kết hiển thị Lớp CADisplayLink

Class CADisplayLink là một NSTimer chuyên dụng đồng bộ hóa chính nó với tốc độ làm mới màn hình của màn hình. Điều này đảm bảo rằng bộ đệm pixel chúng ta trích xuất sẽ từ thời gian chính xác của Video Stream của chúng ta. Nó sẽ được hiển thị vào thời điểm chính xác khi màn hình được làm mới. Đồng bộ hóa NSTimer tiêu chuẩn với tốc độ làm mới màn hình luôn gặp phải vấn đề. Với các màn hình có tốc độ làm mới đa dạng mà Apple sản xuất, nó trở nên bất khả thi. Tuy nhiên, CADisplayLink điều chỉnh tốc độ của nó khi tốc độ làm mới màn hình thay đổi. Apple giải thích cách hoạt động này trong Phiên WWDC 2021. Khi chúng ta tạo liên kết hiển thị, chúng ta cung cấp tên của một hàm để gọi mỗi khi nó thực thi. Một code mẫu để tạo liên kết có thể trông như sau:

lazy var displayLink: CADisplayLink = CADisplayLink(target: self, 
                                                  selector: #selector(displayLinkFired(link:)))

ở đoạn mã trên, một hàm được gọi là displayLinkFired(link: CADisplayLink) trích xuất bộ đệm điểm ảnh từ AVPlayerItem.

Bắt đầu trình phát

Apple lưu ý rằng việc tải một tệp video mất một lượng thời gian đáng kể. Nó có thể mất thời gian hơn nữa khi tệp video đang được phát trực tuyến qua mạng. AVFoundation cho phép chương trình của chúng ta tiếp tục thực thi trong khi các bước thiết lập và đệm ban đầu đang diễn ra ở nền. Bạn có thể gặp phải vấn đề nếu chúng ta tải một video vào AVFoundation và sau đó ngay lập tức cố gắng làm việc với nó. AVFoundation có thể không hiển thị một thông báo lỗi hoặc trạng thái, nhưng nó cũng sẽ không hoạt động như mong đợi.
Bước quan trọng nhất trong quá trình này là sử dụng một observer(quan sát viên) để chờ cho đến khi AVPlayerItem có trạng thái .readyToPlay trước khi gắn vào đầu ra và hiển thị các đối tượng liên kết. Trong ví dụ dưới đây, statusOberserver, player, playerItemVideoOutputdisplayLink đều được khai báo ở cấp độ lớp.

//khởi tạo trình phát
let videoItem = AVPlayerItem(url: streamURL!)
self.player = AVPlayer(playerItem: videoItem) //1
//*lưu ý* chỉ thêm liên kết hiển thị và đầu ra sau khi đã sẵn sàng phát
self.statusObserver = videoItem.observe(\.status, //2
                       options: [.new, .old],
                 changeHandler: { playerItem, change in
    if playerItem.status == .readyToPlay { //3
      playerItem.add(self.playerItemVideoOutput)
      self.displayLink.add(to: .main, forMode: .common)
      self.player?.play() //4
    }
})

Chúng cần tồn tại trong suốt thời gian video đang được sử dụng. Dưới đây là những điều cần lưu ý về mã trên

  1. Việc tạo trình phát sẽ mất một lượng thời gian lớn, nhưng việc thực thi chương trình sẽ không dừng lại và chờ đợi
  2. NSKeyValueObservation là cách swift định nghĩa các observer(quan sát viên) câu lệnh sẽ thực thi mỗi khi thuộc tính videoItem.status thay đổi giá trị
  3. Kiểm tra xem thuộc tính .status có phải là .readyToPlay không ?
  4. Sau khi thêm liên kết hiển thị và các đối tượng đầu ra video bắt đầu phát video

Trích xuất bộ đệm Pixel

Một khi video bắt đầu phát, hàm liên kết hiển thị được gọi cho mỗi làm mới màn hình. Dưới đây là một ví dụ về cách trích xuất bộ đệm pixel và hiển thị nó lên UIImageView.

@objc func displayLinkFired(link: CADisplayLink) { //1
  let currentTime = playerItemVideoOutput.itemTime(forHostTime: CACurrentMediaTime())
  if playerItemVideoOutput.hasNewPixelBuffer(forItemTime: currentTime) { //2
    if let buffer = playerItemVideoOutput.copyPixelBuffer(forItemTime: currentTime, itemTimeForDisplay: nil) {
      let frameImage = CIImage(cvImageBuffer: buffer) //3
      //4
      let pixelate = CIFilter(name: "CIPixellate")!
      pixelate.setValue(frameImage, forKey: kCIInputImageKey)
      pixelate.setValue(self.filterSlider.value, forKey: kCIInputScaleKey)
      pixelate.setValue(CIVector(x: frameImage.extent.midX, y: frameImage.extent.midY), forKey: kCIInputCenterKey)
     let newFrame = pixelate.outputImage!.cropped(to: frameImage.extent)
     self.videoView.image = UIImage(ciImage: newFrame) //5
    }
  }
}

Dưới đây là những gì câu lệnh thực thi:

  1. Đánh dấu func với @objc để nó hoạt động với bộ chọn liên kết hiển thị
  2. Hỏi xem có bộ đệm pixel mới để hiển thị không.
  3. Tùy thuộc vào tốc độ làm mới của màn hình và tốc độ khung hình của video, sẽ không luôn luôn có bộ đệm mới.
  4. Sau khi trích xuất bộ đệm pixel, chuyển đổi nó thành một đối tượng CoreImage
  5. Áp dụng bất kỳ bộ lọc nào. Ví dụ này áp dụng bộ lọc CIPixellate. Nó sử dụng một thanh trượt để cho phép người dùng thay đổi cường độ khi video đang phát
  6. Chuyển đổi hình ảnh đã lọc thành UIImage và gán nó cho UIImageView
    Bây giờ khi tỷ lệ pixellate thay đổi, hình ảnh sẽ được lọc nhưng video sẽ phát mượt mà.

Nguồn tham khảo : how to add a  filter to a video stream in ios