Android uygulamanızda kamera kullanılıyorsa yönleri işlerken dikkat edilmesi gereken bazı özel noktalar vardır. Bu belgede, Android camera2 API'nin temel kavramlarını bildiğiniz varsayılır. camera2'ye genel bir bakış için blog yayınımızı veya özetimizi okuyabilirsiniz. Bu belgeyi incelemeden önce kamera uygulaması yazmayı denemenizi de öneririz.
Arka plan
Android kamera uygulamalarında yönleri işlemek zordur ve aşağıdaki faktörlerin dikkate alınması gerekir:
- Doğal yön: Cihazın tasarımına göre "normal" konumda olduğu zamanki ekran yönü. Genellikle cep telefonları için dikey yön, dizüstü bilgisayarlar için yatay yön.
- Sensör yönü: Cihaza fiziksel olarak monte edilmiş sensörün yönü.
- Ekran döndürme: Cihazın doğal yönünden ne kadar döndürüldüğü.
- Vizör boyutu: Kamera önizlemesini görüntülemek için kullanılan vizörün boyutu.
- Kameranın çıkış yaptığı görüntü boyutu.
Bu faktörler bir araya geldiğinde kamera uygulamaları için çok sayıda olası kullanıcı arayüzü ve önizleme yapılandırması ortaya çıkar. Bu doküman, geliştiricilerin bu yönlendirmelerde nasıl gezinebileceğini ve Android uygulamalarındaki kamera yönlerini nasıl doğru şekilde işleyebileceğini göstermeyi amaçlamaktadır.
İşleri biraz daha basitleştirmek için aksi belirtilmedikçe tüm örneklerin arkaya bakan bir kamerayı içerdiğini varsayın. Ayrıca, aşağıdaki fotoğrafların tümü, görsel olarak daha net olması için simüle edilmiştir.
Yönler hakkında her şey
Doğal Yön
Doğal yön, cihazın normalde olması beklenen konumdayken ekran yönü olarak tanımlanır. Telefonların doğal yönü genellikle portre modudur. Başka bir deyişle, telefonların genişliği daha kısa, yüksekliği ise daha uzundur. Dizüstü bilgisayarların doğal yönü yataydır. Yani genişlikleri daha uzun, yükseklikleri daha kısadır. Tabletler bu durumdan biraz daha karmaşıktır. Dikey veya yatay olabilirler.
Sensör Yönü
Resmi olarak, sensör yönü, sensörden gelen bir çıkış görüntüsünün cihazın doğal yönüyle eşleşmesi için saat yönünde döndürülmesi gereken dereceyle ölçülür. Diğer bir deyişle, sensör yönü, bir sensörün cihaza monte edilmeden önce saat yönünün tersine döndürüldüğü derece sayısıdır. Ekrana bakıldığında, dönüş saat yönünde gibi görünür. Bunun nedeni, arka kamera sensörünün cihazın "arka" tarafına takılmış olmasıdır.
Android 10 Uyumluluk Tanımı 7.5.5 Kamera Yönü'ne göre, ön ve arka kameralar "Kameranın uzun boyutu ekranın uzun boyutuyla aynı doğrultuda olacak şekilde yönlendirilmelidir.".
Kameralardan gelen çıkış arabellekleri yatay boyuttadır. Telefonların doğal yönü genellikle dikey olduğundan, çıkış arabelleğinin uzun kenarının ekranın uzun kenarıyla eşleşmesi için sensör yönü genellikle doğal yönden 90 veya 270 derece uzaktadır. Doğal yönü yatay olan cihazlarda (ör. Chromebook'lar) sensör yönü farklıdır. Bu cihazlarda, görüntü sensörleri çıkış arabelleğinin uzun kenarı ekranın uzun kenarıyla eşleşecek şekilde yerleştirilir. Her ikisi de yatay boyutta olduğundan yönler eşleşir ve sensör yönü 0 veya 180 derecedir.
Aşağıdaki resimlerde, cihaz ekranına bakan bir gözlemcinin bakış açısıyla nasıl göründüğü gösterilmektedir:
Aşağıdaki sahneyi ele alalım:
| Telefon | Dizüstü bilgisayar |
|---|---|
![]() |
![]() |
Telefonlarda sensör yönü genellikle 90 veya 270 derece olduğundan sensör yönü dikkate alınmadığında elde edeceğiniz görüntüler şu şekilde görünür:
| Telefon | Dizüstü bilgisayar |
|---|---|
![]() |
![]() |
Saat yönünün tersine sensör yönünün sensorOrientation değişkeninde depolandığını varsayalım. Sensör yönünü telafi etmek için yönü cihazın doğal yönüyle tekrar hizalamak üzere çıkış arabelleklerini `sensorOrientation` saat yönünde döndürmeniz gerekir.
Android'de uygulamalar, kamera önizlemelerini göstermek için TextureView veya SurfaceView'ı kullanabilir. Uygulamalar bunları doğru şekilde kullanırsa her ikisi de sensör yönünü işleyebilir. Sensör yönünü nasıl hesaba katmanız gerektiğini aşağıdaki bölümlerde ayrıntılı olarak açıklayacağız.
Ekran döndürme
Ekran döndürme, ekranda çizilen grafiklerin döndürülmesiyle resmî olarak tanımlanır. Bu, cihazın doğal yönünden fiziksel olarak döndürülmesinin ters yönüdür. Aşağıdaki bölümlerde, ekran döndürmelerinin tümünün 90'ın katları olduğu varsayılmaktadır. Ekran döndürmeyi mutlak dereceyle alırsanız {0, 90, 180, 270} değerlerinden en yakınına yuvarlayın.
Aşağıdaki bölümlerdeki "Ekran yönü", cihazın fiziksel olarak yatay veya dikey konumda tutulup tutulmadığını ifade eder ve "ekran döndürme"den farklıdır.
Cihazları, aşağıdaki şekilde gösterildiği gibi önceki konumlarından saat yönünün tersine 90 derece döndürdüğünüzü varsayalım:
Çıkış arabelleklerinin, sensör yönüne göre döndürüldüğünü varsayarsak aşağıdaki çıkış arabelleklerine sahip olursunuz:
| Telefon | Dizüstü bilgisayar |
|---|---|
![]() |
![]() |
Ekran döndürme değişkeni displayRotation'da saklanıyorsa doğru görüntüyü elde etmek için çıkış arabelleklerini displayRotation ile saat yönünün tersine döndürmeniz gerekir.
Ön kameralarda, ekran döndürme işlemi ekranla ters yönde görüntü arabelleklerine uygulanır. Ön kamerayla çalışıyorsanız arabellekleri displayRotatation ile saat yönünde döndürmeniz gerekir.
Uyarılar
Ekran döndürme, cihazın saat yönünün tersine dönüşünü ölçer. Bu durum tüm yönlendirme/döndürme API'leri için geçerli değildir.
Örneğin,
-
Display#getRotation()işaretini kullanırsanız bu dokümanda belirtildiği gibi saatin tersi yönünde dönüş elde edersiniz. - OrientationEventListener#onOrientationChanged(int) kullanıyorsanız bunun yerine saat yönünde dönüşü alırsınız.
Burada dikkat edilmesi gereken önemli nokta, ekran döndürmenin doğal yönlendirmeye göre yapılmasıdır. Örneğin, bir telefonu 90 veya 270 derece döndürdüğünüzde yatay şekilli bir ekran elde edersiniz. Buna karşılık, bir dizüstü bilgisayarı aynı miktarda döndürdüğünüzde portre şeklinde bir ekran elde edersiniz. Uygulamalar bunu her zaman göz önünde bulundurmalı ve cihazın doğal yönü hakkında hiçbir zaman varsayımda bulunmamalıdır.
Örnekler
Yönlerin ve dönüşlerin ne olduğunu göstermek için önceki şekilleri kullanalım.
| Telefon | Dizüstü bilgisayar |
|---|---|
| Doğal Yön = Dikey | Doğal Yön = Yatay |
| Sensör Yönü = 90 | Sensör Yönü = 0 |
| Ekran döndürme = 0 | Ekran döndürme = 0 |
| Ekran yönü = Dikey | Ekran yönü = Yatay |
| Telefon | Dizüstü bilgisayar |
|---|---|
| Doğal Yön = Dikey | Doğal Yön = Yatay |
| Sensör Yönü = 90 | Sensör Yönü = 0 |
| Ekran döndürme = 90 | Ekran döndürme = 90 |
| Ekran yönü = Yatay | Ekran yönü = Dikey |
Vizör Boyutu
Uygulamalar, vizörü her zaman yön, döndürme ve ekran çözünürlüğüne göre yeniden boyutlandırmalıdır. Uygulamalar genel olarak vizörün yönünü mevcut ekran yönüyle aynı yapmalıdır. Diğer bir deyişle, uygulamalar vizörün uzun kenarını ekranın uzun kenarıyla aynı hizaya getirmelidir.
Kameraya Göre Görüntü Çıkış Boyutu
Önizleme için görüntü çıkış boyutunu seçerken mümkün olduğunda Vizör'ün boyutuna eşit veya biraz daha büyük bir boyut seçmelisiniz. Genellikle çıkış arabelleklerinin ölçeğinin büyütülmesini istemezsiniz. Bu durumda pikseller bozulur. Ayrıca, performansı düşürebilecek ve daha fazla pil kullanabilecek çok büyük bir boyut seçmemeniz gerekir.
JPEG Yönü
Yaygın bir durumla, yani JPEG fotoğraf çekmeyle başlayalım. camera2 API'sinde, çıkış JPEG'lerinizin saat yönünde ne kadar döndürülmesini istediğinizi belirtmek için yakalama isteğinde JPEG_ORIENTATION değerini iletebilirsiniz.
Bahsettiğimiz noktaların kısa bir özeti:
-
Sensör yönünü işlemek için görüntü arabelleğini saat yönünde
sensorOrientationderece döndürmeniz gerekir. -
Ekran döndürmeyi işlemek için arka kameralarda
displayRotationsaat yönünün tersine, ön kameralarda ise saat yönünde bir arabellek döndürmeniz gerekir.
2 faktörü topladığınızda, saat yönünde döndürmek istediğiniz tutar
-
Arka kameralar için
sensorOrientation - displayRotation. -
sensorOrientation + displayRotationön kameralar için.
Bu mantığın örnek kodunu JPEG_ORIENTATION belgelerinde görebilirsiniz. Belgelerdeki örnek kodda deviceOrientation simgesinin, cihazın saat yönünde dönüşünü kullandığını unutmayın. Bu nedenle, ekran döndürme işaretleri tersine çevrilir.
Önizle
Kamera önizlemesi nasıl? Bir uygulamanın kamera önizlemesini görüntüleyebileceği 2 ana yöntem vardır: SurfaceView ve TextureView. Her biri, yönlendirmenin doğru şekilde işlenmesi için farklı yaklaşımlar gerektirir.
SurfaceView
SurfaceView, önizleme arabelleklerini işlemeniz veya animasyon eklemeniz gerekmediği sürece kamera önizlemeleri için genellikle önerilir. TextureView'dan daha yüksek performanslıdır ve daha az kaynak gerektirir.
SurfaceView'in düzenlenmesi de nispeten daha kolaydır. Yalnızca kamera önizlemesini görüntülediğiniz SurfaceView'un en boy oranıyla ilgili endişelenmeniz gerekir.
Kaynak
Android platformu, SurfaceView'in altında çıkış arabelleklerini cihazın ekran yönüne uyacak şekilde döndürür. Diğer bir deyişle, hem sensör yönünü hem de ekran döndürmeyi hesaba katar. Daha basit bir ifadeyle, ekranımız yatay olduğunda yatay bir önizleme, dikey olduğunda ise dikey bir önizleme elde ederiz.
Bu durum aşağıdaki tabloda gösterilmektedir. Burada unutulmaması gereken nokta, ekran döndürmenin tek başına kaynağın yönünü belirlemediğidir.
| Ekran döndürme | Telefon (doğal yönlendirme = dikey) | Dizüstü bilgisayar (doğal yön = yatay) |
|---|---|---|
| 0 | ![]() |
![]() |
| 90 | ![]() |
![]() |
| 180) | ![]() |
![]() |
| 270 | ![]() |
![]() |
Düzen
Gördüğünüz gibi SurfaceView, zorlu işlerin bazılarını bizim için halihazırda ele alıyor. Ancak artık vizörün boyutunu veya ekrandaki önizlemenizin ne kadar büyük olmasını istediğinizi de göz önünde bulundurmanız gerekiyor. SurfaceView, kaynak arabelleği kendi boyutlarına uyacak şekilde otomatik olarak ölçeklendirir. Vizörün en boy oranının sourcebuffer'ın en boy oranıyla aynı olduğundan emin olmanız gerekir. Örneğin, portre şeklindeki bir önizlemeyi yatay şeklindeki bir SurfaceView'e sığdırmaya çalışırsanız aşağıdaki gibi bozuk bir görüntü elde edersiniz:
Genellikle vizörün en boy oranının (yani genişlik/yükseklik) kaynağın en boy oranıyla aynı olmasını istersiniz. Görüntüleyiciye sığdırmak için görüntüyü kırpmak (görüntülemeyi düzeltmek için piksellerin bir kısmını kesmek) istemiyorsanız aspectRatioActivity değerinin aspectRatioSource değerinden büyük olduğu ve aspectRatioActivity değerinin aspectRatioSource değerinden küçük veya bu değere eşit olduğu olmak üzere dikkate alınması gereken 2 durum vardır.
aspectRatioActivity > aspectRatioSource
Bu durumda etkinliğin "daha geniş" olduğunu düşünebilirsiniz. Aşağıda, 16:9 etkinliğiniz ve 4:3 kaynağınız olduğu bir örneği ele alıyoruz.
aspectRatioActivity = 16/9 ≈ 1.78 aspectRatioSource = 4/3 ≈ 1.33
Öncelikle vizörünüzün de 4:3 olmasını istersiniz. Ardından, kaynağı ve vizörü etkinliğe aşağıdaki gibi yerleştirmek istersiniz:
Bu durumda, vizörün yüksekliğini etkinliğin yüksekliğiyle eşleştirirken vizörün en boy oranını kaynağın en boy oranıyla aynı yapmanız gerekir. Sözde kod aşağıdaki gibidir:
viewfinderHeight = activityHeight; viewfinderWidth = activityHeight * aspectRatioSource;
aspectRatioActivity ≤ aspectRatioSource
Diğer durum ise etkinliğin "daha dar" veya "daha uzun" olmasıdır. Önceki örneği tekrar kullanabiliriz. Ancak aşağıdaki örnekte cihazı 90 derece döndürdüğünüz için etkinlik 9:16, kaynak ise 3:4 olur.
aspectRatioActivity = 9/16 = 0.5625 aspectRatioSource = 3/4 = 0.75
Bu durumda, kaynağı ve vizörü etkinliğe aşağıdaki gibi yerleştirmeniz gerekir:
Vizörün en boy oranını kaynağın en boy oranıyla aynı yaparken vizörün genişliğini etkinliğin genişliğiyle eşleşecek şekilde ayarlamanız gerekir (önceki durumda yükseklik eşleştiriliyordu). Sözde kod:
viewfinderWidth = activityWidth; viewfinderHeight = activityWidth / aspectRatioSource;
Kırpma
Camera2 örneklerindeki AutoFitSurfaceView.kt (github), SurfaceView'i geçersiz kılar ve her iki boyutta da etkinliğe eşit veya "biraz daha büyük" bir görüntü kullanarak uyuşmayan en boy oranlarını işler, ardından taşan içeriği kırpar. Bu, önizlemenin etkinliğin tamamını kapsamasını veya görüntüyü bozmadan sabit boyutlu bir görünümü tamamen doldurmasını isteyen uygulamalar için yararlıdır.
Caveat
Önceki örnekte, boş alan kalmaması için önizleme etkinliğin biraz daha büyük yapılarak ekran alanı en üst düzeye çıkarılmaya çalışılıyor. Bu, taşan kısımların varsayılan olarak üst düzendeki (veya ViewGroup) tarafından kırpılmasına dayanır. Bu davranış, RelativeLayout ve LinearLayout ile tutarlıdır ancak ConstraintLayout ile tutarlı DEĞİLDİR. ConstraintLayout, alt görünümleri düzenin içine sığdırmak için yeniden boyutlandırabilir. Bu durum, amaçlanan "ortayı kırpma" efektini bozarak önizlemelerin uzamasına neden olur. Referans olarak bu commit işlemine bakabilirsiniz.
TextureView
TextureView, kamera önizlemesinin içeriği üzerinde maksimum kontrol sağlar ancak performans açısından maliyeti vardır. Ayrıca, kamera önizlemesinin tam olarak doğru şekilde gösterilmesi için daha fazla çalışma yapılması gerekir.
Kaynak
Android platformu, TextureView'un altında, cihazın doğal yönüne uygun olması için çıkış arabelleklerini sensör yönüne göre döndürür. TextureView, sensör yönünü işler ancak ekran döndürmelerini işlemez. Çıkış arabelleklerini cihazın doğal yönüyle hizalar. Bu nedenle, ekran döndürme işlemlerini kendiniz yapmanız gerekir.
Bu durum aşağıdaki tabloda gösterilmektedir. Şekilleri ilgili ekran döndürme işlemine göre döndürmeyi deneyin. SurfaceView'da aynı şekilleri elde edersiniz.
| Ekran döndürme | Telefon (doğal yönlendirme = dikey) | Dizüstü bilgisayar (doğal yön = yatay) |
|---|---|---|
| 0 | ![]() |
![]() |
| 90 | ![]() |
![]() |
| 180) | ![]() |
![]() |
| 270 | ![]() |
![]() |
Düzen
TextureView durumunda düzen biraz karmaşıktır. Daha önce TextureView için bir dönüşüm matrisi kullanılması önerilmişti ancak bu yöntem tüm cihazlarda çalışmaz. Bunun yerine burada açıklanan adımları uygulamanızı öneririz.
TextureView'da önizlemeleri doğru şekilde yerleştirmek için 3 adımlı süreç:
- TextureView'ın boyutunu, seçilen önizleme boyutuyla aynı olacak şekilde ayarlayın.
- Potansiyel olarak gerilmiş TextureView'ı önizlemenin orijinal boyutlarına geri ölçeklendirin.
-
TextureView'ı saat yönünün tersine
displayRotationdöndürün.
Ekran döndürme özelliği 90 derece olan bir telefonunuz olduğunu varsayalım.
1. TextureView'ın boyutunu, seçilen önizleme boyutuyla aynı olacak şekilde ayarlayın.
Seçtiğiniz önizleme boyutunun previewWidth × previewHeight olduğunu varsayalım. Burada previewWidth > previewHeight (sensör çıkışı doğası gereği yatay şekildedir). Bir yakalama oturumu yapılandırırken önizleme boyutunu (previewWidth × previewHeight) belirtmek için SurfaceTexture#setDefaultBufferSize(int width, height) çağrılmalıdır.
setDefaultBufferSize'ı çağırmadan önce View#setLayoutParams(android.view.ViewGroup.LayoutParams) ile TextureView'ın boyutunu da `previewWidth × previewHeight` olarak ayarlamanız önemlidir. Bunun nedeni, TextureView'ın ölçülen genişlik ve yüksekliğiyle SurfaceTexture#setDefaultBufferSize(int width, height) işlevini çağırmasıdır. TextureView'ın boyutu önceden açıkça ayarlanmamışsa yarış durumu oluşabilir. Bu sorun, TextureView'ın boyutu açıkça ayarlanarak giderilir.
Artık TextureView, kaynağın boyutlarıyla eşleşmeyebilir. Telefonlarda kaynak dikey şekilli olsa da TextureView, az önce ayarladığınız layoutParams nedeniyle yatay şekillidir. Bu durum, burada gösterildiği gibi, önizlemelerin uzamasına neden olur:
2. Potansiyel olarak uzatılmış TextureView'ı önizlemenin orijinal boyutlarına geri ölçeklendirme
Genişletilmiş önizlemeyi kaynağın boyutlarına geri ölçeklendirmek için aşağıdakileri göz önünde bulundurun.
Kaynağın boyutları (sourceWidth × sourceHeight) şunlardır:
-
previewHeight × previewWidth, doğal yön dikey veya ters dikey ise (sensör yönü 90 veya 270 derece ise) -
previewWidth × previewHeight, doğal yön yatay veya ters yatay ise (sensör yönü 0 veya 180 derece)
View#setScaleX(float) ve View#setScaleY(float) kullanarak uzamayı düzeltme
-
setScaleX(
sourceWidth / previewWidth) -
setScaleY(
sourceHeight / previewHeight)
3. Önizlemeyi `displayRotation` saat yönünün tersine döndürün.
Daha önce de belirtildiği gibi, ekran döndürmeyi telafi etmek için önizlemeyi displayRotation saat yönünün tersine döndürmelisiniz.
Bu işlemi View#setRotation(float)
-
Saat yönünde döndürme işlemi yaptığından setRotation(
-displayRotation) kullanın.
Örnek
-
Jetpack'teki camerax,
PreviewViewöğesini daha önce açıklandığı gibi TextureView düzeni olarak işler. Dönüşümü PreviewCorrector ile yapılandırır.
Not: Kodunuzda TextureView için daha önce dönüşüm matrisi kullandıysanız önizleme, Chromebook gibi doğal olarak yatay olan bir cihazda doğru görünmeyebilir. Dönüşüm matrisiniz, sensör yönünün 90 veya 270 derece olduğunu yanlış varsayıyor olabilir. Geçici çözüm için GitHub'daki bu commit'e başvurabilirsiniz ancak uygulamanızı buradaki yöntemi kullanacak şekilde taşımanızı önemle tavsiye ederiz.





















