Pull to refresh

Android Camera2 API от чайника, часть 2, пишем видео

Reading time18 min
Views9.3K


Продолжаем разбираться с CAMERA2 API Android.
В предыдущей статье мы осваивали работу камеры, чтобы делать фоточки, используя новое API. Теперь же займёмся съемкой видео. Вообще изначально, главной моей целью был стрим по сети живого видео с камеры Android при помощи Media Codec, но так уж вышло, что сначала на сцену вылез Media Recorder и захотел поделиться с почтеннейшей публикой тем, как хорошо он умеет записывать видосики. Поэтому стримингом мы займёмся в следующий раз, а пока разберёмся, как присобачить Media Recorder к новому API. Пост про него получился довольно банальным, поэтому под кат могут заглядывать только новички и совершеннейшие чайники.



Итак, Media Recorder



Как мы видим из самого названия класса и приведённой выше картинки, Media Recorder нужен нам для того, чтобы взять где-то источник аудио или видео или всё вместе и записать в итоге, всё это в файл в желаемом, а главное доступном формате.

В нашем случае задача простая, берем видео и аудио с камеры и микрофона и пишем в файл в формате MPEG_4. Некоторые извращенцы бывало подсовывали для Media Recorder вместо файла сетевой сокет, чтобы иметь возможность гнать видос по сети, но к счастью, эти пещерные времена уже в прошлом. Мы займемся подобным в следующей статье, но возьмём для этого уже цивилизованный Media Codec.

Как все помнят по предыдущему Camera API из далекого 2011, тогда подключение MediaRecorder не составляло никакой сложности. Приятно отметить, никакой сложности не возникает и теперь. И пусть нас не пугает картинка полной схемы работы камеры.



Нам всего лишь нужно пристегнуть Media Recorder к поверхности Surface на которую выводится изображение с камеры, а дальше он всё сделает сам. С аудио ещё тривиальнее, просто задаем нужные форматы, и Media Recorder разберется со звуком самостоятельно, не докучая нам всякими коллбэками.

Помните, как удивлялся японский товарищ из прошлого поста:

Одна из причин почему Camera2 приводит в недоумение, это то насколько много коллбэков надо использовать, чтобы сделать один снимок.



А здесь, наоборот, удивительно то, насколько мало коллбэков нужно чтобы записать видео файл. Всего два. Как поёт Земфира:«Меньше всего нужны мне твои коллбэки».

И сейчас мы их напишем

Как исходное, возьмём код из прошлой статьи и выкинем из него всё, что относится к фотографированию и оставим, по сути, лишь разрешения и инициализацию камеры. Камеру тоже оставим всего одну — фронтальную.

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

Как мы видим, в разрешениях прибавилась опция RECORD_AUDIO. Без него Media Recorder сможет записать только голое видео без звука. А если мы попытаемся все-таки указать звуковые форматы без разрешения, то он не запустится вообще. Поэтому разрешаем запись звука и прочее, помня, конечно, о том что в реальном коде в главном потоке такие вещи делать нехорошо, а хорошо только в демонстрационном.

Далее инициализируем сам Media Recorder в отдельном методе

 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, "не запустили медиа рекордер");
        }


    }


Тут тоже всё ясно-понятно и пояснений давать не требуется.

Далее наступает самый ответственный этап — присобачивание Media Recorder к Surface. В прошлом посте мы выводили на Surface изображение с камеры и с него же снимали кадр при помощи Image Reader. Для этого мы просто указывали оба компонента в списке Surface.

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


Здесь то же самое, только вместо ImageReader указываем:

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



Там вообще, через запятую, можно что угодно лепить, все используемые вами компоненты и даже Media Codec. То есть, вы можете в одном окне делать фотки, снимать видео и стримить его. Surface добрый — позволяет. Правда, можно ли всё делать одновременно, этого не подскажу. По идее, судя по картинке работы камеры — можно.




Должно, вроде как, просто разлетаться по разным потокам. Так что поле для экспериментов есть.

Но вернёмся к Media Recorder

Практически мы сделали всё. Нам не нужно в отличие от фотографирования никаких дополнительных реквестов для съёмки, не нужен никакой аналог ImageSaver – наш работяга рекордер делает всё сам. И это приятно.

В итоге программа приобретает совершенно минималистический вид.

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

    }


}


добавляем к ней 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>







И небольшое дополнение в манифест

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



Всё работает и успешно пишет файлы.
Единственное, защиты от дурака нет и поэтому, если неразумно тыкать в экранные кнопки в рандомном порядке, то можно всё сломать.

P.S.

После опубликования поста пытливым читателем было замечено существование заметного лага изображения с камеры после нажатия кнопки, «закончить запись», перед тем, как вы пожелаете сделать запись новую. Тогда как, попробовав сделать то же самое на стандартной камере Android, вы никаких лагов не увидите, все будет вполне себе плавненько.

Как оказалось, задержка происходит по двум причинам.
Первая — это пересоздание сессии mSession. Как вы понимаете, на новую инициализацию камеры требуется время.
Вторая — основная задержка по времени получалась из-за того, что остановка медиарекордера mMediaRecorder.stop(), оказывается, выполняется совсем не асинхронно, как я наивно надеялся, а вполне себе вешает главный поток. И по идее, должна быть вынесена в фон. Но на самом деле и фон тут не поможет, так как пересоздание mSession все равно требует нового экземпляра mMediaRecorder, а мы его никак не получим, пока не остановим старый. Конечно, можно попробовать запускать некий mMediaRecorder2, пока закрываем mMediaRecorder1, но это подход индусский и ненадежный, так как:

Поведение нескольких экземпляров MediaRecorder является неопределенным afaik и может работать или не работать в зависимости от устройства.

В документации не упоминается, поддерживаются ли несколько экземпляров:

developer.android.com/reference/android/media/MediaRecorder.html

Есть жалобы о сбоях при создании экземпляров более одного экземпляра.


Может это и до сих пор так, а может ситуация и изменилась с тех пор, но в любом случае выход есть. Правда, с другой стороны.

Мы попробуем не закрывать CameraPreviewSession() каждый раз, когда требуется новая запись. Но в этом случае при остановке медиарекордера, она закроется сама, причем вместе с приложением, издевательски написав, что «потеряна Surface вашего медиарекордера». Что, как бы и логично, мы же его сами остановили и закрыли.

Поэтому здесь нам надо использовать другой Surface — по латыни PersistentInputSurface() или «сурфейс упоротый упорный». Он не закроется при останове медиарекордера, а будет себе жить дальше. Правда, он требует более специфического обращения (см. комментарии к статье), но зато работает и работает без лагов!

Вариант кода без лагов
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.media.MediaCodec;
import android.os.AsyncTask;
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 {

    Surface recorderSurface =null;
    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();
                    Log.i(LOG_TAG, "START");

                }
            }
        });


        mButtonStopRecordVideo.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                if ((myCameras[CAMERA1] != null) & (mMediaRecorder != null)) {
                    myCameras[CAMERA1].stopRecordingVideo();
                    Log.i(LOG_TAG, "STOP");
                }


            }
        });


        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.setVideoSize(640, 480);
        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);





    }

    private void setUpMediaRecorder2() {

        mMediaRecorder = new MediaRecorder();

        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);

        mMediaRecorder.setInputSurface(recorderSurface);


        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
        mCurrentFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), "test"+count+".mp4");
        mMediaRecorder.setOutputFile(mCurrentFile.getAbsolutePath());
        mMediaRecorder.setVideoSize(640, 480);
        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, " запустили медиа рекордер2");

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

                /**Surface for the camera preview set up*/

                mPreviewBuilder.addTarget(surface);

                /**MediaRecorder setup for surface*/


              //  Surface recorderSurface = mMediaRecorder.getSurface();

                recorderSurface=MediaCodec.createPersistentInputSurface();
                mMediaRecorder.setInputSurface(recorderSurface);



                try {
                    mMediaRecorder.prepare();
                    Log.i(LOG_TAG, " запустили медиа рекордер");

                } catch (Exception e) {
                    Log.i(LOG_TAG, "не запустили медиа рекордер");
                }
                mPreviewBuilder.addTarget(recorderSurface);

                mCameraDevice.createCaptureSession(Arrays.asList(surface,recorderSurface),
                        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() {




            count++;
            MyTask mt = new MyTask();
            mt.execute();


        }


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

    }


    class MyTask extends AsyncTask<Void, Void, Void> {

        @Override
        protected void onPreExecute() {

        }

        @Override
        protected Void doInBackground(Void... params) {
            mMediaRecorder.stop();
            mMediaRecorder.release();
            setUpMediaRecorder2();
            return null;
        }

        @Override
        protected void onPostExecute(Void result) {

        }
    }




}



Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
Total votes 10: ↑10 and ↓0+10
Comments25

Articles