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

​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编码,为此我们针对截图这块,做了如下的调整(对应:实时快照):

原接口:

java 复制代码
	 /**
	  * 请使用新的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()。

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

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

如何调用?

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

ini 复制代码
    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回调如下:

arduino 复制代码
    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,我们会把截图时间返上来),便于统一管理,参考代码如下:

ini 复制代码
    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可能是更好的选择。

相关推荐
aqi0012 天前
FFmpeg开发笔记(五十二)移动端的国产视频播放器GSYVideoPlayer
android·ffmpeg·音视频·直播·流媒体
aqi0013 天前
FFmpeg开发笔记(五十一)适合学习研究的几个音视频开源框架
ffmpeg·音视频·直播·流媒体
aqi0019 天前
FFmpeg开发笔记(五十)聊聊几种流媒体传输技术的前世今生
ffmpeg·音视频·直播·流媒体
aqi0020 天前
FFmpeg开发笔记(四十九)助您在毕业设计中脱颖而出的几个流行APP
ffmpeg·音视频·直播·流媒体
aqi001 个月前
FFmpeg开发笔记(四十八)从0开始搭建直播系统的开源软件架构
ffmpeg·音视频·直播·流媒体
aqi001 个月前
FFmpeg开发笔记(四十七)寒冬下安卓程序员的几个技术转型发展方向
ffmpeg·音视频·直播·流媒体
aqi001 个月前
FFmpeg开发笔记(四十六)利用SRT协议构建手机APP的直播Demo
ffmpeg·音视频·直播·流媒体
x007xyz1 个月前
前端纯手工绘制音频波形图
前端·音视频开发·canvas
aqi001 个月前
FFmpeg开发笔记(四十五)使用SRT Streamer开启APP直播推流
ffmpeg·音视频·直播·流媒体
音视频牛哥1 个月前
Android摄像头采集选Camera1还是Camera2?
音视频开发·视频编码·直播