Пользовательские данные byteArray для WebRTC videoTrack

Мне нужно использовать WebRTC для Android, чтобы отправить определенное обрезанное (лицо) видео в videoChannel. Я смог манипулировать классом Camera1Session WebRTC, чтобы обрезать лицо. Прямо сейчас я устанавливаю это для ImageView.listenForBytebufferFrames() из Camera1Session.java

private void listenForBytebufferFrames() {
    this.camera.setPreviewCallbackWithBuffer(new PreviewCallback() {
        public void onPreviewFrame(byte[] data, Camera callbackCamera) {
            Camera1Session.this.checkIsOnCameraThread();
            if(callbackCamera != Camera1Session.this.camera) {
                Logging.e("Camera1Session", "Callback from a different camera. This should never happen.");
            } else if(Camera1Session.this.state != Camera1Session.SessionState.RUNNING) {
                Logging.d("Camera1Session", "Bytebuffer frame captured but camera is no longer running.");
            } else {
                mFrameProcessor.setNextFrame(data, callbackCamera);
                long captureTimeNs = TimeUnit.MILLISECONDS.toNanos(SystemClock.elapsedRealtime());
                if(!Camera1Session.this.firstFrameReported) {
                    int startTimeMs = (int)TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - Camera1Session.this.constructionTimeNs);
                    Camera1Session.camera1StartTimeMsHistogram.addSample(startTimeMs);
                    Camera1Session.this.firstFrameReported = true;
                }

                ByteBuffer byteBuffer1 = ByteBuffer.wrap(data);
                Frame outputFrame = new Frame.Builder()
                        .setImageData(byteBuffer1,
                                Camera1Session.this.captureFormat.width,
                                Camera1Session.this.captureFormat.height,
                                ImageFormat.NV21)
                        .setTimestampMillis(mFrameProcessor.mPendingTimeMillis)
                        .setId(mFrameProcessor.mPendingFrameId)
                        .setRotation(3)
                        .build();
                int w = outputFrame.getMetadata().getWidth();
                int h = outputFrame.getMetadata().getHeight();
                SparseArray<Face> detectedFaces = mDetector.detect(outputFrame);
                if (detectedFaces.size() > 0) {

                    Face face = detectedFaces.valueAt(0);
                    ByteBuffer byteBufferRaw = outputFrame.getGrayscaleImageData();
                    byte[] byteBuffer = byteBufferRaw.array();
                    YuvImage yuvimage  = new YuvImage(byteBuffer, ImageFormat.NV21, w, h, null);
                    ByteArrayOutputStream baos = new ByteArrayOutputStream();

                    //My crop logic to get face co-ordinates

                    yuvimage.compressToJpeg(new Rect(left, top, right, bottom), 80, baos);
                    final byte[] jpegArray = baos.toByteArray();
                    Bitmap bitmap = BitmapFactory.decodeByteArray(jpegArray, 0, jpegArray.length);

                    Activity currentActivity = getActivity();
                    if (currentActivity instanceof CallActivity) {
                        ((CallActivity) currentActivity).setBitmapToImageView(bitmap); //face on ImageView is set just fine
                    }
                    Camera1Session.this.events.onByteBufferFrameCaptured(Camera1Session.this, data, Camera1Session.this.captureFormat.width, Camera1Session.this.captureFormat.height, Camera1Session.this.getFrameOrientation(), captureTimeNs);
                    Camera1Session.this.camera.addCallbackBuffer(data);
                } else {
                    Camera1Session.this.events.onByteBufferFrameCaptured(Camera1Session.this, data, Camera1Session.this.captureFormat.width, Camera1Session.this.captureFormat.height, Camera1Session.this.getFrameOrientation(), captureTimeNs);
                    Camera1Session.this.camera.addCallbackBuffer(data);
                }

            }
        }
    });
}

jpegArray это последний byteArray, через который мне нужно передавать WebRTCчто я пробовал с чем-то вроде этого:

Camera1Session.this.events.onByteBufferFrameCaptured(Camera1Session.this, jpegArray, (int) face.getWidth(), (int) face.getHeight(), Camera1Session.this.getFrameOrientation(), captureTimeNs);
Camera1Session.this.camera.addCallbackBuffer(jpegArray);

Установка их таким образом дает мне следующую ошибку:

../../webrtc/sdk/android/src/jni/androidvideotracksource.cc line 82
Check failed: length >= width * height + 2 * uv_width * ((height + 1) / 2) (2630 vs. 460800)

Что я предполагаю, потому что androidvideotracksource не получает одинаковую длину byteArray что он ожидает, так как кадр обрезается сейчас. Может ли кто-нибудь указать мне, как этого добиться? Это правильный путь / место для манипулирования данными и подачи в videoTrack?

Редактировать:bitmap byteArray data не дает мне предварительный просмотр камеры на ImageViewв отличие от byteArray jpegArray, Может потому что они упакованы по разному?

3 ответа

Решение

Хорошо, это была определенно проблема того, как оригинал byte[] data был упакован и путь byte[] jpegArray был упакован. Изменение способа упаковки и масштабирования, как предложил АлексКон, сработало для меня. Я нашел помощь в другом посте на Stackru о том, как его упаковать. Вот код для этого:

private byte[] getNV21(int left, int top, int inputWidth, int inputHeight, Bitmap scaled) {
int [] argb = new int[inputWidth * inputHeight];
    scaled.getPixels(argb, 0, inputWidth, left, top, inputWidth, inputHeight);
    byte [] yuv = new byte[inputWidth*inputHeight*3/2];
    encodeYUV420SP(yuv, argb, inputWidth, inputHeight);
    scaled.recycle();
    return yuv;
}

private void encodeYUV420SP(byte[] yuv420sp, int[] argb, int width, int height) {
    final int frameSize = width * height;

    int yIndex = 0;
    int uvIndex = frameSize;

    int a, R, G, B, Y, U, V;
    int index = 0;
    for (int j = 0; j < height; j++) {
        for (int i = 0; i < width; i++) {

            a = (argb[index] & 0xff000000) >> 24; // a is not used obviously
            R = (argb[index] & 0xff0000) >> 16;
            G = (argb[index] & 0xff00) >> 8;
            B = (argb[index] & 0xff) >> 0;

            // well known RGB to YUV algorithm
            Y = ( (  66 * R + 129 * G +  25 * B + 128) >> 8) +  16;
            U = ( ( -38 * R -  74 * G + 112 * B + 128) >> 8) + 128;
            V = ( ( 112 * R -  94 * G -  18 * B + 128) >> 8) + 128;

            // NV21 has a plane of Y and interleaved planes of VU each sampled by a factor of 2
            //    meaning for every 4 Y pixels there are 1 V and 1 U.  Note the sampling is every other
            //    pixel AND every other scanline.
            yuv420sp[yIndex++] = (byte) ((Y < 0) ? 0 : ((Y > 255) ? 255 : Y));
            if (j % 2 == 0 && index % 2 == 0) {
                yuv420sp[uvIndex++] = (byte)((V<0) ? 0 : ((V > 255) ? 255 : V));
                yuv420sp[uvIndex++] = (byte)((U<0) ? 0 : ((U > 255) ? 255 : U));
            }

            index ++;
        }
    }
}`

Я передаю это byte[] data в onByteBufferFrameCaptured а также callback:

Camera1Session.this.events.onByteBufferFrameCaptured(
                            Camera1Session.this,
                            data,
                            w,
                            h,
                            Camera1Session.this.getFrameOrientation(),
                            captureTimeNs);
Camera1Session.this.camera.addCallbackBuffer(data);

До этого мне пришлось масштабировать растровое изображение, что довольно просто:

int width = bitmapToScale.getWidth();
int height = bitmapToScale.getHeight();
Matrix matrix = new Matrix();
matrix.postScale(newWidth / width, newHeight / height);
Bitmap scaledBitmap = Bitmap.createBitmap(bitmapToScale, 0, 0, bitmapToScale.getWidth(), bitmapToScale.getHeight(), matrix, true);

Можем ли мы использовать канал данных WebRTC для обмена пользовательскими данными, например, "обрезанным" изображением лица в вашем случае, и выполнить соответствующие вычисления на стороне получателя, используя любую стороннюю библиотеку, например OpenGL и т. Д.? Причина, по которой я предлагаю, заключается в том, что поток WebRTC Video, полученный с канала, является потоком в реальном времени, а не байтовым массивом. WebRTC Video по своей внутренней архитектуре не предназначен для обрезки видео с другой стороны. Если мы хотим обрезать или увеличить видео, мы должны использовать любую библиотеку ar для выполнения этой работы.

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

В частности, WebRTC и потоковое видео предполагают, что видео имеет фиксированные размеры. Если вы хотите обрезать обнаруженное лицо, вы можете либо добавить обрезанное изображение, например, черными пикселями (WebRTC не использует прозрачность), и обрезать видео на стороне приемника, либо, если у вас нет контроля над приемник, измените размер обрезанной области, чтобы заполнить ожидаемый width * height кадр (вы также должны сохранить ожидаемое соотношение сторон).

Обратите внимание, что сжатие / распаковка JPEG, которую вы используете для обрезки оригинала, далеко не эффективна. Некоторые другие параметры можно найти в Обрезка изображения и изменить размер в Android.

Другие вопросы по тегам