Android平台GB28181设备接入模块摄像头采集方向不对怎么办?

简介: 我们在做Android平台GB28181设备接入模块的时候,有开发者提到这样的诉求:他们的智能头盔、执法记录仪等设备,采集到的图像,是旋转了90、180甚至270°的,设备本身无法针对图像做翻转或者旋转操作,问我们这种情况下需要如何处理?

背景

我们在做Android平台GB28181设备接入模块的时候,有开发者提到这样的诉求:他们的智能头盔、执法记录仪等设备,采集到的图像,是旋转了90、180甚至270°的,设备本身无法针对图像做翻转或者旋转操作,问我们这种情况下需要如何处理?


实际上,这块,我们前几年在做RTMP推送和轻量级RTSP服务模块的时候,老早处理了这类问题。


鉴于Android平台video数据采集分camera和camera2(Android 5.0+)接口,我们单独说明:

camera接口示例

16f9e19202e641c48839a9cca5992ba7.png

    //Github: https://github.com/daniulive/SmarterStreaming
    //author: 89030985@qq.com
    @Override
    public void onPreviewFrame(byte[] data, Camera camera) {
        frameCount++;
        if (frameCount % 3000 == 0) {
            Log.i("OnPre", "gc+");
            System.gc();
            Log.i("OnPre", "gc-");
        }
        if (data == null) {
            Parameters params = camera.getParameters();
            Size size = params.getPreviewSize();
            int bufferSize = (((size.width | 0x1f) + 1) * size.height * ImageFormat.getBitsPerPixel(params.getPreviewFormat())) / 8;
            camera.addCallbackBuffer(new byte[bufferSize]);
        } else {
            if (isRTSPPublisherRunning || isPushingRtmp || isRecording || isGB28181StreamRunning) {
                if (1 == video_opt_) {
                      /* byte[] i420_data = new byte[videoWidth*videoHeight*3/2];
                     libPublisher.SmartPublisherNV21ToI420Rotate(publisherHandle, data, videoWidth, videoWidth,i420_data, videoHeight, videoHeight/2, videoHeight/2,
                        videoWidth, videoHeight, 90);
                     libPublisher.SmartPublisherOnCaptureVideoI420DataV2(publisherHandle, i420_data, videoHeight, videoWidth,videoHeight, videoHeight/2, videoHeight/2);
                     */
                    libPublisher.SmartPublisherOnCaptureVideoData(publisherHandle, data, data.length, currentCameraType, currentOrigentation);
                } else if (3 == video_opt_) {
                    int w = videoWidth, h = videoHeight;
                    int y_stride = videoWidth, uv_stride = videoWidth;
                    int y_offset = 0, uv_offset = videoWidth * videoHeight;
                    int is_vertical_flip = 0, is_horizontal_flip = 0;
                    int rotation_degree = 0;
                    // 镜像只用在前置摄像头场景下
                    if (is_mirror && FRONT == currentCameraType) {
                        // 竖屏, (垂直翻转->顺时旋转270度)等价于(顺时旋转旋转270度->水平翻转)
                        if (PORTRAIT == currentOrigentation)
                            is_vertical_flip = 1;
                        else
                            is_horizontal_flip = 1;
                    }
                    if (PORTRAIT == currentOrigentation) {
                        if (BACK == currentCameraType)
                            rotation_degree = 90;
                        else
                            rotation_degree = 270;
                    } else if (LANDSCAPE_LEFT_HOME_KEY == currentOrigentation) {
                        rotation_degree = 180;
                    }
                    if (640 == w && 480 == h && PORTRAIT == currentOrigentation) {
                        // 480 * 640 竖屏情况下裁剪到 368 * 640, 均匀裁剪掉视频的上下两部分
                        h = 368;
                        y_offset = 56 * y_stride;
                        uv_offset += (56 >> 1) * uv_stride;
                    }
                    int scale_w = 0, scale_h = 0, scale_filter_mode = 0;
                    // 缩放测试++
                    /*
                    if (w >= 1280 && h >= 720) {
                        scale_w = align((int)(w * 0.8 + 0.5), 2);
                        scale_h = align((int)(h * 0.8 + 0.5), 2);
                    } else {
                        scale_w = align((int)(w * 1.5 + 0.5), 2);
                        scale_h = align((int)(h * 1.5 + 0.5), 2);
                    }
                    if(scale_w >0 && scale_h >0) {
                        scale_filter_mode = 3;
                     Log.i(TAG, "onPreviewFrame w:" + w + ", h:" + h + " s_w:" + scale_w + ", s_h:" + scale_h);
                    }
                    */
                    // 缩放测试---
                    libPublisher.PostLayerImageNV21ByteArray(publisherHandle, 0, 0, 0,
                            data, y_offset, y_stride, data, uv_offset, uv_stride, w, h,
                            is_vertical_flip, is_horizontal_flip, scale_w, scale_h, scale_filter_mode, rotation_degree);
                    // i420接口测试++
                    /*
                    byte[] i420_data = new byte[videoWidth*videoHeight*3/2];
                    int u_stride = videoWidth >> 1;
                    int v_stride = u_stride;
                    libPublisher.SmartPublisherNV21ToI420Rotate(publisherHandle, data, y_stride, uv_stride, i420_data, y_stride, u_stride, v_stride,
                            videoWidth, videoHeight, 0);
                    y_offset = 0;
                    int u_offset = y_offset + videoWidth * videoHeight;
                    int v_offset = u_offset + videoWidth*videoHeight/4;
                    libPublisher.PostLayerImageI420ByteArray(publisherHandle, 0, 0, 0,
                            i420_data, y_offset, y_stride, i420_data, u_offset, u_stride, i420_data, v_offset, v_stride,
                            w, h, is_vertical_flip, is_horizontal_flip, scale_w, scale_h, scale_filter_mode, rotation_degree);
                     */
                    // i420接口测试--
                }
            }
            camera.addCallbackBuffer(data);
        }
    }

对应的接口设计如下:

  /**
   * 投递层NV21图像
   *
   * @param index: 层索引, 必须大于等于0
   *
   * @param left: 层叠加的左上角坐标, 对于第0层的话传0
   *
   * @param top: 层叠加的左上角坐标, 对于第0层的话传0
   *
   * @param y_plane: y平面图像数据
   *
   * @param y_offset: 图像偏移, 这个主要目的是用来做clip的,一般传0
   *
   * @param y_row_stride: stride information
   *
   * @param uv_plane: uv平面图像数据
   *
   * @param uv_offset: 图像偏移, 这个主要目的是用来做clip的,一般传0
   *
   * @param uv_row_stride: stride information
   *
   * @param width: width, 必须大于1, 且必须是偶数
   *
   * @param height: height, 必须大于1, 且必须是偶数
   *
   * @param  is_vertical_flip: 是否垂直翻转, 0不翻转, 1翻转
   *
   * @param  is_horizontal_flip:是否水平翻转, 0不翻转, 1翻转
   *
   * @param  scale_width: 缩放宽,必须是偶数, 0或负数不缩放
   *
   * @param  scale_height: 缩放高, 必须是偶数, 0或负数不缩放
   *
   * @param  scale_filter_mode: 缩放质量, 传0使用默认速度,可选等级范围是:[1,3],值越大缩放质量越好, 但速度越慢
   *
   * @param  rotation_degree: 顺时针旋转, 必须是0, 90, 180, 270, 注意:旋转是在缩放, 垂直/水品反转之后再做, 请留意顺序
   *
   * @return {0} if successful
   */
  public native int PostLayerImageNV21ByteArray(long handle, int index, int left, int top,
                           byte[] y_plane, int y_offset, int y_row_stride,
                           byte[] uv_plane, int uv_offset, int uv_row_stride,
                           int width, int height, int is_vertical_flip,  int is_horizontal_flip,
                           int scale_width,  int scale_height, int scale_filter_mode,
                           int rotation_degree);

对应Camera2的接口示例

c8669a68a61348548def791b05bc4ad7.jpg

    @Override
    public void onCameraImageData(Image image) {
        Rect crop_rect = image.getCropRect();
        if (isPushingRtmp || isRTSPPublisherRunning || isGB28181StreamRunning || isRecording) {
            if (libPublisher != null) {
                Image.Plane[] planes = image.getPlanes();
                int w = image.getWidth(), h = image.getHeight();
                int y_offset = 0, u_offset = 0, v_offset = 0;
                if (!crop_rect.isEmpty()) {
                    // 裁剪测试++, 视频中心裁剪320*180一块区域
                            /*crop_rect.left = image.getWidth()/2 - 320/2;
                            crop_rect.top  = image.getHeight()/2 - 180/2;
                            crop_rect.right = crop_rect.left + 320;
                            crop_rect.bottom = crop_rect.top + 180;
                             */
                    // 裁剪测试--
                    w = crop_rect.width();
                    h = crop_rect.height();
                    y_offset += crop_rect.top * planes[0].getRowStride() + crop_rect.left * planes[0].getPixelStride();
                    u_offset += (crop_rect.top / 2) * planes[1].getRowStride() + (crop_rect.left / 2) * planes[1].getPixelStride();
                    v_offset += (crop_rect.top / 2) * planes[2].getRowStride() + (crop_rect.left / 2) * planes[2].getPixelStride();
                    ;
                    // Log.i(TAG, "crop w:" + w + " h:" + h + " y_offset:"+ y_offset + " u_offset:" + u_offset + " v_offset:" + v_offset);
                }
                int scale_w = 0, scale_h = 0, scale_filter_mode = 0;
                scale_filter_mode = 3;
                int rotation_degree = cameraImageRotationDegree_;
                if (rotation_degree < 0) {
                    Log.i(TAG, "onCameraImageData rotation_degree < 0, may need to set orientation_ to 0, 90, 180 or 270");
                    return;
                }
                libPublisher.PostLayerImageYUV420888ByteBuffer(publisherHandle, 0, 0, 0,
                        planes[0].getBuffer(), y_offset, planes[0].getRowStride(),
                        planes[1].getBuffer(), u_offset, planes[1].getRowStride(),
                        planes[2].getBuffer(), v_offset, planes[2].getRowStride(), planes[1].getPixelStride(),
                        w, h, 0, 0,
                        scale_w, scale_h, scale_filter_mode, rotation_degree);
            }
        }
    }

对应的接口设计如下:

  /**
   * 投递层YUV420888图像, 专门为android.media.Image的android.graphics.ImageFormat.YUV_420_888格式提供的接口
   *
   * @param index: 层索引, 必须大于等于0
   *
   * @param left: 层叠加的左上角坐标, 对于第0层的话传0
   *
   * @param top: 层叠加的左上角坐标, 对于第0层的话传0
   *
   * @param y_plane: 对应android.media.Image.Plane[0].getBuffer()
   *
   * @param y_offset: 图像偏移, 这个主要目的是用来做clip的,一般传0
   *
   * @param y_row_stride: 对应android.media.Image.Plane[0].getRowStride()
   *
   * @param u_plane: android.media.Image.Plane[1].getBuffer()
   *
   * @param u_offset: 图像偏移, 这个主要目的是用来做clip的,一般传0
   *
   * @param u_row_stride: android.media.Image.Plane[1].getRowStride()
   *
   * @param v_plane: 对应android.media.Image.Plane[2].getBuffer()
   *
   * @param v_offset: 图像偏移, 这个主要目的是用来做clip的,一般传0
   *
   * @param v_row_stride: 对应android.media.Image.Plane[2].getRowStride()
   *
   * @param uv_pixel_stride: 对应android.media.Image.Plane[1].getPixelStride()
   *
   * @param width: width, 必须大于1, 且必须是偶数
   *
   * @param height: height, 必须大于1, 且必须是偶数
   *
   * @param  is_vertical_flip: 是否垂直翻转, 0不翻转, 1翻转
   *
   * @param  is_horizontal_flip:是否水平翻转, 0不翻转, 1翻转
   *
   * @param  scale_width: 缩放宽,必须是偶数, 0或负数不缩放
   *
   * @param  scale_height: 缩放高, 必须是偶数, 0或负数不缩放
   *
   * @param  scale_filter_mode: 缩放质量, 传0使用默认速度,可选等级范围是:[1,3],值越大缩放质量越好, 但速度越慢
   *
   * @param  rotation_degree: 顺时针旋转, 必须是0, 90, 180, 270, 注意:旋转是在缩放, 垂直/水品反转之后再做, 请留意顺序
   *
   * @return {0} if successful
   */
  public native int PostLayerImageYUV420888ByteBuffer(long handle, int index, int left, int top,
                             ByteBuffer y_plane, int y_offset, int y_row_stride,
                               ByteBuffer u_plane, int u_offset, int u_row_stride,
                             ByteBuffer v_plane, int v_offset, int v_row_stride, int uv_pixel_stride,
                             int width, int height, int is_vertical_flip,  int is_horizontal_flip,
                             int scale_width,  int scale_height, int scale_filter_mode,
                               int rotation_degree);

总结

无需赘述,看过以上两个接口后,是不是觉得,即使数据需要更客制化的处理,比如缩放、水平翻转、垂直翻转、旋转等,也都可以实现?


实际上,数据源这块,不止Android自带的采集设备,其他编码前数据类型(如YV12/NV21/NV12/I420/RGB24/RGBA32/RGB565),均可实现更精细的处理。

相关文章
|
8月前
|
监控 Android开发 数据安全/隐私保护
批量发送短信的平台,安卓群发短信工具插件脚本,批量群发短信软件【autojs版】
这个Auto.js脚本实现了完整的批量短信发送功能,包含联系人管理、短信内容编辑、发送状态监控等功能
|
12月前
|
存储 编解码 监控
Android平台GB28181执法记录仪技术方案与实现
本文介绍了大牛直播SDK的SmartGBD在执法记录仪场景中的应用。GB28181协议作为视频监控联网的国家标准,为设备互联互通提供规范。SmartGBD专为Android平台设计,支持音视频采集、编码与传输,具备自适应算法和多功能扩展优势。文章分析了执法记录仪的需求,如实时音视频传输、设备管理及数据安全,并详细阐述了基于SmartGBD的技术实现方案,包括环境准备、SDK集成、设备注册、音视频处理及功能扩展等步骤。最后展望了SmartGBD在未来智慧物联领域的广阔应用前景。
750 13
|
12月前
|
监控 Shell Linux
Android调试终极指南:ADB安装+多设备连接+ANR日志抓取全流程解析,覆盖环境变量配置/多设备调试/ANR日志分析全流程,附Win/Mac/Linux三平台解决方案
ADB(Android Debug Bridge)是安卓开发中的重要工具,用于连接电脑与安卓设备,实现文件传输、应用管理、日志抓取等功能。本文介绍了 ADB 的基本概念、安装配置及常用命令。包括:1) 基本命令如 `adb version` 和 `adb devices`;2) 权限操作如 `adb root` 和 `adb shell`;3) APK 操作如安装、卸载应用;4) 文件传输如 `adb push` 和 `adb pull`;5) 日志记录如 `adb logcat`;6) 系统信息获取如屏幕截图和录屏。通过这些功能,用户可高效调试和管理安卓设备。
|
5月前
|
移动开发 前端开发 Android开发
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
863 12
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
|
5月前
|
移动开发 JavaScript 应用服务中间件
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
733 5
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
|
5月前
|
移动开发 Rust JavaScript
【01】首页建立-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【01】首页建立-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
942 4
【01】首页建立-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
|
6月前
|
开发工具 Android开发
X Android SDK file not found: adb.安卓开发常见问题-Android SDK 缺少 `adb`(Android Debug Bridge)-优雅草卓伊凡
X Android SDK file not found: adb.安卓开发常见问题-Android SDK 缺少 `adb`(Android Debug Bridge)-优雅草卓伊凡
723 11
X Android SDK file not found: adb.安卓开发常见问题-Android SDK 缺少 `adb`(Android Debug Bridge)-优雅草卓伊凡
|
5月前
|
移动开发 Android开发
【03】建立隐私关于等相关页面和内容-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【03】建立隐私关于等相关页面和内容-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
278 0
|
6月前
|
Java 开发工具 Maven
【01】完整的安卓二次商业实战-详细的初级步骤同步项目和gradle配置以及开发思路-优雅草伊凡
【01】完整的安卓二次商业实战-详细的初级步骤同步项目和gradle配置以及开发思路-优雅草伊凡
668 6
|
8月前
|
安全 数据库 Android开发
在Android开发中实现两个Intent跳转及数据交换的方法
总结上述内容,在Android开发中,Intent不仅是活动跳转的桥梁,也是两个活动之间进行数据交换的媒介。运用Intent传递数据时需注意数据类型、传输大小限制以及安全性问题的处理,以确保应用的健壯性和安全性。
541 11