Android平台如何不推RTMP|不发布RTSP流|不实时录像|不回传GB28181数据时实时快照?

本文涉及的产品
视觉智能开放平台,视频资源包5000点
视觉智能开放平台,图像资源包5000点
视觉智能开放平台,分割抠图1万点
简介: 本文介绍了一种在Android平台上实现实时截图快照的方法,尤其适用于无需依赖系统接口的情况,如在RTMP推送、RTSP服务或GB28181设备接入等场景下进行截图。通过底层模块(libSmartPublisher.so)实现了截图功能,封装了`SnapShotImpl.java`类来管理截图流程。此外,提供了关键代码片段展示初始化SDK实例、执行截图、以及在Activity销毁时释放资源的过程。此方案还考虑到了快照数据的灵活处理需求,符合GB/T28181-2022的技术规范。对于寻求更灵活快照机制的开发者来说,这是一个值得参考的设计思路。

 技术背景

我们知道,Android平台不管RTMP推送、轻量级RTSP服务模块还是GB28181设备接入模块,早期,如果需要实现截图功能,又不想依赖Android系统接口,最好的办法是,在底层实现快照截图。

快照截图,实际上我们2016年就支持了,不过,需要在RTMP推送、轻量级RTSP服务发布RTSP流、开启实时录像或GB28181设备接入侧已经在传数据的时候,有数据下去,才可以实现截图快照。

本次,我们要实现的是,上述条件不满足的情况下,如何让大牛直播SDK的底层模块(libSmartPublisher.so)实时截图。

技术实现

本文以大牛直播SDK的Camera2Demo为例,废话不多说,上方案:

image.gif

这里,我们专门封装了 SnapShotImpl.java

/*
 * SnapShotImpl.java
 * Author: daniusdk.com
 * WeChat: xinsheng120
 */
public SnapShotImpl(String dir, Context context, android.os.Handler handler,
                    SmartPublisherJniV2 lib_sdk, LibPublisherWrapper publisher) {
    this.dir_ = dir;
    if (context != null)
        this.context_ = new WeakReference<>(context);
    if (handler != null)
        this.os_handler_ = new WeakReference<>(handler);
    this.lib_sdk_ = lib_sdk;
    this.publisher_ = publisher;
}

image.gif

init_sdk_instance()实现:

protected boolean init_sdk_instance() {
    if (!publisher_.empty())
        return true;
    Context context = application_context();
    if (null == context)
        return false;
    if (null == lib_sdk_)
        return false;
    long handle = lib_sdk_.SmartPublisherOpen(context, 0, 3, 1920, 1080);
    if (0 == handle) {
        Log.e(TAG, "init_sdk_instance sdk open failed!");
        return false;
    }
    lib_sdk_.SetSmartPublisherEventCallbackV2(handle, new SDKEventHandler(this));
    lib_sdk_.SmartPublisherSetFPS(handle, 4);
    publisher_.set(lib_sdk_, handle);
    update_sdk_instance_release_time();
    Log.i(TAG, "init_sdk_instance ok handle:" + handle);
    return true;
}

image.gif

capture()实现:

protected boolean capture(String file_name, boolean is_update_layers, String user_data) {
    if (is_null_or_empty(file_name)) {
        Log.e(TAG, "capture file name is null");
        return false;
    }
    if (publisher_.empty()) {
        if (!init_sdk_instance()) {
            Log.e(TAG, "init_sdk_instance failed");
            return false;
        }
    }
    if (is_update_layers && layer_post_thread_ != null)
        layer_post_thread_.update_layers();
    boolean ret = publisher_.CaptureImage(0, 100, file_name, user_data);
    if (ret)
        update_sdk_instance_release_time();
    return ret;
}
public boolean capture() {
    if (is_null_or_empty(dir_)) {
        Log.e(TAG, "capture dir is null");
        return false;
    }
    String file_name = dir_ + File.separatorChar +  make_current_date_time_string() + JPEG_SUFFIX;
    boolean ret = capture(file_name, true, null);
    Log.i(TAG, "capture ret:" + ret + ", file:" + file_name);
    return  ret;
}

image.gif

对应的CaptureImge()封装如下:

public boolean CaptureImage(int compress_format, int quality, String file_name, String user_data_string) {
    if (!check_native_handle())
        return false;
    return OK == lib_publisher_.CaptureImage(get(), compress_format, quality, file_name, user_data_string);
}

image.gif

模块头文件SmartPublisherJniV2.java接口设计如下:

/**
 * 新的截图接口, 支持JPEG和PNG两种格式
 * @param compress_format: 压缩格式, 0:JPEG格式, 1:PNG格式, 其他返回错误
 * @param quality: 取值范围:[0, 100], 值越大图像质量越好, 仅对JPEG格式有效, 若是PNG格式,请填100
 * @param file_name: 图像文件名, 例如:/dirxxx/test20231113100739.jpeg, /dirxxx/test20231113100739.png
 * @param user_data_string: 用户自定义字符串
 * @return {0} if successful
 */
 public native int CaptureImage(long handle, int compress_format, int quality, String file_name, String user_data_string);

image.gif

最外层MainActivity.java调用示例如下:

private final LibPublisherWrapper snap_shot_publisher_ = new LibPublisherWrapper();
private LibPublisherWrapper publisher_array_[] = {stream_publisher_, snap_shot_publisher_};
class ButtonCaptureImageListener implements View.OnClickListener {
    public void onClick(View v) {
        if (null == snap_shot_impl_) {
            snap_shot_impl_ = new SnapShotImpl(image_path_, context_, handler_, libPublisher, snap_shot_publisher_);
            snap_shot_impl_.start();
        }
        startLayerPostThread();
        snap_shot_impl_.set_layer_post_thread(layer_post_thread_);
        snap_shot_impl_.capture();
    }
}

image.gif

记得投递数据,让有截图快照的数据源传递到底层模块:

@Override
public void onCameraImageData(Image image) {
    Image.Plane[] planes = image.getPlanes();
    ...
    for (LibPublisherWrapper i : publisher_array_)
        i.PostLayerImageYUV420888ByteBuffer(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);
}

image.gif

onDestroy()的时候,记得停掉,并释放资源:

@Override
protected void onDestroy() {
    Log.i(TAG, "activity destory!");
    
    ...
    if (snap_shot_impl_ != null) {
        snap_shot_impl_.stop();
        snap_shot_impl_ = null;
    }
    snap_shot_publisher_.release();
    super.onDestroy();
}

image.gif

总结

市面上,咱们能看到的实时截图快照,大多是要么直接基于Android系统接口实现,要么只能在RTMP推送、实时录像、轻量级RTSP服务发布流数据、GB28181设备接入侧回传音视频数据的时候才可以用,如果想要更灵活的处理快照数据,特别是,实现GB/T28181-2022关于快照的技术规范诉求,灵活的快照模式,需要底层模块设计的非常灵活才行,以上是Android平台推送端实时快照的大概设计逻辑,感兴趣的开发者,可以单独跟我沟通讨论。

相关文章
|
13天前
|
Java Android开发 Swift
安卓与iOS开发对比:平台选择对项目成功的影响
【10月更文挑战第4天】在移动应用开发的世界中,选择合适的平台是至关重要的。本文将深入探讨安卓和iOS两大主流平台的开发环境、用户基础、市场份额和开发成本等方面的差异,并分析这些差异如何影响项目的最终成果。通过比较这两个平台的优势与挑战,开发者可以更好地决定哪个平台更适合他们的项目需求。
51 1
|
29天前
|
IDE Android开发 iOS开发
探索Android与iOS开发的差异:平台选择对项目成功的影响
【9月更文挑战第27天】在移动应用开发的世界中,Android和iOS是两个主要的操作系统平台。每个系统都有其独特的开发环境、工具和用户群体。本文将深入探讨这两个平台的关键差异点,并分析这些差异如何影响应用的性能、用户体验和最终的市场表现。通过对比分析,我们将揭示选择正确的开发平台对于确保项目成功的重要作用。
|
4天前
|
Linux API 开发工具
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
ijkplayer是由B站研发的移动端播放器,基于FFmpeg 3.4,支持Android和iOS。其源码托管于GitHub,截至2024年9月15日,获得了3.24万星标和0.81万分支,尽管已停止更新6年。本文档介绍了如何在Linux环境下编译ijkplayer的so库,以便在较新的开发环境中使用。首先需安装编译工具并调整/tmp分区大小,接着下载并安装Android SDK和NDK,最后下载ijkplayer源码并编译。详细步骤包括环境准备、工具安装及库编译等。更多FFmpeg开发知识可参考相关书籍。
22 0
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
|
19天前
|
XML 存储 Java
探索安卓开发之旅:从基础到进阶
【9月更文挑战第37天】安卓开发,一个充满无限可能的领域。它不仅关乎技术的深度与广度,更关乎开发者的成长与突破。本文将带你走进安卓开发的世界,从基础知识的学习到进阶技巧的掌握,一起感受编程的魅力与乐趣。
|
13天前
|
缓存 搜索推荐 Android开发
安卓开发中的自定义控件实践
【10月更文挑战第4天】在安卓开发的海洋中,自定义控件是那片璀璨的星辰。它不仅让应用界面设计变得丰富多彩,还提升了用户体验。本文将带你探索自定义控件的核心概念、实现过程以及优化技巧,让你的应用在众多竞争者中脱颖而出。
|
16天前
|
Android开发
Android开发表情emoji功能开发
本文介绍了一种在Android应用中实现emoji表情功能的方法,通过将图片与表情字符对应,实现在`TextView`中的正常显示。示例代码展示了如何使用自定义适配器加载emoji表情,并在编辑框中输入或删除表情。项目包含完整的源码结构,可作为开发参考。视频演示和源码详情见文章内链接。
41 4
Android开发表情emoji功能开发
|
14天前
|
Web App开发 安全 程序员
FFmpeg开发笔记(五十五)寒冬里的安卓程序员可进阶修炼的几种姿势
多年的互联网寒冬在今年尤为凛冽,坚守安卓开发愈发不易。面对是否转行或学习新技术的迷茫,安卓程序员可从三个方向进阶:1)钻研谷歌新技术,如Kotlin、Flutter、Jetpack等;2)拓展新功能应用,掌握Socket、OpenGL、WebRTC等专业领域技能;3)结合其他行业,如汽车、游戏、安全等,拓宽职业道路。这三个方向各有学习难度和保饭碗指数,助你在安卓开发领域持续成长。
42 1
FFmpeg开发笔记(五十五)寒冬里的安卓程序员可进阶修炼的几种姿势
|
12天前
|
缓存 搜索推荐 Android开发
安卓开发中的自定义控件基础与进阶
【10月更文挑战第5天】在Android应用开发中,自定义控件是提升用户体验和界面个性化的重要手段。本文将通过浅显易懂的语言和实例,引导你了解自定义控件的基本概念、创建流程以及高级应用技巧,帮助你在开发过程中更好地掌握自定义控件的使用和优化。
25 10
|
16天前
|
JSON API Android开发
探索安卓开发之旅:打造你的第一个天气应用
在这篇文章中,我们将一起踏上一段激动人心的旅程,学习如何在安卓平台上开发一个简单的天气应用。通过实际操作和代码示例,我们将逐步构建一个能够显示当前位置天气情况的应用。无论你是编程新手还是有一定经验的开发者,这篇文章都将为你提供清晰的指导和启发性的见解,帮助你理解和掌握安卓开发的基础知识。让我们一起探索代码的世界,解锁新技能,实现你的创意和梦想。
|
12天前
|
Android开发 Swift iOS开发
探索安卓与iOS开发的差异:从代码到用户体验
【10月更文挑战第5天】在移动应用开发的广阔天地中,安卓和iOS两大平台各占半壁江山。它们在技术架构、开发环境及用户体验上有着根本的不同。本文通过比较这两种平台的开发过程,揭示背后的设计理念和技术选择如何影响最终产品。我们将深入探讨各自平台的代码示例,理解开发者面临的挑战,以及这些差异如何塑造用户的日常体验。