API Camera2 do Android do bule, parte 2, gravando um vídeo



Continuamos a lidar com a API CAMERA2 para Android.
No artigo anterior, dominamos a câmera para tirar fotos usando a nova API. Agora vamos gravar um vídeo. Em geral, inicialmente, meu principal objetivo era transmitir vídeo ao vivo de uma câmera Android usando o Media Codec, mas aconteceu que, a princípio, o Media Recorder entrou em cena e queria compartilhar com o público mais respeitável o quão bem ele pode gravar videoclipes. Portanto, faremos o streaming da próxima vez, mas, por enquanto, descobriremos como adicionar o Media Recorder à nova API. O post sobre ele acabou sendo bastante banal, então apenas iniciantes e bules perfeitos podem olhar embaixo do gato.



So Media Recorder



Como podemos ver pelo próprio nome da classe e pela imagem acima, precisamos do Media Recorder para levar para algum lugar a fonte do áudio ou vídeo ou todos juntos e gravar no final, tudo isso em um arquivo no formato desejado e mais importante acessível.

No nosso caso, a tarefa é simples: pegamos vídeo e áudio da câmera e do microfone e gravamos em um arquivo no formato MPEG_4. Alguns pervertidos costumavam colocar um soquete de rede no Media Recorder em vez de um arquivo para poder conduzir o vídeo pela rede, mas, felizmente, esses tempos de caverna já estão no passado. Faremos o mesmo no próximo artigo, mas, para isso, usaremos o já codificado Media Codec.

Como todos se lembram da API anterior da câmera do final de 2011 , conectar o MediaRecorder não foi difícil. É agradável notar que nenhuma dificuldade surge agora. E não nos assuste com a imagem do esquema completo da câmera.



Só precisamos fixar o gravador de mídia na superfície da superfície na qual a imagem da câmera é exibida e ele fará tudo sozinho. Com o áudio, é ainda mais trivial, basta definir os formatos desejados e o Media Recorder descobrirá por conta própria sem nos incomodar com todos os tipos de retornos de chamada.

Lembre-se da surpresa do amigo japonês desde o último post:

Uma das razões pelas quais a Camera2 está perplexa é quantas chamadas de retorno você precisa usar para tirar uma foto.



E aqui, pelo contrário, é surpreendente como são necessários poucos retornos de chamada para gravar um arquivo de vídeo. Apenas dois. Enquanto Zemfira canta: "Preciso do seu retorno."

E agora vamos escrevê-los

Como fonte, pegamos o código do artigo anterior e jogamos fora tudo relacionado à fotografia e deixamos, de fato, apenas a resolução e a inicialização da câmera. Também deixamos apenas uma câmera - a frente.

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 { //      myCameras = new CameraService[mCameraManager.getCameraIdList().length]; for (String cameraID : mCameraManager.getCameraIdList()) { Log.i(LOG_TAG, "cameraID: " + cameraID); int id = Integer.parseInt(cameraID); //     myCameras[id] = new CameraService(mCameraManager, cameraID); } } catch (CameraAccessException e) { Log.e(LOG_TAG, e.getMessage()); e.printStackTrace(); } public class CameraService { private String mCameraID; private CameraDevice mCameraDevice = null; private CameraCaptureSession mSession; private CaptureRequest.Builder mPreviewBuilder; public CameraService(CameraManager cameraManager, String cameraID) { mCameraManager = cameraManager; mCameraID = cameraID; } private CameraDevice.StateCallback mCameraCallback = new CameraDevice.StateCallback() { @Override public void onOpened(CameraDevice camera) { mCameraDevice = camera; Log.i(LOG_TAG, "Open camera with id:" + mCameraDevice.getId()); //startCameraPreviewSession();        Media Recorder } @Override public void onDisconnected(CameraDevice camera) { mCameraDevice.close(); Log.i(LOG_TAG, "disconnect camera with id:" + mCameraDevice.getId()); mCameraDevice = null; } @Override public void onError(CameraDevice camera, int error) { Log.i(LOG_TAG, "error! camera id:" + camera.getId() + " error:" + error); } }; 

Como podemos ver, a opção RECORD_AUDIO foi adicionada às permissões. Sem ele, o Media Recorder só pode gravar vídeo sem som. E se ainda tentarmos especificar formatos de som sem permissão, ele não será iniciado. Portanto, permitimos gravar sons e outras coisas, lembrando, é claro, que em código real no fluxo principal, fazer essas coisas não é bom, mas apenas na demonstração.

Em seguida, inicialize o próprio gravador de mídia em um 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, "   "); } } 


Também aqui tudo é claro e compreensível, e nenhuma explicação é necessária.

Em seguida, vem o estágio mais crucial - a adição do Media Recorder ao Surface. Na última postagem, exibimos a imagem da câmera na superfície e gravamos um quadro usando o Image Reader. Para fazer isso, simplesmente especificamos os dois componentes na lista Surface.

 Arrays.asList(surface,mImageReader.getSurface()) 


Aqui a mesma coisa, somente em vez de ImageReader especificamos:

 (Arrays.asList(surface, mMediaRecorder.getSurface()). 


Em geral, você pode esculpir qualquer coisa com uma vírgula, todos os componentes que você usa e até o Media Codec. Ou seja, você pode tirar fotos em uma janela, gravar vídeo e transmiti-lo. Superfície boa - permite. É verdade que é possível fazer tudo ao mesmo tempo? Em teoria, a julgar pela imagem da câmera, você pode.




Deveria espalhar-se por diferentes fluxos. Portanto, há um campo para experimentos.

Mas voltando ao gravador de mídia

Quase fizemos tudo. Ao contrário da fotografia, não precisamos de pedidos adicionais para fotografar, não precisamos de nenhum análogo do ImageSaver - nosso gravador trabalha duro faz tudo sozinho. E é legal.

Como resultado, o programa assume uma aparência 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 { //      myCameras = new CameraService[mCameraManager.getCameraIdList().length]; for (String cameraID : mCameraManager.getCameraIdList()) { Log.i(LOG_TAG, "cameraID: " + cameraID); int id = Integer.parseInt(cameraID); //     myCameras[id] = new CameraService(mCameraManager, cameraID); } } catch (CameraAccessException e) { Log.e(LOG_TAG, e.getMessage()); e.printStackTrace(); } setUpMediaRecorder(); } 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, "   "); } } public class CameraService { private String mCameraID; private CameraDevice mCameraDevice = null; private CameraCaptureSession mSession; private CaptureRequest.Builder mPreviewBuilder; public CameraService(CameraManager cameraManager, String cameraID) { mCameraManager = cameraManager; mCameraID = cameraID; } private CameraDevice.StateCallback mCameraCallback = new CameraDevice.StateCallback() { @Override public void onOpened(CameraDevice camera) { mCameraDevice = camera; Log.i(LOG_TAG, "Open camera with id:" + mCameraDevice.getId()); startCameraPreviewSession(); } @Override public void onDisconnected(CameraDevice camera) { mCameraDevice.close(); Log.i(LOG_TAG, "disconnect camera with id:" + mCameraDevice.getId()); mCameraDevice = null; } @Override public void onError(CameraDevice camera, int error) { Log.i(LOG_TAG, "error! camera id:" + camera.getId() + " error:" + error); } }; private void startCameraPreviewSession() { SurfaceTexture texture = mImageView.getSurfaceTexture(); texture.setDefaultBufferSize(640, 480); Surface surface = new Surface(texture); try { mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD); /**Surface for the camera preview set up*/ mPreviewBuilder.addTarget(surface); /**MediaRecorder setup for surface*/ Surface recorderSurface = mMediaRecorder.getSurface(); mPreviewBuilder.addTarget(recorderSurface); mCameraDevice.createCaptureSession(Arrays.asList(surface, mMediaRecorder.getSurface()), new CameraCaptureSession.StateCallback() { @Override public void onConfigured(CameraCaptureSession session) { mSession = session; try { mSession.setRepeatingRequest(mPreviewBuilder.build(), null, mBackgroundHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } @Override public void onConfigureFailed(CameraCaptureSession session) { } }, mBackgroundHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } public void stopRecordingVideo() { try { mSession.stopRepeating(); mSession.abortCaptures(); mSession.close(); } catch (CameraAccessException e) { e.printStackTrace(); } mMediaRecorder.stop(); mMediaRecorder.release(); count++; setUpMediaRecorder(); startCameraPreviewSession(); } public boolean isOpen() { if (mCameraDevice == null) { return false; } else { return true; } } public void openCamera() { try { if (checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { mCameraManager.openCamera(mCameraID, mCameraCallback, mBackgroundHandler); } } catch (CameraAccessException e) { Log.i(LOG_TAG, e.getMessage()); } } } @Override public void onPause() { stopBackgroundThread(); super.onPause(); } @Override public void onResume() { super.onResume(); startBackgroundThread(); } } 


adicione a isso 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> 






E uma pequena adição ao manifesto

 <uses-permission android:name="android.permission.RECORD_AUDIO"/> 


Tudo funciona e grava arquivos com sucesso.
A única coisa é que não há proteção contra o tolo e, portanto, se não for razoável pressionar os botões na tela em uma ordem aleatória, você poderá quebrar tudo.

Source: https://habr.com/ru/post/pt471774/


All Articles