
Continuamos lidiando con la API de Android CAMERA2.
En el artículo anterior, dominamos la cámara para tomar fotos usando la nueva API. Ahora tomemos un video. En general, inicialmente, mi objetivo principal era transmitir video en vivo desde una cámara Android usando Media Codec, pero sucedió que al principio Media Recorder entró en escena y quiso compartir con la audiencia más respetable qué tan bien puede grabar videoclips. Por lo tanto, comenzaremos a transmitir la próxima vez, pero por ahora descubriremos cómo agregar Media Recorder a la nueva API. La publicación sobre él resultó ser bastante banal, por lo que solo los principiantes y las teteras perfectas pueden mirar debajo del gato.
So Media Recorder
Como podemos ver en el nombre mismo de la clase y en la imagen de arriba, necesitamos Media Recorder para tomar en algún lugar la fuente del audio o video o todos juntos y grabar al final, todo esto en un archivo en el formato deseado y lo más importante accesible.
En nuestro caso, la tarea es simple: tomamos video y audio de la cámara y el micrófono y escribimos en un archivo en formato MPEG_4. Algunos pervertidos solían deslizar un socket de red para Media Recorder en lugar de un archivo para poder conducir el video a través de la red, pero afortunadamente, estos tiempos de cueva ya están en el pasado. Haremos lo mismo en el próximo artículo, pero para esto tomamos el códec de medios ya civilizado.
Como todos recuerdan de la API de cámara anterior
de lejos de 2011 , conectar MediaRecorder no fue difícil. Es agradable notar que no surge ninguna dificultad ahora. Y no nos asustemos con la imagen del esquema completo de la cámara.

Solo necesitamos sujetar el Grabador de medios a la superficie de la superficie en la que se muestra la imagen de la cámara, y luego él hará todo por sí mismo. Con el audio, es aún más trivial, solo configure los formatos deseados, y Media Recorder lo resolverá por sí solo sin molestarnos con todo tipo de devoluciones de llamada.
Recuerda lo sorprendido que estuvo el amigo japonés de la última publicación:
Una de las razones por las que Camera2 está perplejo es la cantidad de devoluciones de llamada que debe usar para tomar una foto.
Y aquí, por el contrario, es sorprendente cómo
se necesitan
pocas devoluciones de llamada para grabar un archivo de video. Solo dos. Como canta Zemfira: "Necesito tus devoluciones de llamadas lo más mínimo".
Y ahora los escribiremosComo fuente, tomamos el código del artículo anterior y desechamos todo lo relacionado con la fotografía y dejamos, de hecho, solo la resolución e inicialización de la cámara. También dejamos solo una cámara: la frontal.
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 {
Como podemos ver, la opción RECORD_AUDIO se ha agregado a los permisos. Sin él, Media Recorder solo puede grabar videos desnudos sin sonido. Y si aún tratamos de especificar formatos de sonido sin permiso, entonces no comenzará en absoluto. Por lo tanto, permitimos grabar sonido y demás, recordando, por supuesto, que en código real en la transmisión principal, hacer esas cosas no es bueno, pero solo es bueno en la demostración.
Luego, inicialice el Grabador de Medios en un método separado 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, " "); } }
Aquí, también, todo es claro y comprensible y no se requiere explicación.
Luego viene la etapa más crucial: agregar Media Recorder a Surface. En la última publicación, mostramos la imagen de la cámara en la Surface y tomamos un marco con Image Reader. Para hacer esto, simplemente especificamos ambos componentes en la lista de Surface.
Arrays.asList(surface,mImageReader.getSurface())
Aquí lo mismo, solo que en lugar de ImageReader especificamos:
(Arrays.asList(surface, mMediaRecorder.getSurface()).
Allí, en general, puede esculpir cualquier cosa con una coma, todos los componentes que usa e incluso Media Codec. Es decir, puede tomar fotos en una ventana, grabar videos y transmitirlos. Superficie buena - permite. Es cierto, ¿es posible hacer todo al mismo tiempo? En teoría, a juzgar por la imagen de la cámara, puedes hacerlo.

Debería, simplemente, dispersarse en diferentes flujos. Entonces hay un campo para los experimentos.
Pero volvamos a Media RecorderCasi lo hicimos todo. A diferencia de la fotografía, no necesitamos solicitudes adicionales para disparar, no necesitamos ningún análogo de ImageSaver: nuestra grabadora que trabaja duro hace todo por sí mismo. Y es lindo.
Como resultado, el programa adquiere un aspecto completamente minimalista.
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 {
añadir a ello DISEÑO <?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>
Y una pequeña adición al manifiesto <uses-permission android:name="android.permission.RECORD_AUDIO"/>
Todo funciona y escribe archivos con éxito.
Lo único es que no hay protección contra el tonto y, por lo tanto, si no es razonable presionar los botones en pantalla en un orden aleatorio, puede romper todo.