Android平台RTMP推送|轻量级RTSP服务|GB28181设备接入模块之实时快照保存JPG还是PNG?

手机APP/开发
305
0
0
2024-04-02
标签   Android

JPG还是PNG?

JPG和PNG是两种常见的图片文件格式,在压缩方式、图像质量、透明效果和可编辑性等方面存在显著差异。

  • 压缩方式:JPG是一种有损压缩格式,通过丢弃图像数据来减小文件大小,因此可能会损失一些图像细节和质量。而PNG使用的是无损压缩格式,它不会丢失任何原始图像数据,从而保持了图像的完整性和质量。
  • 图像质量:由于压缩方式的不同,JPG在压缩后会牺牲一部分图像数据,因此在图像质量上可能存在损失,例如可能会出现锯齿状边缘或颜色失真。相比之下,PNG的无损压缩可以保证原图像数据的完整性,其256个透明层次的设定可以使图片边缘平滑融合,从而消除图片锯齿边缘。
  • 透明效果:PNG支持透明度,可以用作背景透明的图片,而JPG则不支持透明效果。因此,如果你需要制作半透明的图像或者需要背景透明的图片,PNG是一个更好的选择。
  • 可编辑性:JPG是一种不可编辑的图片格式,一旦被保存为JPG格式,就无法进行修改。而PNG是一种可编辑的图片格式,可以通过图像编辑软件(如Photoshop)进行修改、编辑和重新保存。例如,你可以改变PNG图片中的文字样式、线条等元素。

Android推流端的截图设计

大牛直播SDK早期在做Android平台RTMP推流和轻量级RTSP服务模块的时候,截图考虑到PNG的特性,直接保存png图片,随着GB28181-2022规范的实施,规范里面有明确要求,需要支持JPG编码,为此我们针对截图这块,做了如下的调整(对应:实时快照):

原接口:

	 /**
	  * 请使用新的CaptureImage接口, 这个接口只能保存PNG图片, 不推荐使用
	  * Save current image during publishing stream(实时快照)
	  *
	  * @param imageName: image name, which including fully path, "/sdcard/daniuliveimage/daniu.png", etc.
	  *
	  * @return {0} if successful
	  */
	 public native int SmartPublisherSaveCurImage(long handle,  String imageName);

值得注意的是,原接口如果需要截图,还需要调用SmartPublisherSaveImageFlag()。

新的接口,我们设计如下:

	/**
	 * 新的截图接口, 支持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);

如何调用?

废话不多说,直接上代码:

    private SimpleDateFormat capture_image_date_format_;

    class ButtonCaptureImageListener implements OnClickListener {
        @SuppressLint("SimpleDateFormat")
        public void onClick(View v) {
            if(isPushingRtmp || isRecording || isRTSPPublisherRunning || isPushingRtsp)
            {
                if (null == capture_image_date_format_)
                    capture_image_date_format_ = new SimpleDateFormat("yyyyMMddHHmmssSSS");

                String timeStamp = capture_image_date_format_.format(new Date());
                String imageFileName = timeStamp;    //创建以时间命名的文件名称

                String imagePath = imageSavePath + "/" + imageFileName;

                int quality;
                boolean is_jpeg = true;
                if (is_jpeg) {
                    imagePath += ".jpeg";
                    quality = 100;
                }
                else {
                    imagePath += ".png";
                    quality = 100;
                }

                int capture_ret = libPublisher.CaptureImage(publisherHandle,is_jpeg?0:1, quality, imagePath, "test_user_data");
                Log.i(TAG, "capture_image ret:" + capture_ret + ", file:" + imagePath);
            }
            else
            {
                Log.e(TAG, "快照失败,请确保在推送、录像或内置RTSP服务发布状态..");
            }
        }
    }

截图成功,对应的event回调如下:

    class EventHandeV2 implements NTSmartEventCallbackV2 {
        @Override
        public void onNTSmartEventCallbackV2(long handle, int id, long param1, long param2, String param3, String param4, Object param5) {

            Log.i(TAG, "EventHandeV2: handle=" + handle + " id:" + id);

            String publisher_event = "";

            switch (id) {
                .....

                case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_CAPTURE_IMAGE:
                    publisher_event = "快照: " + param1 + " 路径:" + param3;
                    if (0 == param1) {
                        rename_image_file_name(param3, param2);
                        publisher_event = publisher_event + "截取快照成功.." + ", 用户数据:" + param4;
                    } else
                        publisher_event = publisher_event + "截取快照失败..";

                    break;
                    ....
        }
    }

如果需要对截图后的文件重命名(比如gb28181,我们会把截图时间返上来),便于统一管理,参考代码如下:

    private void rename_image_file_name(String file_name, long file_date_time_ms) {
        if (null == file_name || file_name.isEmpty()
                || file_date_time_ms < 1 || null == capture_image_date_format_)
            return;

        try {
            java.io.File file = new File(file_name);
            if (!file.exists() || !file.isFile() || !file.canRead() || file.length() < 1)
                return;

            String file_name_extension = null;
            int index = file_name.lastIndexOf('.');
            if (index > -1)
                file_name_extension = file_name.substring(index + 1);

            Date file_date = new Date(file_date_time_ms);
            String new_file_name = capture_image_date_format_.format(file_date);
            if (file_name_extension != null && !file_name_extension.isEmpty())
                new_file_name += "." + file_name_extension;

            java.io.File new_file = new java.io.File(file.getParent(), new_file_name);
            if (file.renameTo(new_file))
                Log.i(TAG, "rename image file name ok, file_name:" + file_name + ", new:"+ new_file_name);
             else
                Log.e(TAG, "rename image file name failed, file_name:" + file_name);

        } catch (Exception e) {
            Log.e(TAG, "rename_image_file_name Exception:", e);
        }
    }

总结

Android平台RTMP推送、轻量级RTSP还是GB28181设备对接模块,选择哪种图片格式主要取决于具体的使用需求。如果你需要压缩图像文件并且不关心原始图像的完整性,JPG可能是一个更好的选择。而如果你需要保持原始图像的完整性和质量,或者需要制作背景透明的图片,那么PNG可能是更好的选择。