Nếu ứng dụng Android của bạn dùng camera, thì bạn cần cân nhắc một số yếu tố đặc biệt khi xử lý hướng. Tài liệu này giả định rằng bạn hiểu các khái niệm cơ bản về API camera2 của Android. Bạn có thể đọc bài đăng trên blog hoặc bản tóm tắt của chúng tôi để biết thông tin tổng quan về camera2. Bạn cũng nên thử viết một ứng dụng máy ảnh trước khi xem tài liệu này.
Thông tin khái quát
Việc xử lý hướng trong các ứng dụng camera Android khá phức tạp và cần xem xét các yếu tố sau:
- Hướng tự nhiên: Hướng màn hình khi thiết bị ở vị trí "bình thường" theo thiết kế của thiết bị – thường là hướng dọc đối với điện thoại di động và hướng ngang đối với máy tính xách tay.
- Hướng của cảm biến: Hướng của cảm biến được gắn trên thiết bị.
- Độ xoay màn hình: Mức độ xoay của thiết bị so với hướng tự nhiên.
- Kích thước kính ngắm: Kích thước kính ngắm dùng để hiển thị bản xem trước của camera.
- Kích thước hình ảnh do camera xuất ra.
Sự kết hợp của các yếu tố này tạo ra nhiều cấu hình giao diện người dùng và bản xem trước có thể có cho các ứng dụng máy ảnh. Tài liệu này nhằm minh hoạ cách nhà phát triển có thể điều hướng các hướng này và xử lý đúng hướng camera trong ứng dụng Android.
Để đơn giản hoá, hãy giả định rằng tất cả ví dụ đều liên quan đến camera sau, trừ phi có đề cập khác. Ngoài ra, tất cả ảnh sau đây đều là ảnh mô phỏng để minh hoạ rõ ràng hơn.
Mọi điều cần biết về hướng
Hướng tự nhiên
Hướng tự nhiên được xác định là hướng màn hình khi thiết bị ở vị trí mà người dùng thường mong đợi. Đối với điện thoại, hướng tự nhiên thường là hướng dọc. Nói cách khác, điện thoại có chiều rộng ngắn hơn và chiều cao dài hơn. Đối với máy tính xách tay, hướng tự nhiên của chúng là hướng ngang, tức là chiều rộng dài hơn và chiều cao ngắn hơn. Máy tính bảng phức tạp hơn một chút so với điện thoại – máy tính bảng có thể ở chế độ dọc hoặc ngang.
Hướng cảm biến
Nói một cách chính thức, hướng cảm biến được đo bằng số độ mà hình ảnh đầu ra từ cảm biến cần được xoay theo chiều kim đồng hồ để khớp với hướng tự nhiên của thiết bị. Nói cách khác, hướng cảm biến là số độ mà cảm biến xoay ngược chiều kim đồng hồ trước khi được gắn trên thiết bị. Khi nhìn vào màn hình, bạn sẽ thấy hướng xoay theo chiều kim đồng hồ. Điều này là do cảm biến camera sau được lắp đặt ở mặt "sau" của thiết bị.
Theo Định nghĩa về khả năng tương thích với Android 10 7.5.5 Hướng của camera, camera trước và camera sau "PHẢI được định hướng sao cho chiều dài của camera phù hợp với chiều dài của màn hình".
Các vùng đệm đầu ra của camera có kích thước ngang. Vì hướng tự nhiên của điện thoại thường là hướng dọc, nên hướng cảm biến thường là 90 hoặc 270 độ so với hướng tự nhiên để cạnh dài của vùng đệm đầu ra khớp với cạnh dài của màn hình. Hướng cảm biến khác nhau đối với những thiết bị có hướng tự nhiên là hướng ngang, chẳng hạn như Chromebook. Trên những thiết bị này, cảm biến hình ảnh được đặt sao cho cạnh dài của vùng đệm đầu ra khớp với cạnh dài của màn hình. Vì cả hai đều có kích thước ngang, nên hướng của chúng sẽ khớp và hướng của cảm biến là 0 hoặc 180 độ.
Các hình minh hoạ sau đây cho thấy mọi thứ trông như thế nào theo quan điểm của người quan sát, nhìn vào màn hình thiết bị:
Hãy xem xét cảnh sau:
| Điện thoại | Máy tính xách tay |
|---|---|
![]() |
![]() |
Vì hướng cảm biến thường là 90 hoặc 270 độ trên điện thoại, nên nếu không tính đến hướng cảm biến, hình ảnh bạn nhận được sẽ trông như sau:
| Điện thoại | Máy tính xách tay |
|---|---|
![]() |
![]() |
Giả sử hướng cảm biến ngược chiều kim đồng hồ được lưu trữ trong biến sensorOrientation. Để bù cho hướng cảm biến, bạn cần xoay các vùng đệm đầu ra theo `sensorOrientation` theo chiều kim đồng hồ để đưa hướng trở lại phù hợp với hướng tự nhiên của thiết bị.
Trong Android, các ứng dụng có thể dùng TextureView hoặc SurfaceView để hiển thị bản xem trước của camera. Cả hai đều có thể xử lý hướng cảm biến nếu các ứng dụng sử dụng đúng cách. Chúng tôi sẽ trình bày chi tiết cách bạn nên tính đến hướng của cảm biến trong các phần sau.
Độ xoay màn hình
Độ xoay màn hình được xác định chính thức bằng độ xoay của đồ hoạ được vẽ trên màn hình, theo hướng ngược lại với độ xoay thực của thiết bị so với hướng tự nhiên. Các phần sau đây giả định rằng tất cả các hướng xoay màn hình đều là bội số của 90. Nếu bạn truy xuất hướng xoay màn hình theo độ tuyệt đối, hãy làm tròn hướng xoay đó đến giá trị gần nhất trong số {0, 90, 180, 270}.
"Hướng hiển thị" trong các phần sau đây đề cập đến việc thiết bị được giữ theo hướng ngang hay dọc và khác với "xoay màn hình".
Giả sử bạn xoay các thiết bị 90 độ ngược chiều kim đồng hồ so với vị trí trước đó như minh hoạ trong hình sau:
Giả sử các vùng đệm đầu ra đã được xoay dựa trên hướng cảm biến, thì bạn sẽ có các vùng đệm đầu ra sau:
| Điện thoại | Máy tính xách tay |
|---|---|
![]() |
![]() |
Nếu độ xoay màn hình được lưu trữ trong biến displayRotation, để có được hình ảnh chính xác, bạn nên xoay các vùng đệm đầu ra theo chiều ngược chiều kim đồng hồ bằng displayRotation.
Đối với camera trước, chế độ xoay màn hình sẽ tác động lên các vùng đệm hình ảnh theo hướng ngược lại so với màn hình. Nếu đang xử lý camera trước, bạn nên xoay các vùng đệm theo displayRotatation theo chiều kim đồng hồ.
Chú ý
Độ xoay màn hình đo độ xoay ngược chiều kim đồng hồ của thiết bị. Điều này không đúng với tất cả các API về hướng/xoay.
Ví dụ:
-
Nếu sử dụng
Display#getRotation(), bạn sẽ nhận được chế độ xoay ngược chiều kim đồng hồ như đã đề cập trong tài liệu này. - Nếu sử dụng OrientationEventListener#onOrientationChanged(int), bạn sẽ nhận được chế độ xoay theo chiều kim đồng hồ.
Điều quan trọng cần lưu ý ở đây là độ xoay màn hình tương ứng với hướng tự nhiên. Ví dụ: nếu xoay điện thoại theo chiều dọc 90 hoặc 270 độ, bạn sẽ thấy màn hình có hình dạng ngang. Ngược lại, bạn sẽ có màn hình dọc nếu xoay máy tính xách tay cùng một góc. Các ứng dụng phải luôn ghi nhớ điều này và không bao giờ được giả định về hướng tự nhiên của thiết bị.
Ví dụ
Hãy sử dụng các hình trước đó để minh hoạ hướng và góc xoay.
| Điện thoại | Máy tính xách tay |
|---|---|
| Hướng tự nhiên = Dọc | Hướng tự nhiên = Hướng ngang |
| Hướng cảm biến = 90 | Hướng của cảm biến = 0 |
| Độ xoay màn hình = 0 | Độ xoay màn hình = 0 |
| Hướng màn hình = Dọc | Hướng màn hình = Ngang |
| Điện thoại | Máy tính xách tay |
|---|---|
| Hướng tự nhiên = Dọc | Hướng tự nhiên = Hướng ngang |
| Hướng cảm biến = 90 | Hướng của cảm biến = 0 |
| Độ xoay màn hình = 90 | Độ xoay màn hình = 90 |
| Hướng màn hình = Ngang | Hướng màn hình = Dọc |
Kích thước kính ngắm
Các ứng dụng phải luôn điều chỉnh kích thước khung ngắm dựa trên hướng, chế độ xoay và độ phân giải màn hình. Nói chung, các ứng dụng phải làm cho hướng của khung ngắm giống với hướng hiển thị hiện tại. Nói cách khác, các ứng dụng phải căn chỉnh cạnh dài của khung ngắm với cạnh dài của màn hình.
Kích thước đầu ra của hình ảnh theo camera
Khi chọn kích thước đầu ra của hình ảnh cho bản xem trước, bạn nên chọn kích thước bằng hoặc lớn hơn một chút so với kích thước của Khung ngắm bất cứ khi nào có thể. Bạn thường không muốn các vùng đệm đầu ra được mở rộng vì điều này sẽ gây ra hiện tượng pixel hoá. Bạn cũng không nên chọn kích thước quá lớn vì có thể làm giảm hiệu suất và tiêu hao nhiều pin hơn.
Hướng JPEG
Hãy bắt đầu bằng một tình huống thường gặp – chụp ảnh JPEG. Trong API camera2, bạn có thể truyền JPEG_ORIENTATION trong yêu cầu chụp để chỉ định mức độ xoay theo chiều kim đồng hồ mà bạn muốn đối với các tệp JPEG đầu ra.
Sau đây là bản tóm tắt nhanh về những nội dung chúng tôi đã đề cập:
-
Để xử lý hướng cảm biến, bạn cần xoay vùng đệm hình ảnh theo chiều kim đồng hồ
sensorOrientation. -
Để xử lý việc xoay màn hình, bạn cần xoay một vùng đệm theo hướng ngược chiều kim đồng hồ
displayRotationđối với camera sau, theo chiều kim đồng hồ đối với camera trước.
Cộng 2 yếu tố này lại, số lượng bạn muốn xoay theo chiều kim đồng hồ là
-
sensorOrientation - displayRotationcho camera sau. -
sensorOrientation + displayRotationcho camera trước.
Bạn có thể xem mã mẫu cho logic này trong tài liệu JPEG_ORIENTATION. Xin lưu ý rằng deviceOrientation trong mã mẫu của tài liệu đang sử dụng chế độ xoay thiết bị theo chiều kim đồng hồ. Do đó, các dấu hiệu xoay màn hình sẽ bị đảo ngược.
Xem trước
Còn bản xem trước của camera thì sao? Có 2 cách chính để ứng dụng có thể hiển thị bản xem trước của camera: SurfaceView và TextureView. Mỗi loại yêu cầu một phương pháp riêng để xử lý đúng hướng.
SurfaceView
SurfaceView thường được đề xuất cho bản xem trước của camera, miễn là bạn không cần xử lý hoặc tạo hiệu ứng cho các vùng đệm xem trước. Nó có hiệu suất cao hơn và ít tốn tài nguyên hơn TextureView.
SurfaceView cũng tương đối dễ bố trí. Bạn chỉ cần lo lắng về tỷ lệ khung hình của SurfaceView mà bạn đang hiển thị bản xem trước của camera.
Nguồn
Bên dưới SurfaceView, nền tảng Android xoay các vùng đệm đầu ra để khớp với hướng màn hình của thiết bị. Nói cách khác, nó tính đến cả hướng của cảm biến và độ xoay màn hình. Nói một cách đơn giản hơn, khi màn hình ở chế độ ngang, chúng ta sẽ có một bản xem trước cũng ở chế độ ngang và ngược lại đối với chế độ dọc.
Điều này được minh hoạ trong bảng sau. Điều quan trọng cần lưu ý ở đây là chỉ riêng việc xoay màn hình không xác định được hướng của nguồn.
| Độ xoay màn hình | Điện thoại (Hướng tự nhiên = Dọc) | Máy tính xách tay (Hướng tự nhiên = Chiều ngang) |
|---|---|---|
| 0 | ![]() |
![]() |
| 90 | ![]() |
![]() |
| 180 | ![]() |
![]() |
| 270 | ![]() |
![]() |
Bố cục
Như bạn thấy, SurfaceView đã xử lý một số vấn đề phức tạp cho chúng ta. Nhưng giờ đây, bạn cần cân nhắc kích thước của kính ngắm hoặc kích thước bạn muốn xem trước trên màn hình. SurfaceView tự động điều chỉnh tỷ lệ bộ nhớ đệm nguồn cho phù hợp với kích thước của bộ nhớ đệm đó. Bạn cần đảm bảo tỷ lệ khung hình của kính ngắm giống với tỷ lệ khung hình của sourcebuffer. Ví dụ: nếu cố gắng điều chỉnh một bản xem trước có hình chân dung cho vừa với SurfaceView có hình ngang, bạn sẽ thấy hình ảnh bị biến dạng như sau:
Thông thường, bạn muốn tỷ lệ khung hình (tức là chiều rộng/chiều cao) của khung ngắm giống với tỷ lệ khung hình của nguồn. Nếu bạn không muốn cắt hình ảnh trong khung ngắm (cắt bớt một số pixel để cố định màn hình), thì có 2 trường hợp cần xem xét, đó là khi aspectRatioActivity lớn hơn aspectRatioSource và khi aspectRatioActivity nhỏ hơn hoặc bằng aspectRatioSource
aspectRatioActivity > aspectRatioSource
Bạn có thể coi trường hợp này là hoạt động "rộng" hơn. Dưới đây là ví dụ về trường hợp bạn có một hoạt động 16:9 và một nguồn 4:3.
aspectRatioActivity = 16/9 ≈ 1.78 aspectRatioSource = 4/3 ≈ 1.33
Trước tiên, bạn cũng muốn kính ngắm của mình có tỷ lệ khung hình là 4:3. Sau đó, bạn muốn điều chỉnh nguồn và khung ngắm cho phù hợp với hoạt động như sau:
Trong trường hợp này, bạn nên điều chỉnh chiều cao của khung ngắm sao cho khớp với chiều cao của hoạt động, đồng thời điều chỉnh tỷ lệ khung hình của khung ngắm sao cho giống với tỷ lệ khung hình của nguồn. Mã giả có dạng như sau:
viewfinderHeight = activityHeight; viewfinderWidth = activityHeight * aspectRatioSource;
aspectRatioActivity ≤ aspectRatioSource
Trường hợp khác là khi hoạt động "hẹp" hoặc "cao". Chúng ta có thể dùng lại ví dụ trước, ngoại trừ trong ví dụ sau, bạn xoay thiết bị 90 độ, khiến hoạt động có tỷ lệ khung hình là 9:16 và nguồn có tỷ lệ khung hình là 3:4.
aspectRatioActivity = 9/16 = 0.5625 aspectRatioSource = 3/4 = 0.75
Trong trường hợp này, bạn muốn điều chỉnh nguồn và khung ngắm cho vừa với hoạt động như sau:
Bạn nên điều chỉnh chiều rộng của khung ngắm sao cho khớp với chiều rộng của hoạt động (thay vì chiều cao trong trường hợp trước) trong khi vẫn giữ nguyên tỷ lệ khung hình của khung ngắm so với tỷ lệ khung hình của nguồn. Mã giả:
viewfinderWidth = activityWidth; viewfinderHeight = activityWidth / aspectRatioSource;
Cắt xén
AutoFitSurfaceView.kt (github) trong các mẫu Camera2 ghi đè SurfaceView và xử lý tỷ lệ khung hình không khớp bằng cách sử dụng hình ảnh bằng hoặc "chỉ lớn hơn" hoạt động theo cả hai chiều, sau đó cắt nội dung tràn. Điều này hữu ích cho những ứng dụng muốn bản xem trước bao phủ toàn bộ hoạt động hoặc lấp đầy hoàn toàn một khung hiển thị có kích thước cố định mà không làm biến dạng hình ảnh.
Cảnh báo
Mẫu trước đó cố gắng tối đa hoá không gian màn hình bằng cách làm cho bản xem trước lớn hơn hoạt động một chút để không còn khoảng trống nào. Điều này dựa trên thực tế là các phần tràn sẽ bị bố cục mẹ (hoặc ViewGroup) cắt theo mặc định. Hành vi này nhất quán với RelativeLayout và LinearLayout nhưng KHÔNG nhất quán với ConstraintLayout. ConstraintLayout có thể đổi kích thước các Thành phần hiển thị con để chúng vừa với bố cục. Điều này sẽ làm hỏng hiệu ứng "cắt ở giữa" dự kiến và khiến bản xem trước bị kéo giãn. Bạn có thể tham khảo cam kết này.
TextureView
TextureView cho phép bạn kiểm soát tối đa nội dung của bản xem trước camera, nhưng điều này sẽ ảnh hưởng đến hiệu suất. Bạn cũng cần phải làm nhiều việc hơn để hiển thị bản xem trước của camera một cách chính xác.
Nguồn
Bên dưới TextureView, nền tảng Android sẽ xoay các vùng đệm đầu ra theo hướng cảm biến để khớp với hướng tự nhiên của thiết bị. Mặc dù TextureView xử lý hướng của cảm biến, nhưng không xử lý việc xoay màn hình. Thao tác này sẽ căn chỉnh các vùng đệm đầu ra theo hướng tự nhiên của thiết bị, tức là bạn sẽ phải tự xử lý các chế độ xoay màn hình.
Điều này được minh hoạ trong bảng sau. Hãy thử xoay các số liệu theo hướng xoay màn hình tương ứng, bạn sẽ thực sự nhận được các số liệu tương tự trong SurfaceView.
| Độ xoay màn hình | Điện thoại (Hướng tự nhiên = Dọc) | Máy tính xách tay (Hướng tự nhiên = Chiều ngang) |
|---|---|---|
| 0 | ![]() |
![]() |
| 90 | ![]() |
![]() |
| 180 | ![]() |
![]() |
| 270 | ![]() |
![]() |
Bố cục
Bố cục hơi phức tạp đối với trường hợp TextureView. Trước đây, bạn nên sử dụng ma trận biến đổi cho TextureView, nhưng phương thức đó không hoạt động trên tất cả các thiết bị. Bạn nên làm theo các bước được mô tả tại đây.
Quy trình gồm 3 bước để bố trí chính xác bản xem trước trên TextureView:
- Đặt kích thước của TextureView giống với kích thước xem trước đã chọn.
- Điều chỉnh tỷ lệ TextureView có thể bị kéo giãn về kích thước ban đầu của bản xem trước.
-
Xoay TextureView
displayRotationngược chiều kim đồng hồ.
Giả sử bạn có một chiếc điện thoại có chế độ xoay màn hình 90 độ.
1. Đặt kích thước của TextureView giống với kích thước xem trước đã chọn
Giả sử kích thước xem trước bạn chọn là previewWidth × previewHeight, trong đó previewWidth > previewHeight (đầu ra của cảm biến có dạng ngang theo bản chất). Khi định cấu hình một phiên chụp, bạn nên gọi SurfaceTexture#setDefaultBufferSize(int width, height) để chỉ định kích thước xem trước (previewWidth × previewHeight).
Trước khi gọi setDefaultBufferSize, bạn cũng phải đặt kích thước của TextureView thành `previewWidth × previewHeight` bằng View#setLayoutParams(android.view.ViewGroup.LayoutParams). Lý do là TextureView gọi SurfaceTexture#setDefaultBufferSize(int width, height) bằng chiều rộng và chiều cao đo được. Nếu bạn không đặt kích thước của TextureView một cách rõ ràng từ trước, thì điều này có thể gây ra tình trạng xung đột. Vấn đề này được giảm thiểu bằng cách đặt kích thước của TextureView một cách rõ ràng trước.
Giờ đây, TextureView có thể không khớp với kích thước của nguồn. Trong trường hợp điện thoại, nguồn có hình dạng dọc, nhưng TextureView có hình dạng ngang do layoutParams mà bạn vừa đặt. Điều này sẽ dẫn đến việc bản xem trước bị kéo giãn, như minh hoạ ở đây:
2. Điều chỉnh tỷ lệ TextureView có thể bị kéo giãn về kích thước ban đầu của bản xem trước
Hãy cân nhắc những điều sau đây để điều chỉnh tỷ lệ bản xem trước bị kéo giãn về kích thước của nguồn.
Phương diện của nguồn (sourceWidth × sourceHeight) là:
-
previewHeight × previewWidth, nếu hướng tự nhiên là hướng dọc hoặc hướng dọc lộn ngược (hướng cảm biến là 90 hoặc 270 độ) -
previewWidth × previewHeight, nếu hướng tự nhiên là hướng ngang hoặc hướng ngang ngược (hướng cảm biến là 0 hoặc 180 độ)
Khắc phục tình trạng kéo giãn bằng cách sử dụng View#setScaleX(float) và View#setScaleY(float)
-
setScaleX(
sourceWidth / previewWidth) -
setScaleY(
sourceHeight / previewHeight)
3. Xoay bản xem trước theo `displayRotation` ngược chiều kim đồng hồ
Như đã đề cập trước đó, bạn nên xoay bản xem trước theo hướng ngược chiều kim đồng hồ displayRotation để bù cho việc xoay màn hình.
Bạn có thể thực hiện việc này bằng cách View#setRotation(float)
-
setRotation(
-displayRotation), vì thao tác này sẽ xoay theo chiều kim đồng hồ.
Mẫu
-
PreviewViewtừ camerax trong Jetpack xử lý bố cục TextureView như đã mô tả trước đó. Thao tác này định cấu hình phép biến đổi bằng PreviewCorrector.
Lưu ý: Nếu trước đây bạn đã sử dụng ma trận biến đổi cho TextureView trong mã của mình, thì bản xem trước có thể không hiển thị đúng trên một thiết bị có hướng ngang tự nhiên như Chromebook. Có thể ma trận biến đổi của bạn giả định sai hướng của cảm biến là 90 hoặc 270 độ. Bạn có thể tham khảo cam kết này trên GitHub để tìm giải pháp thay thế, nhưng bạn nên di chuyển ứng dụng để sử dụng phương thức được mô tả ở đây.





















