如何用静态 YUV 帧覆盖 onPreviewFrame 数据?

Posted

技术标签:

【中文标题】如何用静态 YUV 帧覆盖 onPreviewFrame 数据?【英文标题】:How to overwrite onPreviewFrame data with static YUV frame? 【发布时间】:2014-05-07 23:08:14 【问题描述】:

我的应用程序重写了 onPreviewFrame 回调以将当前相机帧传递给 webrtc 本机函数。这非常有效,但是如果在我的应用中选择了该选项,我希望能够切换到发送静态帧而不是视频。

到目前为止,我已经创建了一个 YUV NV21 图像,我将其存储在 assets 目录中。将该帧向下传递给本机函数的所有尝试都导致了紫色/绿色条纹,而不是实际图像。

这是我目前所拥有的;

@Override
public void onPreviewFrame(byte[] data, Camera camera) 
    previewBufferLock.lock();

    if (mFrameProvider.isEnabled()) 
         mFrameProvider.overwriteWithFrame(data, expectedFrameSize);
    

    if (isCaptureRunning) 
        if (data.length == expectedFrameSize) 
             ProvideCameraFrame(data, expectedFrameSize, context);
             cameraUtils.addCallbackBuffer(camera, data);
        
    
    previewBufferLock.unlock();



@Override
public byte[] overwriteWithPreviewFrame(byte[] data, int expectedFrameSize) 
   if (mFrameData == null) 
       loadPreviewFrame();
   

   for (int i=0; i < expectedFrameSize; i++) 
        if (i < mFrameData.length) 
        data[i] = mFrameData[i];
        
   

   return data;

private void loadPreviewFrame() 
    try 
        InputStream open = mContext.getResources().getAssets().open(PREVIEW_FRAME_FILE);

        mFrameData = IOUtils.toByteArray(open);
        open.close();
     catch (Exception e) 
        Log.e("", "", e);
    

我也尝试将图像转换为位图。所以问题是如何从资产中打开 YUV 帧并将其转换为合适的格式以传递给本机方法。

产生以下输出;

【问题讨论】:

如果您看到 an 图像,那么您显然在更换相机输出方面取得了一些成功。您确定您的图片格式正确吗? 是的,我也尝试过使用在线提供的 YUV 示例帧.. :( 无论您提供何种图像,紫色/绿色条纹输出看起来是否相同? (或者在使用相同图像的后续运行中会有所不同?)只是想弄清楚您是否看到图像的失真版本,或者来自其他地方的乱码数据。 我得到的图像似乎是绿色/紫色,在某些图像上你可以辨认出实际图像的一部分,我附上了一个例子。 我对大约 30% 的数据丢失感到困惑,难道你的样本帧被格式化为 YUV 4:2:2? 【参考方案1】:

在与 android API 进行了长时间的斗争之后,我设法让它工作了。

有两个问题导致绿色/紫色输出;

数据丢失:生成的 YUV 帧大于相同分辨率下的原始预览帧,因此传递给原生代码的数据丢失了大约 30% 的图像数据。

分辨率错误:本机代码需要预览帧的分辨率,而不是相机的分辨率。

以下是希望添加静态框架的任何人的有效解决方案;

所以更新了代码:

@Override
public byte[] getPreviewFrameData(int width, int height) 
    if (mPreviewFrameData == null) 
        loadPreviewFrame(width, height);
    

    return mPreviewFrameData;


private void loadPreviewFrame(int width, int height) 
    try 
        Bitmap previewImage = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.frame);
        Bitmap resizedPreviewImage = Bitmap.createScaledBitmap(previewImage, width, height, false);

        BitmapConverter bitmapConverter = new BitmapConverter();
        mPreviewFrameData = bitmapConverter.convertToNV21(resizedPreviewImage);
     catch (Exception e) 
        Log.e("DisabledCameraFrameProvider", "Failed to loadPreviewFrame");
    


class BitmapConverter 
    byte [] convertToNV21(Bitmap bitmap) 
        int inputWidth = bitmap.getWidth();
        int inputHeight = bitmap.getHeight();

        int [] argb = new int[inputWidth * inputHeight];

        bitmap.getPixels(argb, 0, inputWidth, 0, 0, inputWidth, inputHeight);

        byte [] yuv = new byte[inputWidth*inputHeight*3/2];
        encodeYUV420SP(yuv, argb, inputWidth, inputHeight);

        bitmap.recycle();

        return yuv;
    

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

        int yIndex = 0;
        int uvIndex = frameSize;

        int R, G, B, Y, U, V;
        int index = 0;
        for (int j = 0; j < height; j++) 
            for (int i = 0; i < width; i++) 
                R = (argb[index] & 0xff0000) >> 16;
                G = (argb[index] & 0xff00) >> 8;
                B = (argb[index] & 0xff);

                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;

                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 ++;
            
        
    

最后在你的回调中;

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

     byte[] bytes = data;

     if (!mProvider.isVideoEnabled()) 
         Camera.Size previewSize = camera.getParameters().getPreviewSize();
         bytes = mProvider.getPreviewFrameData(previewSize.width, previewSize.height);
     

     ProvideCameraFrame(bytes, bytes.length, context);

关键是将图像缩放到相机预览尺寸并将图像转换为YUV色彩空间。

【讨论】:

以上是关于如何用静态 YUV 帧覆盖 onPreviewFrame 数据?的主要内容,如果未能解决你的问题,请参考以下文章

我如何从 android 相机 onPreviewFrame 中知道 YUV 格式?

如何用DirectX显示YUV格式的图像 20HanZhu1

如何使用 python 和 openCV 从 .yuv 视频文件 (YUV420) 中提取帧?

如何用从一个数据帧到另一个数据帧的值替换字符串

在 Android 上从视频播放中捕获 YUV 帧

如何用python实现视频关键帧提取并保存为图片