
Kami terus berurusan dengan CAMERA2 Android API.
Pada artikel sebelumnya, kami menguasai kamera untuk mengambil foto menggunakan API baru. Sekarang mari kita ambil video. Secara umum, awalnya, tujuan utama saya adalah melakukan streaming video langsung dari kamera Android menggunakan Media Codec, tetapi kebetulan Media Recorder pertama kali muncul dan ingin berbagi dengan audiens yang paling terhormat seberapa baik dia dapat merekam klip video. Karenanya, kami akan streaming kali berikutnya, tetapi untuk saat ini kami akan mencari cara menambahkan Media Recorder ke API baru. Posting tentang dia ternyata cukup dangkal, jadi hanya pemula dan poci teh sempurna yang bisa melihat ke bawah kucing.
Jadi Media Recorder
Seperti yang dapat kita lihat dari nama kelas dan gambar di atas, kita membutuhkan Media Recorder untuk mengambil sumber audio atau video atau semua bersama-sama dan merekam pada akhirnya, semua ini dalam file dalam format yang diinginkan, dan yang paling penting diakses.
Dalam kasus kami, tugasnya sederhana, kami mengambil video dan audio dari kamera dan mikrofon dan menulis ke file dalam format MPEG_4. Beberapa penyimpang digunakan untuk menyelipkan soket jaringan untuk Media Recorder daripada file agar dapat mengarahkan video melalui jaringan, tetapi untungnya, waktu gua ini sudah di masa lalu. Kami akan melakukan hal yang sama di artikel berikutnya, tetapi untuk ini kami mengambil Media Codec yang sudah beradab.
Karena semua orang ingat dari Camera API sebelumnya
dari jauh 2011 , maka menghubungkan MediaRecorder tidak sulit. Sangat menyenangkan untuk dicatat bahwa tidak ada kesulitan muncul sekarang. Dan jangan sampai kita takut dengan gambar skema penuh kamera.

Kita hanya perlu mengencangkan Perekam Media ke permukaan Permukaan di mana gambar dari kamera ditampilkan, dan kemudian dia akan melakukan semuanya sendiri. Dengan audio, ini bahkan lebih sepele, cukup atur format yang diinginkan, dan Media Recorder akan mengetahuinya sendiri tanpa mengganggu kami dengan semua jenis panggilan balik.
Ingat betapa terkejutnya teman Jepang itu dari pos terakhir:
Salah satu alasan mengapa Camera2 bingung adalah berapa banyak panggilan balik yang perlu Anda gunakan untuk mengambil satu pemotretan.
Dan di sini, sebaliknya, mengejutkan betapa
sedikit callback yang diperlukan untuk merekam file video. Hanya dua. Seperti yang Zemfira nyanyikan: "Aku paling tidak membutuhkan panggilan balikmu."
Dan sekarang kita akan menulisnyaSebagai sumber, kami mengambil kode dari artikel sebelumnya dan membuang semua yang terkait dengan memotret dan meninggalkan, pada kenyataannya, hanya resolusi dan inisialisasi kamera. Kami juga hanya menyisakan satu kamera - bagian depan.
private CameraManager mCameraManager = null; private final int CAMERA1 = 0; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d(LOG_TAG, " "); if (checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED || (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) || (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) ) { requestPermissions(new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO}, 1); } mCameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE); try {
Seperti yang bisa kita lihat, opsi RECORD_AUDIO telah ditambahkan ke izin. Tanpa itu, Perekam Media hanya dapat merekam video telanjang tanpa suara. Dan jika kita masih mencoba menentukan format suara tanpa izin, maka itu tidak akan memulai sama sekali. Oleh karena itu, kami mengizinkan perekaman suara dan barang, mengingat, tentu saja, bahwa dalam kode nyata di arus utama, melakukan hal-hal seperti itu tidak baik, tetapi hanya baik di demo.
Selanjutnya, inisialisasi Perekam Media itu sendiri dalam metode terpisah private void setUpMediaRecorder() { mMediaRecorder = new MediaRecorder(); mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); mCurrentFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), "test"+count+".mp4"); mMediaRecorder.setOutputFile(mCurrentFile.getAbsolutePath()); CamcorderProfile profile = CamcorderProfile.get(CamcorderProfile.QUALITY_480P); mMediaRecorder.setVideoFrameRate(profile.videoFrameRate); mMediaRecorder.setVideoSize(profile.videoFrameWidth, profile.videoFrameHeight); mMediaRecorder.setVideoEncodingBitRate(profile.videoBitRate); mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); mMediaRecorder.setAudioEncodingBitRate(profile.audioBitRate); mMediaRecorder.setAudioSamplingRate(profile.audioSampleRate); try { mMediaRecorder.prepare(); Log.i(LOG_TAG, " "); } catch (Exception e) { Log.i(LOG_TAG, " "); } }
Di sini juga, semuanya jelas dan dapat dimengerti dan tidak ada penjelasan yang diperlukan.
Berikutnya adalah tahap yang paling penting - menambahkan Media Recorder ke Surface. Dalam posting terakhir, kami menampilkan gambar dari kamera di Permukaan dan mengambil bingkai dengannya menggunakan Image Reader. Untuk melakukan ini, kami cukup menentukan kedua komponen dalam daftar Permukaan.
Arrays.asList(surface,mImageReader.getSurface())
Di sini hal yang sama, hanya alih-alih ImageReader kita tentukan:
(Arrays.asList(surface, mMediaRecorder.getSurface()).
Di sana, secara umum, Anda dapat memahat apa pun dengan koma, semua komponen yang Anda gunakan dan bahkan Media Codec. Artinya, Anda dapat mengambil foto dalam satu jendela, merekam video, dan streaming. Permukaan bagus - memungkinkan. Benar, mungkinkah melakukan semuanya sekaligus? Saya tidak akan memberi tahu Anda hal ini. Secara teori, dilihat dari gambar kamera, Anda bisa.

Seharusnya, seperti, hanya tersebar di aliran yang berbeda. Jadi ada bidang untuk eksperimen.
Tetapi kembali ke Media RecorderHampir kami melakukan semuanya. Tidak seperti memotret, kami tidak memerlukan permintaan tambahan untuk pemotretan, kami tidak memerlukan analog dari ImageSaver - perekam kami yang bekerja keras melakukan semuanya sendiri. Dan itu bagus.
Hasilnya, program ini terlihat sangat minimalis.
package com.example.mediarecorder1; import androidx.appcompat.app.AppCompatActivity; import androidx.core.content.ContextCompat; import android.Manifest; import android.content.Context; import android.content.pm.PackageManager; import android.graphics.SurfaceTexture; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CameraManager; import android.hardware.camera2.CaptureRequest; import android.media.CamcorderProfile; import android.os.Bundle; import android.media.MediaRecorder; import android.os.Environment; import android.os.Handler; import android.os.HandlerThread; import android.util.Log; import android.view.Surface; import android.view.TextureView; import android.view.View; import android.widget.Button; import java.io.File; import java.util.Arrays; public class MainActivity extends AppCompatActivity { public static final String LOG_TAG = "myLogs"; CameraService[] myCameras = null; private CameraManager mCameraManager = null; private final int CAMERA1 = 0; private int count =1; private Button mButtonOpenCamera1 = null; private Button mButtonRecordVideo = null; private Button mButtonStopRecordVideo = null; public static TextureView mImageView = null; private HandlerThread mBackgroundThread; private Handler mBackgroundHandler = null; private File mCurrentFile; private MediaRecorder mMediaRecorder = null; private void startBackgroundThread() { mBackgroundThread = new HandlerThread("CameraBackground"); mBackgroundThread.start(); mBackgroundHandler = new Handler(mBackgroundThread.getLooper()); } private void stopBackgroundThread() { mBackgroundThread.quitSafely(); try { mBackgroundThread.join(); mBackgroundThread = null; mBackgroundHandler = null; } catch (InterruptedException e) { e.printStackTrace(); } } protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d(LOG_TAG, " "); if (checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED || (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) || (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) ) { requestPermissions(new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO}, 1); } mButtonOpenCamera1 = findViewById(R.id.button1); mButtonRecordVideo = findViewById(R.id.button2); mButtonStopRecordVideo = findViewById(R.id.button3); mImageView = findViewById(R.id.textureView); mButtonOpenCamera1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (myCameras[CAMERA1] != null) { if (!myCameras[CAMERA1].isOpen()) myCameras[CAMERA1].openCamera(); } } }); mButtonRecordVideo.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if ((myCameras[CAMERA1] != null) & mMediaRecorder != null) { mMediaRecorder.start(); } } }); mButtonStopRecordVideo.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if ((myCameras[CAMERA1] != null) & (mMediaRecorder != null)) { myCameras[CAMERA1].stopRecordingVideo(); } } }); mCameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE); try {
tambahkan LAYOUT <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <TextureView android:id="@+id/textureView" android:layout_width="356dp" android:layout_height="410dp" android:layout_marginTop="32dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.49" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <LinearLayout android:layout_width="292dp" android:layout_height="145dp" android:layout_marginStart="16dp" android:orientation="vertical" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/textureView" app:layout_constraintVertical_bias="0.537"> <Button android:id="@+id/button1" android:layout_width="match_parent" android:layout_height="wrap_content" android:text=" " /> <Button android:id="@+id/button2" android:layout_width="match_parent" android:layout_height="wrap_content" android:text=" " /> <Button android:id="@+id/button3" android:layout_width="match_parent" android:layout_height="wrap_content" android:text=" " /> </LinearLayout> </androidx.constraintlayout.widget.ConstraintLayout>
Dan sedikit tambahan untuk manifes <uses-permission android:name="android.permission.RECORD_AUDIO"/>
Semuanya berfungsi dan berhasil menulis file.
Satu-satunya hal adalah bahwa tidak ada perlindungan dari si bodoh, dan oleh karena itu, jika tidak masuk akal untuk menyodok tombol di layar dalam urutan acak, maka Anda dapat menghancurkan semuanya.