2016-06-02 17 views
7

Tôi cần thực hiện quy trình chỉ đọc trên CPU trên dữ liệu máy ảnh trực tiếp (chỉ từ mặt phẳng Y) sau đó hiển thị trên GPU. Khung không được hiển thị cho đến khi quá trình xử lý hoàn tất (vì vậy tôi không phải lúc nào cũng muốn hiển thị khung hình mới nhất từ ​​máy ảnh, chỉ khung mới nhất mà bên CPU đã xử lý xong). Rendering được tách rời khỏi quá trình xử lý máy ảnh và nhắm tới 60 FPS ngay cả khi các khung máy ảnh đến với tốc độ thấp hơn.Đường ống xử lý và hiển thị máy ảnh không sao chép trên Android

Có một câu hỏi liên quan nhưng cao cấp hơn tại địa chỉ: Lowest overhead camera to CPU to GPU approach on android

Để mô tả các thiết lập hiện tại trong một chi tiết hơn chút: chúng ta có một vùng đệm dữ liệu ứng dụng-side cho dữ liệu máy ảnh nơi vùng đệm là một trong hai "tự do", "đang hiển thị" hoặc "đang chờ hiển thị". Khi một khung mới từ máy ảnh đến, chúng tôi lấy một bộ đệm miễn phí, lưu trữ khung (hoặc tham chiếu đến nó nếu dữ liệu thực tế nằm trong một số vùng đệm do hệ thống cung cấp), xử lý và lưu trữ kết quả trong bộ đệm, sau đó đặt bộ đệm "đang chờ hiển thị". Trong chuỗi trình kết xuất nếu có bất kỳ bộ đệm nào "đang chờ hiển thị" ở đầu vòng lặp kết xuất, chúng tôi sẽ chốt nó là "hiển thị" thay thế, hiển thị máy ảnh và hiển thị nội dung khác bằng thông tin được xử lý được tính toán từ cùng một khung máy ảnh.

Nhờ phản hồi của @ fadden về câu hỏi được liên kết ở trên, giờ đây tôi hiểu tính năng "đầu ra song song" của android camera2 chia sẻ bộ đệm giữa các hàng đợi đầu ra khác nhau, vì vậy không nên liên quan đến bất kỳ bản sao nào trên dữ liệu trên Android hiện đại.

Trong một nhận xét có một gợi ý rằng tôi có thể chốt các đầu ra SurfaceTexture và ImageReader cùng một lúc và chỉ "ngồi trên bộ đệm" cho đến khi quá trình xử lý hoàn tất. Thật không may, tôi không nghĩ rằng điều đó có thể áp dụng trong trường hợp của tôi do kết xuất được tách rời mà chúng tôi vẫn muốn lái xe ở tốc độ 60 FPS và vẫn cần truy cập vào khung hình trước đó trong khi khung mới đang được xử lý để đảm bảo mọi thứ không Không đồng bộ.

Một giải pháp mà bạn nghĩ đến là có nhiều SurfaceTextures - một trong mỗi bộ đệm bên ứng dụng của chúng tôi (chúng tôi hiện đang sử dụng 3). Với lược đồ đó khi chúng tôi nhận được một khung máy ảnh mới, chúng tôi sẽ có được bộ đệm miễn phí từ hồ bơi ứng dụng của chúng tôi. Sau đó, chúng tôi sẽ gọi acquireLatestImage() trên ImageReader để lấy dữ liệu để xử lý và gọi updateTexImage() trên SurfaceTexture trong bộ đệm miễn phí. Tại thời điểm hiển thị, chúng tôi chỉ cần đảm bảo rằng SufaceTexture từ bộ đệm "trong hiển thị" là bộ đệm được gắn với GL và mọi thứ phải đồng bộ phần lớn thời gian (như @fadden đã nhận xét có một cuộc gọi giữa số updateTexImage()acquireLatestImage() nhưng cửa sổ thời gian đó phải đủ nhỏ để làm cho nó trở nên hiếm hoi, và có lẽ là dễ đọc và có thể sửa được bằng cách sử dụng dấu thời gian trong bộ đệm).

Tôi lưu ý trong tài liệu updateTexImage() chỉ có thể được gọi khi SurfaceTexture bị ràng buộc với ngữ cảnh GL, điều này cho thấy tôi sẽ cần ngữ cảnh GL trong chuỗi xử lý máy ảnh để chủ đề camera có thể thực hiện updateTexImage() trên SurfaceTexture trong bộ đệm "tự do" trong khi chuỗi hiển thị vẫn có thể hiển thị từ SurfaceTexture từ bộ đệm "trong hiển thị".

Vì vậy, để những câu hỏi:

  1. Điều này có vẻ giống như một cách tiếp cận hợp lý?
  2. SurfaceTextures cơ bản là một trình bao bọc ánh sáng xung quanh vùng đệm được chia sẻ, hay chúng tiêu thụ một số tài nguyên phần cứng hạn chế và nên được sử dụng một cách tiết kiệm?
  3. Các cuộc gọi SurfaceTexture có đủ rẻ để sử dụng nhiều tính năng vẫn sẽ là một chiến thắng lớn so với việc sao chép dữ liệu không?
  4. Kế hoạch có hai chủ đề với các ngữ cảnh GL riêng biệt với một SurfaceTexture khác nhau bị ràng buộc trong mỗi khả năng làm việc hay tôi đang yêu cầu một thế giới của các trình điều khiển đau và lỗi?

Có vẻ đủ hứa hẹn rằng tôi sẽ bỏ nó; nhưng nghĩ rằng nó có giá trị yêu cầu ở đây trong trường hợp bất cứ ai (về cơ bản @ fadden!) biết về bất kỳ chi tiết nội bộ mà tôi đã bỏ qua mà sẽ làm cho một ý tưởng tồi.

Trả lời

7

Câu hỏi thú vị.

nền Stuff

Có nhiều luồng với bối cảnh độc lập là rất phổ biến. Mỗi ứng dụng sử dụng hiển thị Chế độ xem tăng tốc phần cứng đều có ngữ cảnh GLES trên chủ đề chính, vì vậy bất kỳ ứng dụng nào sử dụng GLSurfaceView (hoặc cuộn EGL của riêng mình bằng SurfaceView hoặc TextureView và chuỗi hiển thị độc lập) đang tích cực sử dụng nhiều ngữ cảnh.

Mỗi TextureView đều có SurfaceTexture bên trong, vì vậy, bất kỳ ứng dụng nào sử dụng nhiều TextureView đều có nhiều SurfaceTextures trên một chuỗi. (Khuôn khổ thực tế had a bug trong việc triển khai thực hiện đã gây ra sự cố với nhiều TextureView, nhưng đó là vấn đề cấp cao, không phải là vấn đề trình điều khiển.)

SurfaceTexture, a/k/a GLConsumer, không làm nhiều việc chế biến. Khi một khung đến từ nguồn (trong trường hợp của bạn, máy ảnh), nó sử dụng một số hàm EGL để "bọc" bộ đệm dưới dạng kết cấu "bên ngoài". Bạn không thể thực hiện các hoạt động EGL này mà không có ngữ cảnh EGL để làm việc, đó là lý do tại sao SurfaceTexture phải được gắn vào một, và tại sao bạn không thể đặt một khung mới vào kết cấu nếu ngữ cảnh sai là hiện tại. Bạn có thể thấy từ the implementation of updateTexImage() rằng nó đang làm rất nhiều điều phức tạp với hàng đợi đệm và kết cấu và hàng rào, nhưng không ai trong số đó yêu cầu sao chép dữ liệu pixel. Tài nguyên hệ thống duy nhất bạn đang thực sự buộc phải là RAM, không phải là không đáng kể nếu bạn đang chụp ảnh có độ phân giải cao.

Connections

Một EGL bối cảnh có thể được di chuyển giữa các chủ đề, nhưng chỉ có thể là "hiện tại" trên một thread tại một thời điểm. Truy cập đồng thời từ nhiều luồng sẽ đòi hỏi nhiều đồng bộ không mong muốn. Một chuỗi đã cho chỉ có một ngữ cảnh "hiện tại". API OpenGL đã phát triển từ một luồng đơn với trạng thái toàn cục thành đa luồng và thay vì viết lại API, chúng chỉ đẩy trạng thái vào lưu trữ cục bộ ... do đó khái niệm "hiện tại".

Có thể tạo ngữ cảnh EGL chia sẻ những thứ nhất định giữa chúng, bao gồm kết cấu, nhưng nếu các ngữ cảnh trên các chủ đề khác nhau, bạn phải rất cẩn thận khi kết cấu được cập nhật. Grafika cung cấp một ví dụ tốt đẹp của getting it wrong.

SurfaceTextures được xây dựng dựa trên BufferQueues, có cấu trúc người tiêu dùng sản xuất. Điều thú vị về SurfaceTextures là chúng bao gồm cả hai mặt, vì vậy bạn có thể nạp dữ liệu ở một bên và kéo nó ra bên kia trong một quá trình đơn lẻ (không giống như SurfaceView, nơi mà người tiêu dùng ở xa). Giống như tất cả các công cụ bề mặt, chúng được xây dựng trên đầu trang của Binder IPC, do đó bạn có thể nạp Surface từ một sợi và an toàn updateTexImage() trong một luồng khác (hoặc quy trình). API được sắp xếp sao cho bạn tạo SurfaceTexture ở phía người tiêu dùng (quy trình của bạn) và sau đó chuyển tham chiếu đến nhà sản xuất (ví dụ: máy ảnh, chủ yếu chạy trong quy trình mediaserver).

Thực hiện

Bạn sẽ gây ra một loạt các chi phí nếu bạn liên tục kết nối và ngắt kết nối BufferQueues. Vì vậy, nếu bạn muốn có ba SurfaceTextures nhận bộ đệm, bạn sẽ cần phải kết nối tất cả ba đến đầu ra của Camera2, và để cho tất cả chúng nhận được "đệm phát sóng". Sau đó, bạn updateTexImage() theo kiểu vòng tròn. Kể từ khi BufferQueue của SurfaceTexture chạy ở chế độ "không đồng bộ", bạn nên luôn luôn nhận được khung mới nhất với mỗi cuộc gọi, không cần phải "thoát" hàng đợi.

Sự sắp xếp này không thực sự có thể cho đến khi thay đổi nhiều đầu ra BufferQueue Lollipop và giới thiệu Camera2, vì vậy tôi không biết liệu có ai đã thử phương pháp này trước đây hay không.

Tất cả SurfaceTextures sẽ được gắn vào cùng một ngữ cảnh EGL, lý tưởng trong một chủ đề không phải là chuỗi giao diện người dùng Xem, vì vậy bạn không phải chiến đấu với nội dung hiện tại. Nếu bạn muốn truy cập các kết cấu từ một bối cảnh thứ hai trong một chủ đề khác nhau, bạn sẽ cần phải sử dụng SurfaceTexture attach/detach cuộc gọi API, có hỗ trợ một cách rõ ràng cách tiếp cận này:

Một đối tượng kết cấu OpenGL ES mới được tạo ra và dân cư với khung hình SurfaceTexture hiện tại tại thời điểm cuộc gọi cuối cùng để tách khỏiFromGLContext().

Hãy nhớ rằng việc chuyển đổi ngữ cảnh EGL là hoạt động bên người tiêu dùng và không có kết nối với máy ảnh, đó là hoạt động của nhà sản xuất. Chi phí liên quan đến việc di chuyển SurfaceTexture giữa các bối cảnh phải nhỏ - ít hơn updateTexImage() - nhưng bạn cần thực hiện các bước thông thường để đảm bảo đồng bộ hóa khi liên lạc giữa các luồng.

Quá xấu ImageReader thiếu một cuộc gọi getTimestamp(), vì điều đó sẽ đơn giản hóa rất nhiều việc khớp bộ đệm từ máy ảnh.

Kết luận

Sử dụng nhiều SurfaceTextures để đệm đầu ra là có thể nhưng khéo léo. Tôi có thể thấy một lợi thế tiềm năng đối với phương pháp đệm ping-pong, trong đó một ST được sử dụng để nhận khung trong chuỗi/ngữ cảnh A trong khi ST khác được sử dụng để hiển thị trong chuỗi/ngữ cảnh B, nhưng vì bạn đang hoạt động trong thực tế thời gian tôi không nghĩ rằng có giá trị trong đệm bổ sung trừ khi bạn đang cố gắng để pad ra thời gian.

Như thường lệ, đọc Android System-Level Graphics Architecture doc.

+0

Cảm ơn lời giải thích tuyệt vời và nó có vẻ giống như một cách tiếp cận khả thi. Tôi có lẽ sẽ thử nó vào cuối tuần. Có lẽ sẽ đặt nó trên github – tangobravo

+0

Bởi "các bước thông thường để đảm bảo đồng bộ hóa", bạn chỉ có nghĩa là thread gọi 'updateTexImage()' phải được gọi là 'detachFromGLContext()' trước khi render thread cố gắng đính kèm nó để render? Không có yêu cầu 'glFinish()' nào cần thiết nếu các ngữ cảnh được thiết lập để chia sẻ các kết cấu như đã đề cập trong lỗi grafika mà bạn đã liên kết? – tangobravo

+0

Có ['getTimestamp()'] (https://developer.android.com/reference/android/media/Image.html#getTimestamp()) trên Hình ảnh từ API 19. Tôi hy vọng rằng sẽ trả về cùng dấu thời gian là SurfaceTexture nhưng sẽ kiểm tra khi tôi kiểm tra phương pháp tiếp cận. – tangobravo

Các vấn đề liên quan