TextureView

Lớp TextureView là một đối tượng khung hiển thị kết hợp khung hiển thị với SurfaceTexture.

Kết xuất bằng OpenGL ES

Đối tượng TextureView bao bọc một SurfaceTexture, phản hồi các lệnh gọi lại và nhận các vùng đệm mới. Khi TextureView nhận được các vùng đệm mới, TextureView sẽ đưa ra yêu cầu vô hiệu hoá khung hiển thị và vẽ bằng nội dung của vùng đệm mới nhất làm nguồn dữ liệu, kết xuất ở bất cứ nơi nào và theo bất cứ cách nào mà trạng thái khung hiển thị cho biết.

OpenGL ES (GLES) có thể kết xuất trên TextureView bằng cách truyền SurfaceTexture đến lệnh gọi tạo EGL, nhưng điều này sẽ gây ra vấn đề. Khi GLES kết xuất trên TextureView, các nhà sản xuất và người tiêu dùng BufferQueue nằm trong cùng một luồng, điều này có thể khiến lệnh gọi trao đổi vùng đệm bị tạm dừng hoặc không thành công. Ví dụ: nếu một nhà sản xuất gửi một số bộ đệm liên tiếp từ luồng giao diện người dùng, thì lệnh gọi hoán đổi bộ đệm EGL cần phải loại bỏ một bộ đệm khỏi BufferQueue. Tuy nhiên, vì ứng dụng tiêu dùng và ứng dụng sản xuất nằm trên cùng một luồng, nên sẽ không có bộ đệm nào và lệnh gọi trao đổi sẽ bị treo hoặc không thành công.

Để ngăn tình trạng hoán đổi vùng đệm bị ngưng trệ, BufferQueue luôn cần có một vùng đệm để loại bỏ khỏi hàng đợi. Để làm như vậy, BufferQueue sẽ loại bỏ nội dung của bộ đệm đã nhận trước đó khi một bộ đệm mới được đưa vào hàng đợi. Nó cũng đặt ra các hạn chế về số lượng vùng đệm tối thiểu và tối đa để ngăn người dùng sử dụng tất cả các vùng đệm cùng một lúc.

Chọn SurfaceView hoặc TextureView

SurfaceView và TextureView đóng vai trò tương tự nhau và đều là thành phần của hệ phân cấp khung hiển thị. Tuy nhiên, SurfaceView và TextureView có các cách triển khai khác nhau. SurfaceView có các tham số giống như các khung hiển thị khác, nhưng nội dung SurfaceView sẽ trong suốt khi được kết xuất.

TextureView có khả năng xử lý độ trong suốt và xoay tốt hơn SurfaceView, nhưng SurfaceView có lợi thế về hiệu suất khi kết hợp các phần tử trên giao diện người dùng được xếp lớp trên video. Khi một ứng dụng hiển thị bằng SurfaceView, SurfaceView sẽ cung cấp cho ứng dụng một lớp thành phần riêng biệt. SurfaceFlinger tạo lớp riêng biệt dưới dạng lớp phủ phần cứng nếu thiết bị hỗ trợ. Khi một ứng dụng kết xuất bằng TextureView, bộ công cụ giao diện người dùng sẽ kết hợp nội dung của TextureView vào hệ phân cấp khung hiển thị bằng GPU. Nội dung cập nhật có thể khiến các phần tử khác trong khung hiển thị vẽ lại, ví dụ: nếu các khung hiển thị khác nằm phía trên TextureView. Sau khi quá trình kết xuất khung hiển thị hoàn tất, SurfaceFlinger sẽ kết hợp lớp giao diện người dùng của ứng dụng và tất cả các lớp khác, sao cho mọi pixel hiển thị đều được kết hợp hai lần.

Nghiên cứu điển hình: Video trên Play của Grafika

Video trên Play của Grafika bao gồm một cặp trình phát video, một trình phát được triển khai bằng TextureView và một trình phát được triển khai bằng SurfaceView. Phần giải mã video của hoạt động này sẽ gửi các khung hình từ MediaCodec đến một vùng cho cả TextureView và SurfaceView. Điểm khác biệt lớn nhất giữa các cách triển khai là các bước cần thiết để trình bày tỷ lệ khung hình chính xác.

Để điều chỉnh tỷ lệ SurfaceView, bạn cần triển khai FrameLayout theo cách tuỳ chỉnh. WindowManager cần gửi một vị trí cửa sổ mới và các giá trị kích thước mới đến SurfaceFlinger. Để điều chỉnh tỷ lệ SurfaceTexture của TextureView, bạn cần định cấu hình ma trận biến đổi bằng TextureView#setTransform().

Sau khi trình bày đúng tỷ lệ khung hình, cả hai cách triển khai đều tuân theo cùng một mẫu. Khi SurfaceView/TextureView tạo bề mặt, mã ứng dụng sẽ cho phép phát. Khi người dùng nhấn vào phát, thao tác này sẽ bắt đầu một luồng giải mã video, với bề mặt là mục tiêu đầu ra. Sau đó, mã ứng dụng sẽ không làm gì cả – quá trình kết hợp và hiển thị do SurfaceFlinger (đối với SurfaceView) hoặc TextureView xử lý.

Nghiên cứu điển hình: Giải mã kép của Grafika

Double Decode của Grafika minh hoạ cách thao tác SurfaceTexture bên trong TextureView.

Tính năng Giải mã kép của Grafika sử dụng một cặp đối tượng TextureView để hiển thị 2 video phát song song, mô phỏng một ứng dụng hội nghị truyền hình. Khi hướng màn hình thay đổi và hoạt động khởi động lại, các bộ giải mã MediaCodec sẽ không dừng, mô phỏng quá trình phát một luồng video theo thời gian thực. Để cải thiện hiệu quả, ứng dụng nên duy trì hoạt động của dịch vụ. Bề mặt này là một đối tượng xử lý cho giao diện nhà sản xuất trong BufferQueue của SurfaceTexture. Vì TextureView quản lý SurfaceTexture, nên ứng dụng cần duy trì SurfaceTexture để duy trì bề mặt.

Để duy trì SurfaceTexture, Double Decode của Grafika sẽ lấy các tham chiếu đến SurfaceTexture từ các đối tượng TextureView và lưu chúng vào một trường tĩnh. Sau đó, Double Decode của Grafika sẽ trả về false từ TextureView.SurfaceTextureListener#onSurfaceTextureDestroyed() để ngăn việc huỷ SurfaceTexture. Sau đó, TextureView sẽ truyền SurfaceTexture đến onSurfaceTextureDestroyed(). SurfaceTexture này có thể được duy trì trong suốt quá trình thay đổi cấu hình hoạt động. Ứng dụng sẽ truyền SurfaceTexture đến TextureView mới thông qua setSurfaceTexture().

Các luồng riêng biệt điều khiển từng bộ giải mã video. Mediaserver gửi các vùng đệm có đầu ra đã giải mã đến SurfaceTexture, người dùng BufferQueue. Các đối tượng TextureView thực hiện kết xuất và chạy trên luồng giao diện người dùng.

Việc triển khai tính năng Giải mã kép của Grafika bằng SurfaceView sẽ khó hơn so với việc triển khai bằng TextureView vì các đối tượng SurfaceView sẽ huỷ các nền tảng trong quá trình thay đổi hướng. Ngoài ra, việc sử dụng các đối tượng SurfaceView sẽ thêm 2 lớp, điều này không lý tưởng do những hạn chế về số lượng lớp phủ có trên phần cứng.