Bu sayfada, CameraX'in yapısı, API ile çalışma, yaşam döngüleriyle çalışma ve kullanım alanlarını birleştirme gibi konular ele alınmaktadır.
CameraX yapısı
Kullanım alanı adı verilen bir soyutlama aracılığıyla bir cihazın kamerasıyla arayüz oluşturmak için CameraX'i kullanabilirsiniz. Aşağıdaki kullanım alanları mevcuttur:
- Önizleme: Önizleme görüntülemek için bir yüzeyi kabul eder (örneğin,
PreviewView
. - Görüntü analizi: Analiz için CPU tarafından erişilebilen arabellekler sağlar. Örneğin, bir ekiple çalışıyorum.
- Resim çekme: Fotoğraf çeker ve kaydeder.
- Video yakalama:
VideoCapture
ile video ve ses kaydı yapın
Kullanım alanları eşzamanlı olarak birleştirilebilir ve etkin olabilir. Örneğin, bir uygulamada önizleme kullanım örneğiyle kullanıcının kameranın gördüğü görüntüyü görüntülemesine izin verilebilir, fotoğraftaki kişilerin gülümseyip gülümsemediğini belirleyen bir görüntü analizi kullanım örneği bulunabilir ve gülümsedikleri anda fotoğraf çekmek için bir görüntü yakalama kullanım örneği eklenebilir.
API modeli
Kitaplıkta çalışmak için şunları belirtirsiniz:
- Yapılandırma seçenekleriyle istenen kullanım alanı.
- Dinleyici ekleyerek çıkış verileriyle ne yapmalı?
- Kameraların ne zaman etkinleştirileceği ve verilerin ne zaman üretileceği gibi istenen akış kullanım alanını Android Mimarisi'ne Yaşam döngüleri.
CameraX uygulaması yazmanın 2 yolu vardır:
CameraController
(şu durumlarda idealdir:
KameraX'i kullanmanın en basit yolunu istiyorsanız) veya
CameraProvider
(şu durumda harikadır:
daha fazla esnekliğe ihtiyaç duyar).
Kamera Kumandası
CameraController
, CameraX temel işlevlerinin çoğunu tek bir sınıfta sağlar. Az miktarda kurulum kodu gerektirir ve kamerayı başlatma, kullanım alanı yönetimi, hedef döndürme, dokunarak odaklama, iki parmak ucunu yakınlaştırmak için yakınlaştırma gibi işlemleri otomatik olarak yönetir. CameraController
öğesini genişleten beton sınıf:
LifecycleCameraController
Kotlin
val previewView: PreviewView = viewBinding.previewView var cameraController = LifecycleCameraController(baseContext) cameraController.bindToLifecycle(this) cameraController.cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA previewView.controller = cameraController
Java
PreviewView previewView = viewBinding.previewView; LifecycleCameraController cameraController = new LifecycleCameraController(baseContext); cameraController.bindToLifecycle(this); cameraController.setCameraSelector(CameraSelector.DEFAULT_BACK_CAMERA); previewView.setController(cameraController);
CameraController
için varsayılan UseCase
değerleri Preview
, ImageCapture
ve
ImageAnalysis
. ImageCapture
veya ImageAnalysis
'ü kapatmak ya da VideoCapture
'yi açmak için setEnabledUseCases()
yöntemini kullanın.
CameraController
'ün diğer kullanımları için QR kodu tarayıcı örneğine veya CameraController
ile ilgili temel bilgiler videosuna göz atın.
CameraProvider
CameraProvider
'ler yine de kullanımı kolaydır ancak kurulumun büyük bir kısmı uygulama geliştirici tarafından yapıldığından, yapılandırmanızı özelleştirmek için daha fazla fırsat vardır (ör. çıkış resminin döndürülmesini etkinleştirme veya ImageAnalysis
'te çıkış resmi biçimini ayarlama). Ayrıca kamera önizlemesi için özel bir Surface
kullanabilirsiniz.
için kamera Denetleyicisi'ni kullanmanız gerekir. CameraController'da
PreviewView
. Mevcut Surface
kodunuz, uygulamanızın diğer bölümlerinde zaten giriş olarak kullanılıyorsa faydalı olabilir.
Kullanım alanlarını set()
yöntemleriyle yapılandırır ve build()
ile sonlandırırsınız.
yöntemidir. Her kullanım alanı nesnesi, kullanım alanına özel bir dizi API sağlar. Örneğin,
Örneğin, görüntü yakalama kullanım alanı bir takePicture()
yöntem çağrısı sağlar.
Bir uygulama tarafından belirli bir başlatma ve durdurma yöntemi
onResume()
ve onPause()
; uygulama, ilişkilendirilecek bir yaşam döngüsü belirler
kullanarak,
cameraProvider.bindToLifecycle()
.
Bu yaşam döngüsü daha sonra CameraX'e kamera yakalama oturumunun ne zaman yapılandırılacağını bildirir ve kamera durumunun yaşam döngüsü geçişleriyle eşleşecek şekilde uygun şekilde değişmesini sağlar.
Her kullanım alanı için uygulama adımları Önizleme uygulama, Resimleri analiz etme, Resim yakalama ve Video yakalama başlıklı makalelere göz atın.
Önizleme kullanım alanı, görüntüleme için bir Surface
ile etkileşim kurar. Uygulamalar
aşağıdaki kodu kullanarak yapılandırma seçenekleriyle kullanım alanını oluşturun:
Kotlin
val preview = Preview.Builder().build() val viewFinder: PreviewView = findViewById(R.id.previewView) // The use case is bound to an Android Lifecycle with the following code val camera = cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview) // PreviewView creates a surface provider and is the recommended provider preview.setSurfaceProvider(viewFinder.getSurfaceProvider())
Java
Preview preview = new Preview.Builder().build(); PreviewView viewFinder = findViewById(R.id.view_finder); // The use case is bound to an Android Lifecycle with the following code Camera camera = cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview); // PreviewView creates a surface provider, using a Surface from a different // kind of view will require you to implement your own surface provider. preview.previewSurfaceProvider = viewFinder.getSurfaceProvider();
Daha fazla örnek kod için resmi CameraX örneğine bakın. uygulamasında bulabilirsiniz.
CameraX Yaşam Döngüleri
CameraX, kameranın ne zaman açılacağını ve ne zaman açılacağını belirlemek için bir yaşam döngüsü gözlemler yakalama oturumu oluşturabileceğinizi ve ne zaman durdurulup kapatılacağını öğreneceksiniz. Kullanım alanı API'leri, ilerlemeyi izlemek için yöntem çağrıları ve geri çağırma sağlar.
Kullanım alanlarını birleştirme bölümünde açıklandığı gibi, bazı kullanım alanı karışımlarını tek bir yaşam döngüsüne bağlayabilirsiniz. Uygulamanızın birleştirilemeyen kullanım alanlarını desteklemesi gerektiğinde aşağıdakilerden birini yapabilirsiniz:
- Uyumlu kullanım alanlarını birden fazla olacak şekilde gruplandırın parça ve ardından parçalar
- Özel bir yaşam döngüsü bileşeni oluşturun ve kamera yaşam döngüsünü manuel olarak kontrol etmek için kullanın
Görüntü ve kamera kullanım alanlarınızın yaşam döngüsü sahiplerini ayırırsanız (ör. özel bir yaşam döngüsü veya retain fragment kullanıyorsanız) ProcessCameraProvider.unbindAll()
kullanarak veya her kullanım alanının bağlamasını tek tek kaldırarak tüm kullanım alanlarının CameraX'ten ayrıldığından emin olmanız gerekir. Alternatif olarak,
bir Yaşam Döngüsü olarak kullanabilirsiniz, CameraX'ın
yakalama oturumunun açılıp kapatılmasını ve kullanım alanlarının bağlantısını kaldırmayı yönetebilir.
Kameranızın tüm işlevleri tek bir cihazın yaşam döngüsüne karşılık geliyorsa
yaşam döngüsüne duyarlı bileşen
AppCompatActivity
veya bir
AppCompat
parçası, daha sonra bağlama sırasında o bileşenin yaşam döngüsü kullanılıyor
tüm kullanım alanları, kamera işlevselliğinin hazır olmasını sağlar.
akıllı bileşen etkin olduğunda ve güvenli bir şekilde imha edildiğinde
gerektiğini unutmayın.
Özel LifecycleOwner'lar
İleri düzey destek kayıtları için özel
LifecycleOwner
kameraX oturum yaşam döngüsünü açıkça denetlemenize olanak tanıyan bir uygulama
standart Android LifecycleOwner
.
Aşağıdaki kod örneğinde, basit bir özel LifecycleOwner'ın nasıl oluşturulacağı gösterilmektedir:
Kotlin
class CustomLifecycle : LifecycleOwner { private val lifecycleRegistry: LifecycleRegistry init { lifecycleRegistry = LifecycleRegistry(this); lifecycleRegistry.markState(Lifecycle.State.CREATED) } ... fun doOnResume() { lifecycleRegistry.markState(State.RESUMED) } ... override fun getLifecycle(): Lifecycle { return lifecycleRegistry } }
Java
public class CustomLifecycle implements LifecycleOwner { private LifecycleRegistry lifecycleRegistry; public CustomLifecycle() { lifecycleRegistry = new LifecycleRegistry(this); lifecycleRegistry.markState(Lifecycle.State.CREATED); } ... public void doOnResume() { lifecycleRegistry.markState(State.RESUMED); } ... public Lifecycle getLifecycle() { return lifecycleRegistry; } }
Bu LifecycleOwner
sayesinde uygulamanız, kodunun istenen noktalarına durum geçişleri yerleştirebilir. Bu işlevi uygulamanızda kullanma hakkında daha fazla bilgi için
bkz. Özel
LifecycleOwner (İş Döngüsü Sahibi) gibi adımları inceleyin.
Eşzamanlı kullanım alanları
Kullanım alanları aynı anda çalışabilir. Kullanım alanları sıralı olarak bir
yaşam döngüsü boyunca, tek bir çağrı ile tüm kullanım alanlarını
CameraProcessProvider.bindToLifecycle()
En iyi uygulamalar hakkında daha fazla bilgi için
daha fazla bilgi için Herkese açık kullanıcı adı yapılandırması
değişiklikler başlıklı makaleyi inceleyin.
Aşağıdaki kod örneğinde uygulama, oluşturulacak iki kullanım alanını belirtir aynı anda çalışır. Ayrıca, her iki kullanım alanı için de kullanılacak yaşam döngüsünü belirtir. Böylece her ikisi de yaşam döngüsüne göre başlatılır ve durdurulur.
Kotlin
private lateinit var imageCapture: ImageCapture override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val cameraProviderFuture = ProcessCameraProvider.getInstance(this) cameraProviderFuture.addListener(Runnable { // Camera provider is now guaranteed to be available val cameraProvider = cameraProviderFuture.get() // Set up the preview use case to display camera preview. val preview = Preview.Builder().build() // Set up the capture use case to allow users to take photos. imageCapture = ImageCapture.Builder() .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) .build() // Choose the camera by requiring a lens facing val cameraSelector = CameraSelector.Builder() .requireLensFacing(CameraSelector.LENS_FACING_FRONT) .build() // Attach use cases to the camera with the same lifecycle owner val camera = cameraProvider.bindToLifecycle( this as LifecycleOwner, cameraSelector, preview, imageCapture) // Connect the preview use case to the previewView preview.setSurfaceProvider( previewView.getSurfaceProvider()) }, ContextCompat.getMainExecutor(this)) }
Java
private ImageCapture imageCapture; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); PreviewView previewView = findViewById(R.id.previewView); ListenableFuture<ProcessCameraProvider> cameraProviderFuture = ProcessCameraProvider.getInstance(this); cameraProviderFuture.addListener(() -> { try { // Camera provider is now guaranteed to be available ProcessCameraProvider cameraProvider = cameraProviderFuture.get(); // Set up the view finder use case to display camera preview Preview preview = new Preview.Builder().build(); // Set up the capture use case to allow users to take photos imageCapture = new ImageCapture.Builder() .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) .build(); // Choose the camera by requiring a lens facing CameraSelector cameraSelector = new CameraSelector.Builder() .requireLensFacing(lensFacing) .build(); // Attach use cases to the camera with the same lifecycle owner Camera camera = cameraProvider.bindToLifecycle( ((LifecycleOwner) this), cameraSelector, preview, imageCapture); // Connect the preview use case to the previewView preview.setSurfaceProvider( previewView.getSurfaceProvider()); } catch (InterruptedException | ExecutionException e) { // Currently no exceptions thrown. cameraProviderFuture.get() // shouldn't block since the listener is being called, so no need to // handle InterruptedException. } }, ContextCompat.getMainExecutor(this)); }
CameraX, Preview
, VideoCapture
, ImageAnalysis
ve ImageCapture
için aynı anda birer örneğin kullanılmasına olanak tanır. Ayrıca,
- Her kullanım alanı kendi başına çalışabilir. Örneğin, bir uygulama video kaydedebilir önizleme özelliğini kullanabilirsiniz.
- Uzantılar etkinleştirildiğinde yalnızca
ImageCapture
vePreview
kombinasyonun çalışacağı garanti edilir. OEM uygulamasına bağlı olarak,ImageAnalysis
eklemek mümkün olmayabilir; uzantılarVideoCapture
kullanım alanı için etkinleştirilmelidir. Ayrıntılar için Uzantı referans dokümanlarını inceleyin. - Kamera kapasitesine bağlı olarak, bazı kameralar daha düşük çözünürlük modları vardır, ancak bazı cihazlarda aynı kombinasyonu destekleyemez. kullanabilirsiniz.
- Kamera donanım düzeyi
FULL
veya daha düşük olan cihazlardaPreview
,VideoCapture
veImageCapture
veyaImageAnalysis
seçeneklerinin birlikte kullanılması, CameraX'in kameranınPRIV
yayınınıPreview
veVideoCapture
için kopyalamasını zorunlu kılabilir. Akış paylaşımı olarak adlandırılan bu kopyalama, bu özelliklerin eş zamanlı kullanımına olanak tanır ancak işleme taleplerinin artması pahasına gelir. En son haberleri sunan, gecikme nedeniyle biraz daha uzun bir gecikme ve daha az pil ömrüyle karşılaşabilirsiniz.
Desteklenen donanım düzeyi
Camera2CameraInfo
adresinden alınabilir. Örneğin, aşağıdaki kod
varsayılan arka kameranın LEVEL_3
cihaz olup olmadığını kontrol eder:
Kotlin
@androidx.annotation.OptIn(ExperimentalCamera2Interop::class) fun isBackCameraLevel3Device(cameraProvider: ProcessCameraProvider) : Boolean { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { return CameraSelector.DEFAULT_BACK_CAMERA .filter(cameraProvider.availableCameraInfos) .firstOrNull() ?.let { Camera2CameraInfo.from(it) } ?.getCameraCharacteristic(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL) == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3 } return false }
Java
@androidx.annotation.OptIn(markerClass = ExperimentalCamera2Interop.class) Boolean isBackCameraLevel3Device(ProcessCameraProvider cameraProvider) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { List\<CameraInfo\> filteredCameraInfos = CameraSelector.DEFAULT_BACK_CAMERA .filter(cameraProvider.getAvailableCameraInfos()); if (!filteredCameraInfos.isEmpty()) { return Objects.equals( Camera2CameraInfo.from(filteredCameraInfos.get(0)).getCameraCharacteristic( CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL), CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3); } } return false; }
İzinler
Uygulamanızın CAMERA
iznine ihtiyacı vardır. Android 10 veya sonraki sürümleri çalıştıran cihazlar hariç olmak üzere, resimleri dosyalara kaydetmek için WRITE_EXTERNAL_STORAGE
izni de gerekir.
Uygulamanızın izinlerini yapılandırma hakkında daha fazla bilgi için İstekte bulunma Uygulama İzinleri.
Şartlar
CameraX aşağıdaki minimum sürüm gereksinimlerine sahiptir:
- Android API düzeyi 21
- Android Mimari Bileşenleri 1.1.1
Yaşam döngüsü bilincine sahip etkinlikler için FragmentActivity
veya AppCompatActivity
değerini kullanın.
Bağımlılıkları bildirme
CameraX bağımlılığı eklemek için projenize Google Maven kod deposunu eklemeniz gerekir.
Projenizin settings.gradle
dosyasını açın ve aşağıdaki gibi google()
deposunu ekleyin:
Eski
dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() } }
Kotlin
dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() } }
Aşağıdakini Android bloğunun sonuna ekleyin:
Groovy
android { compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } // For Kotlin projects kotlinOptions { jvmTarget = "1.8" } }
Kotlin
android { compileOptions { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 } // For Kotlin projects kotlinOptions { jvmTarget = "1.8" } }
Uygulama için her modülün build.gradle
dosyasına aşağıdakileri ekleyin:
Groovy
dependencies { // CameraX core library using the camera2 implementation def camerax_version = "1.5.0-alpha03" // The following line is optional, as the core library is included indirectly by camera-camera2 implementation "androidx.camera:camera-core:${camerax_version}" implementation "androidx.camera:camera-camera2:${camerax_version}" // If you want to additionally use the CameraX Lifecycle library implementation "androidx.camera:camera-lifecycle:${camerax_version}" // If you want to additionally use the CameraX VideoCapture library implementation "androidx.camera:camera-video:${camerax_version}" // If you want to additionally use the CameraX View class implementation "androidx.camera:camera-view:${camerax_version}" // If you want to additionally add CameraX ML Kit Vision Integration implementation "androidx.camera:camera-mlkit-vision:${camerax_version}" // If you want to additionally use the CameraX Extensions library implementation "androidx.camera:camera-extensions:${camerax_version}" }
Kotlin
dependencies { // CameraX core library using the camera2 implementation val camerax_version = "1.5.0-alpha03" // The following line is optional, as the core library is included indirectly by camera-camera2 implementation("androidx.camera:camera-core:${camerax_version}") implementation("androidx.camera:camera-camera2:${camerax_version}") // If you want to additionally use the CameraX Lifecycle library implementation("androidx.camera:camera-lifecycle:${camerax_version}") // If you want to additionally use the CameraX VideoCapture library implementation("androidx.camera:camera-video:${camerax_version}") // If you want to additionally use the CameraX View class implementation("androidx.camera:camera-view:${camerax_version}") // If you want to additionally add CameraX ML Kit Vision Integration implementation("androidx.camera:camera-mlkit-vision:${camerax_version}") // If you want to additionally use the CameraX Extensions library implementation("androidx.camera:camera-extensions:${camerax_version}") }
Uygulamanızı bu şartlara uygun olacak şekilde yapılandırma hakkında daha fazla bilgi için Bağımlılık beyanı başlıklı makaleyi inceleyin.
Camera2 ile CameraX birlikte çalışabilirliği
CameraX, Camera2'yi temel alır ve Camera2 uygulamasında özellikleri okumanın ve hatta yazmanın yollarını gösterir. Tüm ayrıntılar için bkz. Birlikte çalışabilirlik paketi.
CameraX'ın Camera2 özelliklerini nasıl yapılandırdığı hakkında daha fazla bilgi için
Camera2CameraInfo
CameraCharacteristics
Kamera2'nin temelini belirten bir
özellikleri aşağıdaki iki yoldan birinde kullanabilirsiniz:
Camera2CameraControl
kullanın, Bu, temel alınan öğelere ilişkin özellikleriCaptureRequest
, örneğin otomatik odaklama modu gibi ayarlar.Bir CameraX
UseCase
öğesiniCamera2Interop.Extender
ile genişletin. Bu sayede, CaptureRequest'te tıpkıCamera2CameraControl
gibi özellikler ayarlayabilirsiniz. Ayrıca akış kullanım alanını kullanım senaryonuza göre optimize etmenizi sağlar. Daha fazla bilgi için Akışla ilgili kullanım alanlarını kullanma performans metriğine bakın.
Aşağıdaki kod örneğinde, görüntülü görüşme için optimizasyon yapmak amacıyla akış kullanım alanları kullanılmaktadır.
Video görüşmesi akışı kullanım alanının kullanılabilir olup olmadığını almak için Camera2CameraInfo
değerini kullanın. Ardından, temel akış kullanım alanını ayarlamak için Camera2Interop.Extender
simgesini kullanın.
Kotlin
// Set underlying Camera2 stream use case to optimize for video calls. val videoCallStreamId = CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_CALL.toLong() // Check available CameraInfos to find the first one that supports // the video call stream use case. val frontCameraInfo = cameraProvider.getAvailableCameraInfos() .first { cameraInfo -> val isVideoCallStreamingSupported = Camera2CameraInfo.from(cameraInfo) .getCameraCharacteristic( CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES )?.contains(videoCallStreamId) val isFrontFacing = (cameraInfo.getLensFacing() == CameraSelector.LENS_FACING_FRONT) (isVideoCallStreamingSupported == true) && isFrontFacing } val cameraSelector = frontCameraInfo.cameraSelector // Start with a Preview Builder. val previewBuilder = Preview.Builder() .setTargetAspectRatio(screenAspectRatio) .setTargetRotation(rotation) // Use Camera2Interop.Extender to set the video call stream use case. Camera2Interop.Extender(previewBuilder).setStreamUseCase(videoCallStreamId) // Bind the Preview UseCase and the corresponding CameraSelector. val preview = previewBuilder.build() camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview)
Java
// Set underlying Camera2 stream use case to optimize for video calls. Long videoCallStreamId = CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_CALL.toLong(); // Check available CameraInfos to find the first one that supports // the video call stream use case. List<CameraInfo> cameraInfos = cameraProvider.getAvailableCameraInfos(); CameraInfo frontCameraInfo = null; for (cameraInfo in cameraInfos) { Long[] availableStreamUseCases = Camera2CameraInfo.from(cameraInfo) .getCameraCharacteristic( CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES ); boolean isVideoCallStreamingSupported = Arrays.List(availableStreamUseCases) .contains(videoCallStreamId); boolean isFrontFacing = (cameraInfo.getLensFacing() == CameraSelector.LENS_FACING_FRONT); if (isVideoCallStreamingSupported && isFrontFacing) { frontCameraInfo = cameraInfo; } } if (frontCameraInfo == null) { // Handle case where video call streaming is not supported. } CameraSelector cameraSelector = frontCameraInfo.getCameraSelector(); // Start with a Preview Builder. Preview.Builder previewBuilder = Preview.Builder() .setTargetAspectRatio(screenAspectRatio) .setTargetRotation(rotation); // Use Camera2Interop.Extender to set the video call stream use case. Camera2Interop.Extender(previewBuilder).setStreamUseCase(videoCallStreamId); // Bind the Preview UseCase and the corresponding CameraSelector. Preview preview = previewBuilder.build() Camera camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview)
Ek kaynaklar
CameraX hakkında daha fazla bilgi edinmek için aşağıdaki ek kaynaklara göz atın.
Kod Laboratuvarı
Kod örneği