Android Camera2 API aus der Teekanne, Teil 2, schreibt ein Video



Wir beschäftigen uns weiterhin mit der CAMERA2 Android API.
Im vorherigen Artikel haben wir die Kamera beherrscht, um Fotos mit der neuen API aufzunehmen. Jetzt machen wir ein Video. Im Allgemeinen war mein Hauptziel anfangs, Live-Videos von einer Android-Kamera mit Media Codec zu streamen, aber so kam es, dass Media Recorder zuerst auf die Bühne kam und mit dem angesehensten Publikum teilen wollte, wie gut er Videoclips aufnehmen kann. Daher werden wir das nächste Mal streamen, aber jetzt werden wir herausfinden, wie Sie Media Recorder zur neuen API hinzufügen können. Der Beitrag über ihn erwies sich als ziemlich banal, so dass nur Anfänger und perfekte Teekannen unter die Katze schauen können.



Also Media Recorder



Wie wir aus dem Namen der Klasse und dem obigen Bild ersehen können, benötigen wir Media Recorder, um irgendwo die Quelle des Audio- oder Videodateis oder alles zusammen zu nehmen und am Ende alles in einer Datei im gewünschten und vor allem zugänglichen Format aufzunehmen.

In unserem Fall ist die Aufgabe einfach: Wir nehmen Video und Audio von der Kamera und dem Mikrofon auf und schreiben in eine Datei im MPEG_4-Format. Einige Perverse haben früher einen Netzwerk-Socket für Media Recorder anstelle einer Datei gesteckt, um ein Video über das Netzwerk übertragen zu können. Glücklicherweise liegen diese Höhlenzeiten jedoch bereits in der Vergangenheit. Wir werden dasselbe im nächsten Artikel tun, aber dafür nehmen wir den bereits zivilisierten Mediencodec.

Wie sich alle an die vorherige Kamera-API aus dem Jahr 2011 erinnern, war es nicht schwierig, MediaRecorder anzuschließen. Es ist angenehm zu bemerken, dass jetzt keine Schwierigkeit auftritt. Und lassen Sie uns nicht durch das Bild des vollständigen Schemas der Kamera erschrecken.



Wir müssen nur den Media Recorder an der Oberfläche befestigen, auf der das Bild von der Kamera angezeigt wird, und dann erledigt er alles selbst. Mit Audio ist es noch trivialer, stellen Sie einfach die gewünschten Formate ein und Media Recorder wird es selbst herausfinden, ohne uns mit allen Arten von Rückrufen zu ärgern.

Denken Sie daran, wie überrascht der japanische Freund vom letzten Beitrag:

Einer der Gründe, warum Camera2 ratlos ist, ist die Anzahl der Rückrufe, die Sie für eine Aufnahme benötigen.



Und hier ist es im Gegenteil überraschend, wie wenig Rückrufe erforderlich sind, um eine Videodatei aufzunehmen. Nur zwei. Wie Zemfira singt: "Ich brauche deine Rückrufe am wenigsten."

Und jetzt werden wir sie schreiben

Als Quelle nehmen wir den Code aus dem vorherigen Artikel und werfen alles, was mit Fotografieren zu tun hat, weg und lassen tatsächlich nur die Auflösung und Initialisierung der Kamera. Wir lassen auch nur eine Kamera - die Vorderseite.

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); } }; 

Wie wir sehen können, wurde die Berechtigung RECORD_AUDIO zu Berechtigungen hinzugefügt. Ohne sie kann Media Recorder nur nackte Videos ohne Ton aufnehmen. Und wenn wir immer noch versuchen, Soundformate ohne Erlaubnis anzugeben, startet es überhaupt nicht. Daher erlauben wir das Aufnehmen von Sound und anderen Dingen, wobei wir uns natürlich daran erinnern, dass es im realen Code im Hauptstrom nicht gut ist, solche Dinge zu tun, sondern nur in der Demo.

Initialisieren Sie als Nächstes den Media Recorder selbst in einer separaten Methode

  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, "   "); } } 


Auch hier ist alles klar und verständlich und es bedarf keiner Erklärung.

Als nächstes kommt die wichtigste Phase - das Hinzufügen eines Media Recorders zu Surface. Im letzten Beitrag haben wir das Bild von der Kamera auf der Oberfläche angezeigt und mit Image Reader ein Bild damit aufgenommen. Dazu haben wir einfach beide Komponenten in der Oberflächenliste angegeben.

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


Hier das gleiche, nur anstelle von ImageReader geben wir an:

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


Dort können Sie im Allgemeinen alles mit einem Komma, allen von Ihnen verwendeten Komponenten und sogar dem Mediencodec formen. Das heißt, Sie können Fotos in einem Fenster aufnehmen, Videos aufnehmen und streamen. Oberfläche gut - erlaubt. Stimmt es, alles gleichzeitig zu machen? Theoretisch können Sie nach dem Bild der Kamera urteilen.




Es sollte sich nur über verschiedene Ströme verteilen. Es gibt also ein Feld für Experimente.

Aber zurück zum Media Recorder

Fast haben wir alles gemacht. Im Gegensatz zum Fotografieren benötigen wir keine zusätzlichen Anforderungen für die Aufnahme, wir benötigen kein Analogon zu ImageSaver - unser fleißiger Rekorder erledigt alles selbst. Und es ist schön.

Infolgedessen sieht das Programm völlig minimalistisch aus.

 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(); } } 


fügen Sie es LAYOUT hinzu
 <?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> 






Und eine kleine Ergänzung zum Manifest

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


Alles funktioniert und schreibt erfolgreich Dateien.
Das einzige ist, dass es keinen Schutz vor dem Narren gibt. Wenn es daher unvernünftig ist, die Schaltflächen auf dem Bildschirm in zufälliger Reihenfolge zu drücken, können Sie alles kaputt machen.

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


All Articles