web-dev-qa-db-ja.com

Camera2APIでMediaCodecを使用してビデオを録画する

MediaCodecを使用してImageReaderからの生のフレームをonImageAvailableコールバックに記録しようとしていますが、機能するコードを記述できません。ほとんどの例では、Camera 1APIまたはMediaRecorderを使用しています。私の目的は、個々のフレームをキャプチャして処理し、そこからmp4を作成することです。

生のYUVフレーム

        @Override
        public void onImageAvailable(ImageReader reader) {
            Image i = reader.acquireLatestImage();
            processImage(i);
            i.close();
            Log.d("hehe", "onImageAvailable");
        }
    };

MediaCodec

MediaCodec codec = MediaCodec.createByCodecName(name);
 MediaFormat mOutputFormat; // member variable
 codec.setCallback(new MediaCodec.Callback() {
   @Override
   void onInputBufferAvailable(MediaCodec mc, int inputBufferId) {
     ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferId);
     // fill inputBuffer with valid data
     …
     codec.queueInputBuffer(inputBufferId, …);
   }

   @Override
   void onOutputBufferAvailable(MediaCodec mc, int outputBufferId, …) {
     ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);
     MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A
     // bufferFormat is equivalent to mOutputFormat
     // outputBuffer is ready to be processed or rendered.
     …
     codec.releaseOutputBuffer(outputBufferId, …);
   }

   @Override
   void onOutputFormatChanged(MediaCodec mc, MediaFormat format) {
     // Subsequent data will conform to new format.
     // Can ignore if using getOutputFormat(outputBufferId)
     mOutputFormat = format; // option B
   }

   @Override
   void onError(…) {
     …
   }
 });
 codec.configure(format, …);
 mOutputFormat = codec.getOutputFormat(); // option B
 codec.start();
 // wait for processing to complete
 codec.stop();
 codec.release();

https://developer.Android.com/reference/Android/media/MediaCodec で指定されたコードを関連付けることができません。助けてください

10
HimalayanCoder

Queue を作成する必要があります。Imageプレーンから作成された画像のバッファをキューにプッシュし、void onInputBufferAvailable(MediaCodec mc, int inputBufferId)

1)バッファデータをラップするクラスを作成します:

_class MyData{
    byte[] buffer;
    long presentationTimeUs;
    // to tell your encoder that is a EOS, otherwise you can not know when to stop
    boolean isEOS; 
    public MyData(byte[] buffer,long presentationTimeUs, boolean isEOS){
        this.buffer = new byte[buffer.length];
        System.arraycopy(buffer, 0, this.buffer, 0, buffer.length);
        this.presentationTimeUs = presentationTimeUs;
        this.isEOS = isEOS;
    }

    public byte[] getBuffer() {
        return buffer;
    }

    public void setBuffer(byte[] buffer) {
        this.buffer = buffer;
    }

    public long getPresentationTimeUs() {
        return presentationTimeUs;
    }

    public void setPresentationTimeUs(long presentationTimeUs) {
        this.presentationTimeUs = presentationTimeUs;
    }

    public boolean isEOS() {
        return isEOS;
    }

    public void setEOS(boolean EOS) {
        isEOS = EOS;
    }

}
_

2)キューを作成します:

_Queue<MyData> mQueue = new LinkedList<MyData>();
_

3)ネイティブコードを使用してイメージプレーンをバイト配列(byte [])に変換します:

  • Gradleファイルへのネイティブサポートの追加:

    _Android{
    
    compileSdkVersion 27
    
    defaultConfig {
        ...
    
        externalNativeBuild {
            cmake {
                arguments "-DANDROID_STL=stlport_static"
                cppFlags "-std=c++11"
            }
        }
    }
    
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
    ...
    }
    _
    • イメージプレーンをバイト配列に変換する関数の作成:

(native-yuv-to-buffer.cpp)

_    extern "C" JNIEXPORT 
    jbyteArray JNICALL

Java_labs_farzi_camera2previewstream_MainActivity_yuvToBuffer
(

    JNIEnv *env,
    jobject instance,
    jobject yPlane,
    jobject uPlane,
    jobject vPlane,
    jint yPixelStride,
    jint yRowStride,
    jint uPixelStride,
    jint uRowStride,
    jint vPixelStride,
    jint vRowStride,
    jint imgWidth,
    jint imgHeight) {

        bbuf_yIn = static_cast<uint8_t *>(env->GetDirectBufferAddress(yPlane));
        bbuf_uIn = static_cast<uint8_t *>(env->GetDirectBufferAddress(uPlane));
        bbuf_vIn = static_cast<uint8_t *>(env->GetDirectBufferAddress(vPlane));

        buf = (uint8_t *) malloc(sizeof(uint8_t) * imgWidth * imgHeight +
                                 2 * (imgWidth + 1) / 2 * (imgHeight + 1) / 2);

        bool isNV21;
        if (yPixelStride == 1) {
            // All pixels in a row are contiguous; copy one line at a time.
            for (int y = 0; y < imgHeight; y++)
                memcpy(buf + y * imgWidth, bbuf_yIn + y * yRowStride,
                       static_cast<size_t>(imgWidth));
        } else {
            // Highly improbable, but not disallowed by the API. In this case
            // individual pixels aren't stored consecutively but sparsely with
            // other data inbetween each pixel.
            for (int y = 0; y < imgHeight; y++)
                for (int x = 0; x < imgWidth; x++)
                    buf[y * imgWidth + x] = bbuf_yIn[y * yRowStride + x * yPixelStride];
        }

        uint8_t *chromaBuf = &buf[imgWidth * imgHeight];
        int chromaBufStride = 2 * ((imgWidth + 1) / 2);
        if (uPixelStride == 2 && vPixelStride == 2 &&
            uRowStride == vRowStride && bbuf_vIn == bbuf_uIn + 1) {
            isNV21 = true;
            // The actual cb/cr planes happened to be laid out in
            // exact NV21 form in memory; copy them as is
            for (int y = 0; y < (imgHeight + 1) / 2; y++)
                memcpy(chromaBuf + y * chromaBufStride, bbuf_vIn + y * vRowStride,
                       static_cast<size_t>(chromaBufStride));
        } else if (vPixelStride == 2 && uPixelStride == 2 &&
                   uRowStride == vRowStride && bbuf_vIn == bbuf_uIn + 1) {
            isNV21 = false;
            // The cb/cr planes happened to be laid out in exact NV12 form
            // in memory; if the destination API can use NV12 in addition to
            // NV21 do something similar as above, but using cbPtr instead of crPtr.
            // If not, remove this clause and use the generic code below.
        } else {
            isNV21 = true;
            if (vPixelStride == 1 && uPixelStride == 1) {
                // Continuous cb/cr planes; the input data was I420/YV12 or similar;
                // copy it into NV21 form
                for (int y = 0; y < (imgHeight + 1) / 2; y++) {
                    for (int x = 0; x < (imgWidth + 1) / 2; x++) {
                        chromaBuf[y * chromaBufStride + 2 * x + 0] = bbuf_vIn[y * vRowStride + x];
                        chromaBuf[y * chromaBufStride + 2 * x + 1] = bbuf_uIn[y * uRowStride + x];
                    }
                }
            } else {
                // Generic data copying into NV21
                for (int y = 0; y < (imgHeight + 1) / 2; y++) {
                    for (int x = 0; x < (imgWidth + 1) / 2; x++) {
                        chromaBuf[y * chromaBufStride + 2 * x + 0] = bbuf_vIn[y * vRowStride +
                                                                              x * uPixelStride];
                        chromaBuf[y * chromaBufStride + 2 * x + 1] = bbuf_uIn[y * uRowStride +
                                                                              x * vPixelStride];
                    }
                }
            }
        }

        uint8_t *I420Buff = (uint8_t *) malloc(sizeof(uint8_t) * imgWidth * imgHeight +
                                               2 * (imgWidth + 1) / 2 * (imgHeight + 1) / 2);
        SPtoI420(buf,I420Buff,imgWidth,imgHeight,isNV21);

        jbyteArray ret = env->NewByteArray(imgWidth * imgHeight *
                                           3/2);
        env->SetByteArrayRegion (ret, 0, imgWidth * imgHeight *
                                         3/2, (jbyte*)I420Buff);
        free(buf);
        free (I420Buff);
        return ret;
    }

- **Adding a function to convert Semi-planar to planar :** 

    bool SPtoI420(const uint8_t *src, uint8_t *dst, int width, int height, bool isNV21)
    {
        if (!src || !dst) {
            return false;
        }

        unsigned int YSize = width * height;
        unsigned int UVSize = (YSize>>1);

        // NV21: Y..Y + VUV...U
        const uint8_t *pSrcY = src;
        const uint8_t *pSrcUV = src + YSize;

        // I420: Y..Y + U.U + V.V
        uint8_t *pDstY = dst;
        uint8_t *pDstU = dst + YSize;
        uint8_t *pDstV = dst + YSize + (UVSize>>1);

        // copy Y
        memcpy(pDstY, pSrcY, YSize);

        // copy U and V
        for (int k=0; k < (UVSize>>1); k++) {
            if(isNV21) {
                pDstV[k] = pSrcUV[k * 2];     // copy V
                pDstU[k] = pSrcUV[k * 2 + 1];   // copy U
            }else{
                pDstU[k] = pSrcUV[k * 2];     // copy V
                pDstV[k] = pSrcUV[k * 2 + 1];   // copy U
            }
        }

        return true;}
_

4)バッファをキューにプッシュします:

_    private final ImageReader.OnImageAvailableListener mOnGetPreviewListener
        = new ImageReader.OnImageAvailableListener() {

    @Override
    public void onImageAvailable(ImageReader reader) {
        Image image = reader.acquireLatestImage();
        if (image == null)
            return;
        final Image.Plane[] planes = image.getPlanes();
        Image.Plane yPlane = planes[0];
        Image.Plane uPlane = planes[1];
        Image.Plane vPlane = planes[2];
        byte[] mBuffer = yuvToBuffer(yPlane.getBuffer(),
                uPlane.getBuffer(),
                vPlane.getBuffer(),
                yPlane.getPixelStride(),
                yPlane.getRowStride(),
                uPlane.getPixelStride(),
                uPlane.getRowStride(),
                vPlane.getPixelStride(),
                vPlane.getRowStride(),
                image.getWidth(),
                image.getHeight());
        mQueue.add(new MyData(mBuffer, image.getTimestamp(), false));
        image.close();
        Log.d("hehe", "onImageAvailable");
    }
};
_

5)データをエンコードしてh264ビデオファイルを保存します(再生するにはVLC):

_        public void onInputBufferAvailable(MediaCodec mc, int inputBufferId) {
        ByteBuffer inputBuffer = mc.getInputBuffer(inputBufferId);
        Log.d(TAG, "onInputBufferAvailable: ");
        // fill inputBuffer with valid data
        MyData data = mQueue.poll();
        if (data != null) {
            // check if is EOS and process with EOS flag if is the case
            // else if NOT EOS
            if (inputBuffer != null) {
                Log.e(TAG, "onInputBufferAvailable: "+data.getBuffer().length);
                inputBuffer.clear();
                inputBuffer.put(data.getBuffer());

                mc.queueInputBuffer(inputBufferId,
                        0,
                        data.getBuffer().length,
                        data.getPresentationTimeUs(),
                        0);
            }

        } else {

            mc.queueInputBuffer(inputBufferId,
                    0,
                    0,
                    0,
                    0);
        }
    }

    @Override
    public void onOutputBufferAvailable(@NonNull MediaCodec codec, int index, @NonNull MediaCodec.BufferInfo info) {
        Log.d(TAG, "onOutputBufferAvailable: ");
        ByteBuffer outputBuffer = codec.getOutputBuffer(index);
        byte[] outData = new byte[info.size];
        if (outputBuffer != null) {
            outputBuffer.get(outData);
            try {
                fos.write(outData);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        codec.releaseOutputBuffer(index,false);
    }
_

6)void onOutputBufferAvailable(MediaCodec mc, int outputBufferId, …)でトラックを多重化すると、処理はインターネットで見られる同期モードの例と同様になります。 。

私の答えがあなたのお役に立てば幸いです

完全なサンプルコードはこちら

2
E.Abdel

このサンプルを試してみませんか: https://github.com/googlesamples/Android-Camera2Video

私はそれがあなたのすべての要件を確実に満たすと思います、そしてあなたが上記のサンプルのコードに関係することができないならばあなたはいつでも私に連絡することができます。

このサンプルはCamera2 APIを使用しており、必要なのは生のYUVフレームからの変換です。これはそれを使用して実行できます。したがって、指定されたサンプルを1回確認し、そのコードを使用して目的のアプリでMP4ビデオを記録する場合は、問題や問題が発生しないことを願っています。

たとえば-a)これでは、実装する必要があります CameraDevice.StateCallback カメラデバイスの状態の変更に関するイベントを受信します。メソッドをオーバーライドして、CameraDeviceインスタンスを設定し、プレビューを開始し、カメラを停止して解放します。

b)プレビューを開始するときに、ビデオを受け入れるようにMediaRecorderを設定しますフォーマット。

c)次に、CameraDeviceインスタンスでcreateCaptureRequest(CameraDevice.TEMPLATE_RECORD)を使用して CaptureRequest.Builder を設定します。

d)次に、メソッドcreateCaptureSession(surfaces、new CameraCaptureSession.StateCallback(){}を使用して、 CameraCaptureSession.StateCallback を実装します。 )CameraDeviceインスタンス上。ここで、surfacesは、TextureViewのサーフェスビューとMediaRecorderインスタンスのサーフェスで構成されるリストです。

e)MediaRecorderでstart()メソッドとstop()メソッドを使用します実際に記録を開始および停止するインスタンス。

f)最後に、onResume()とonPause()でカメラデバイスをセットアップしてクリーンアップします。

ハッピーコーディング。

1
Nitin Gurbani