【问题标题】:How to change orientation of captured byte[] frames through onPreviewFrame callback?如何通过 onPreviewFrame 回调更改捕获的字节 [] 帧的方向?
【发布时间】:2013-02-28 09:17:06
【问题描述】:

这个问题我搜索了很多次,但一直没有看到满意的答案,所以现在我在这里有最后的希望。

我设置了一个onPreviewFrame 回调。这给出了 byte[] 的原始帧,支持预览格式(NV21H.264 编码类型)。

现在,问题是回调总是从固定方向开始提供byte[] 帧,每当设备旋转时,它都不会反映到捕获的byte[] 帧。我尝试过使用setDisplayOrientationsetRotation,但这些api 只是反映到预览,它根本不会显示到捕获的byte [] 帧。

Android 文档甚至说,Camera.setDisplayOrientation 只影响显示预览,而不影响帧字节:

这不会影响onPreviewFrame(byte[], Camera)、JPEG图片或录制视频传入的字节数组的顺序。

最后有没有办法在任何 API 级别更改byte[] 框架的方向?

【问题讨论】:

  • 您找到解决方案了吗?因为下面的答案不好(性能很差)

标签: android-camera


【解决方案1】:

如果您不关心格式,一种可能的方法是使用YuvImage 类获取JPEG 缓冲区,使用此缓冲区创建Bitmap 并将其旋转到相应的角度。类似的东西:

@Override
public void onPreviewFrame(byte[] data, Camera camera) {

    Size previewSize = camera.getParameters().getPreviewSize();
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    byte[] rawImage = null;

    // Decode image from the retrieved buffer to JPEG
    YuvImage yuv = new YuvImage(data, ImageFormat.NV21, previewSize.width, previewSize.height, null);
    yuv.compressToJpeg(new Rect(0, 0, previewSize.width, previewSize.height), YOUR_JPEG_COMPRESSION, baos);
    rawImage = baos.toByteArray();

    // This is the same image as the preview but in JPEG and not rotated
    Bitmap bitmap = BitmapFactory.decodeByteArray(rawImage, 0, rawImage.length);
    ByteArrayOutputStream rotatedStream = new ByteArrayOutputStream();

    // Rotate the Bitmap
    Matrix matrix = new Matrix();
    matrix.postRotate(YOUR_DEFAULT_ROTATION);

    // We rotate the same Bitmap
    bitmap = Bitmap.createBitmap(bitmap, 0, 0, previewSize.width, previewSize.height, matrix, false);

    // We dump the rotated Bitmap to the stream 
    bitmap.compress(CompressFormat.JPEG, YOUR_JPEG_COMPRESSION, rotatedStream);

    rawImage = rotatedStream.toByteArray();

    // Do something we this byte array
}

【讨论】:

  • 天哪,你可以看出这种方法的性能非常非常糟糕,你不能在onPreviewFrame...中进行这样的处理并且你使用压缩......
【解决方案2】:

我已修改此Open Source Android Touch-To-Record library 的 onPreviewFrame 方法以转置并调整捕获帧的大小。

我在 setCameraParams() 方法中定义了“yuvIplImage”,如下所示。

IplImage yuvIplImage = IplImage.create(mPreviewSize.height, mPreviewSize.width, opencv_core.IPL_DEPTH_8U, 2);

这是我的 onPreviewFrame() 方法:

@Override
public void onPreviewFrame(byte[] data, Camera camera)
{

    long frameTimeStamp = 0L;

    if(FragmentCamera.mAudioTimestamp == 0L && FragmentCamera.firstTime > 0L)
    {
        frameTimeStamp = 1000L * (System.currentTimeMillis() - FragmentCamera.firstTime);
    }
    else if(FragmentCamera.mLastAudioTimestamp == FragmentCamera.mAudioTimestamp)
    {
        frameTimeStamp = FragmentCamera.mAudioTimestamp + FragmentCamera.frameTime;
    }
    else
    {
        long l2 = (System.nanoTime() - FragmentCamera.mAudioTimeRecorded) / 1000L;
        frameTimeStamp = l2 + FragmentCamera.mAudioTimestamp;
        FragmentCamera.mLastAudioTimestamp = FragmentCamera.mAudioTimestamp;
    }

    synchronized(FragmentCamera.mVideoRecordLock)
    {
        if(FragmentCamera.recording && FragmentCamera.rec && lastSavedframe != null && lastSavedframe.getFrameBytesData() != null && yuvIplImage != null)
        {
            FragmentCamera.mVideoTimestamp += FragmentCamera.frameTime;

            if(lastSavedframe.getTimeStamp() > FragmentCamera.mVideoTimestamp)
            {
                FragmentCamera.mVideoTimestamp = lastSavedframe.getTimeStamp();
            }

            try
            {
                yuvIplImage.getByteBuffer().put(lastSavedframe.getFrameBytesData());

                IplImage bgrImage = IplImage.create(mPreviewSize.width, mPreviewSize.height, opencv_core.IPL_DEPTH_8U, 4);// In my case, mPreviewSize.width = 1280 and mPreviewSize.height = 720
                IplImage transposed = IplImage.create(mPreviewSize.height, mPreviewSize.width, yuvIplImage.depth(), 4);
                IplImage squared = IplImage.create(mPreviewSize.height, mPreviewSize.height, yuvIplImage.depth(), 4);

                int[] _temp = new int[mPreviewSize.width * mPreviewSize.height];

                Util.YUV_NV21_TO_BGR(_temp, data, mPreviewSize.width,  mPreviewSize.height);

                bgrImage.getIntBuffer().put(_temp);

                opencv_core.cvTranspose(bgrImage, transposed);
                opencv_core.cvFlip(transposed, transposed, 1);

                opencv_core.cvSetImageROI(transposed, opencv_core.cvRect(0, 0, mPreviewSize.height, mPreviewSize.height));
                opencv_core.cvCopy(transposed, squared, null);
                opencv_core.cvResetImageROI(transposed);

                videoRecorder.setTimestamp(lastSavedframe.getTimeStamp());
                videoRecorder.record(squared);
            }
            catch(com.googlecode.javacv.FrameRecorder.Exception e)
            {
                e.printStackTrace();
            }
        }

        lastSavedframe = new SavedFrames(data, frameTimeStamp);
    }
}

这段代码使用了一个方法“YUV_NV21_TO_BGR”,我从这个link找到的

基本上这种方法是用来解决的,我称之为“Android 上的绿魔问题”。您可以在其他 SO 线程上看到其他 android 开发人员面临同样的问题。在我刚刚对 YuvIplImage 进行转置时添加“YUV_NV21_TO_BGR”方法之前,更重要的是转置、翻转(有或没有调整大小)的组合,结果视频中有绿色输出。这种“YUV_NV21_TO_BGR”方法挽救了这一天。感谢来自上述谷歌群组线程的@David Han。

您还应该知道,onPreviewFrame 中的所有这些处理(转置、翻转和调整大小)都需要很长时间,这会严重影响您的每秒帧数 (FPS) 速率。当我在 onPreviewFrame 方法中使用这段代码时,录制视频的 FPS 从 30fps 下降到 3 帧/秒。

我建议不要使用这种方法。相反,您可以在 AsyncTask 中使用 JavaCV 对视频文件进行后期录制处理(转置、翻转和调整大小)。希望这会有所帮助。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-01
    • 2021-10-25
    • 1970-01-01
    • 1970-01-01
    • 2018-04-02
    • 1970-01-01
    相关资源
    最近更新 更多