Câu hỏi hay.
chút nhanh của nền cho bất cứ ai khác đọc bài viết này:
Mục tiêu ở đây là để giảm thiểu độ trễ màn hình, ví dụ: thời gian giữa khi ứng dụng làm cho một khung và khi bảng điều khiển màn hình sáng lên các pixel. Nếu bạn chỉ cần ném nội dung vào màn hình, điều đó không quan trọng, bởi vì người dùng không thể nói sự khác biệt. Tuy nhiên, nếu bạn đang phản hồi cảm biến đầu vào, mọi khung thời gian chờ sẽ làm cho ứng dụng của bạn cảm thấy ít phản hồi hơn một chút.
Sự cố tương tự với đồng bộ hóa A/V, nơi bạn cần âm thanh được liên kết với khung để đi ra loa khi khung video đang được hiển thị trên màn hình. Trong trường hợp đó, độ trễ tổng thể không quan trọng miễn là nó luôn nhất quán về cả đầu ra âm thanh và video. Điều này phải đối mặt với các vấn đề rất giống nhau, bởi vì bạn sẽ mất đồng bộ nếu các quầy hàng SurfaceFlinger và video của bạn luôn được hiển thị một khung sau đó.
SurfaceFlinger chạy ở mức ưu tiên cao và ít hoạt động tương đối, vì vậy không có khả năng bỏ lỡ nhịp đập ... nhưng điều đó có thể xảy ra. Ngoài ra, nó được tổng hợp các khung từ nhiều nguồn, một số trong đó sử dụng các hàng rào để báo hiệu hoàn thành không đồng bộ. Nếu một khung video on-time được kết hợp với đầu ra OpenGL và kết xuất GLES chưa hoàn thành khi thời hạn kết thúc, toàn bộ bố cục sẽ được hoãn lại đến VSYNC tiếp theo.
Mong muốn giảm thiểu độ trễ đủ mạnh để bản phát hành Android KitKat (4.4) giới thiệu tính năng "DispSync" trong SurfaceFlinger, giúp giảm nửa thời gian trễ của độ trễ hai khung thông thường. (Điều này được đề cập ngắn gọn trong tài liệu kiến trúc đồ họa, nhưng nó không được sử dụng rộng rãi.)
Vì vậy, đó là tình huống. Trong quá khứ, vấn đề này ít hơn đối với video vì video 30fps cập nhật mọi khung hình khác. Các trục trặc tự làm việc tự nhiên bởi vì chúng tôi không cố giữ hàng đợi đầy. Chúng tôi bắt đầu thấy video 48Hz và 60Hz, vì vậy điều này quan trọng hơn.
Câu hỏi đặt ra là, làm cách nào để chúng tôi phát hiện xem khung hình mà chúng tôi gửi cho SurfaceFlinger có được hiển thị sớm nhất có thể hay đang chi tiêu thêm khung chờ đợi sau bộ đệm mà chúng tôi đã gửi trước đó?
Phần đầu tiên của câu trả lời là: bạn không thể. Không có truy vấn trạng thái hoặc gọi lại trên SurfaceFlinger sẽ cho bạn biết trạng thái của nó là gì. Về lý thuyết, bạn có thể tự truy vấn BufferQueue, nhưng điều đó không nhất thiết phải cho bạn biết những gì bạn cần biết.
Vấn đề với truy vấn và gọi lại là họ không thể cho bạn biết trạng thái là, chỉ trạng thái là. Vào thời điểm ứng dụng nhận được thông tin và hành động trên đó, tình hình có thể hoàn toàn khác. Ứng dụng sẽ chạy ở mức độ ưu tiên bình thường, vì vậy ứng dụng sẽ bị chậm trễ.
Đối với đồng bộ hóa A/V, điều này hơi phức tạp hơn, vì ứng dụng không thể biết các đặc tính hiển thị. Ví dụ: một số màn hình có "bảng thông minh" có bộ nhớ được tích hợp sẵn. (Nếu những gì trên màn hình không cập nhật thường xuyên, bạn có thể tiết kiệm rất nhiều năng lượng bằng cách không có bảng điều khiển quét các điểm ảnh trên xe buýt bộ nhớ 60x mỗi giây.) Đây có thể thêm một khung thời gian chờ bổ sung phải được tính.
Giải pháp mà Android đang hướng tới để đồng bộ hóa A/V là yêu cầu ứng dụng thông báo cho SurfaceFlinger khi nó muốn khung được hiển thị. Nếu SurfaceFlinger bỏ lỡ thời hạn, nó sẽ giảm khung. Điều này đã được thêm vào thực nghiệm trong 4.4, mặc dù nó không thực sự có ý định được sử dụng cho đến khi phát hành tiếp theo (nó sẽ hoạt động tốt trong "L preview", mặc dù tôi không biết nếu nó bao gồm tất cả các phần cần thiết để sử dụng nó đầy đủ).
Cách ứng dụng sử dụng cách này là gọi số máy lẻ eglPresentationTimeANDROID()
trước eglSwapBuffers()
. Đối số cho hàm là thời gian trình bày mong muốn, tính bằng nano giây, sử dụng cùng một thời gian như biên đạo múa (cụ thể là Linux CLOCK_MONOTONIC
). Vì vậy, đối với mỗi khung, bạn lấy dấu thời gian bạn nhận được từ Biên đạo múa, thêm số khung hình mong muốn nhân với tỷ lệ làm mới gần đúng (bạn có thể nhận được bằng cách truy vấn đối tượng Hiển thị - xem MiscUtils#getDisplayRefreshNsec()) và chuyển nó đến EGL. Khi bạn trao đổi bộ đệm, thời gian trình bày mong muốn được chuyển cùng với bộ đệm.
Nhớ lại rằng SurfaceFlinger tỉnh dậy một lần cho mỗi VSYNC, xem bộ sưu tập các bộ đệm đang chờ xử lý và cung cấp một bộ phần cứng hiển thị thông qua phần cứng Composer. Nếu bạn yêu cầu hiển thị tại thời điểm T và SurfaceFlinger tin rằng một khung được chuyển tới phần cứng hiển thị sẽ hiển thị tại thời điểm T-1 trở về trước, khung sẽ được giữ lại (và khung hình trước được hiển thị lại). Nếu khung sẽ xuất hiện tại thời điểm T, nó sẽ được gửi đến màn hình. Nếu khung sẽ xuất hiện tại thời điểm T + 1 hoặc sau đó (tức là nó sẽ bỏ lỡ thời hạn), và có một khung khác phía sau nó trong hàng đợi được lên lịch sau (ví dụ: khung dành cho thời gian T + 1) , sau đó khung dành cho thời gian T sẽ bị loại bỏ.
Giải pháp không hoàn toàn phù hợp với vấn đề của bạn. Đối với đồng bộ hóa A/V, bạn cần độ trễ không đổi, không phải độ trễ tối thiểu. Nếu bạn nhìn vào hoạt động "scheduled swap" của Grafika, bạn có thể tìm thấy một số mã sử dụng eglPresentationTimeANDROID()
theo cách tương tự như những gì trình phát video sẽ làm. (Trong trạng thái hiện tại, nó ít hơn một "bộ tạo âm" để tạo ra đầu ra systrace, nhưng các phần cơ bản là ở đó.) Chiến lược có để render vài khung hình phía trước, vì vậy SurfaceFlinger không bao giờ chạy khô, nhưng đó chính xác là sai cho bạn ứng dụng.
Cơ chế hiển thị thời gian thực hiện, tuy nhiên, cung cấp cách thả khung hình thay vì cho phép chúng sao lưu. Nếu bạn tình cờ biết rằng có hai khung thời gian trễ giữa thời gian được Choreographer báo cáo và thời điểm khung của bạn có thể được hiển thị, bạn có thể sử dụng tính năng này để đảm bảo rằng khung sẽ bị xóa thay vì xếp hàng nếu chúng quá xa quá khứ. Hoạt động Grafika cho phép bạn thiết lập tốc độ khung hình và độ trễ được yêu cầu, và sau đó xem kết quả trong systrace.
Sẽ rất hữu ích nếu một ứng dụng biết có bao nhiêu khung thời gian SurfaceFlinger thực sự có, nhưng không có truy vấn cho điều đó. Tuy nhiên, trừ khi bạn đang làm việc trên đồng bộ hóa A/V, tất cả những gì bạn thực sự quan tâm là giảm thiểu độ trễ SurfaceFlinger. hợp lý an toàn để giả sử hai khung hình trên 4.3+. Nếu nó không phải là hai khung hình, bạn có thể có hiệu suất tối ưu, nhưng hiệu ứng ròng sẽ không tồi tệ hơn bạn sẽ nhận được nếu bạn không đặt thời gian trình bày ở tất cả.
Bạn có thể thử đặt thời gian trình bày mong muốn bằng dấu thời gian Choreographer; dấu thời gian trong quá khứ gần đây có nghĩa là "hiển thị CÀNG SỚM CÀNG TỐT". Điều này đảm bảo độ trễ tối thiểu, nhưng có thể phản tác dụng về độ mượt. SurfaceFlinger có độ trễ hai khung vì nó cung cấp mọi thứ trong hệ thống đủ thời gian để hoàn thành công việc. Nếu khối lượng công việc của bạn không đồng đều, bạn sẽ lung lay giữa độ trễ một khung và khung đôi, và đầu ra sẽ trông có vẻ lóng ngóng ở các hiệu ứng chuyển tiếp. (Đây là một mối quan tâm đối với DispSync, làm giảm tổng thời gian xuống 1.5 khung hình.)
Tôi không nhớ khi nào chức năng eglPresentationTimeANDROID()
được thêm vào, nhưng trên bản phát hành cũ, nó phải là không có.
Dòng dưới cùng: cho 'L' và ở mức độ nào đó 4.4, bạn sẽ có thể nhận được hành vi bạn muốn bằng cách sử dụng phần mở rộng EGL với hai khung thời gian chờ. Trên các bản phát hành trước đó, không có sự trợ giúp nào từ hệ thống. Nếu bạn muốn đảm bảo không có bộ đệm theo cách của bạn, bạn có thể cố tình thả một khung hình một cách thường xuyên để cho hàng đợi bộ đệm thoát.
Cập nhật: một cách để tránh xếp hàng đợi khung hình là gọi eglSwapInterval(0)
. Nếu bạn đang gửi đầu ra trực tiếp đến màn hình, cuộc gọi sẽ vô hiệu hóa đồng bộ hóa với VSYNC, hủy giới hạn tốc độ khung hình của ứng dụng. Khi kết xuất thông qua SurfaceFlinger, điều này đặt BufferQueue vào "chế độ không đồng bộ", làm cho nó thả khung nếu chúng được gửi nhanh hơn hệ thống có thể hiển thị chúng.
Lưu ý bạn vẫn còn ba bộ đệm: một bộ đệm đang được hiển thị, một bộ đệm được giữ bởi SurfaceFlinger sẽ được hiển thị ở lần tiếp theo và một ứng dụng đang được ứng dụng vẽ vào.
Đây là một câu hỏi tuyệt vời! –
Câu hỏi hay! –