Android平台RTSP|RTMP播放器高效率如何回调YUV或RGB数据?

技术背景

我们在做Android平台RTSP、RTMP播放器的时候,经常遇到这样的技术诉求,开发者希望拿到播放器解码后的YUV或RGB数据,投递给视觉算法,做AI分析,本文以ffmpeg和大牛直播SDK的SmartPlayer为例,介绍下相关的技术实现。

FFmpeg

FFmpeg 是一个开源的跨平台多媒体处理工具库,广泛应用于音视频处理领域。

格式转换

  • 可以在众多不同的音频和视频格式之间进行转换。例如,将 MP4 格式转换为 AVI 格式,或者将 WAV 音频文件转换为 MP3 格式。
  • 支持几乎所有常见的音视频编码格式,如 H.264、H.265、AAC、MP3 等。

编码与解码

  • 能够对各种音视频编码格式进行解码,将压缩的音视频数据还原为原始的图像和音频信号。
  • 同时也可以进行编码操作,将原始的音视频数据压缩成特定的编码格式,以减小文件大小或满足特定的播放需求。
  1. 视频处理

    • 可以进行视频剪辑、拼接、裁剪等操作。比如,从一个长视频中截取特定的片段,或者将多个视频片段拼接成一个新的视频。
    • 支持视频的旋转、缩放、滤镜添加等特效处理。例如,将视频进行 90 度旋转,或者对视频应用模糊、锐化等滤镜效果。

音频处理

  • 可以进行音频的混音、提取、音量调整等操作。例如,将多个音频文件混合在一起,或者从视频中提取音频轨道。
  • 支持音频的均衡器调整、降噪等处理,以改善音频质量。

流媒体处理

  • 能够处理实时流媒体,如 RTMP、RTSP、HLS 等协议。可以进行流媒体的录制、转码、分发等操作。
  • 对于直播场景,FFmpeg 可以作为推流或拉流的工具,实现视频直播的采集、编码和传输。

集成 FFmpeg

  • 将 FFmpeg 库集成到 Android 项目中,可以通过使用 Android NDK 来编译和链接 FFmpeg 库。
  • 配置项目的build.gradle文件,添加 NDK 相关的配置,并创建一个 JNI 层的接口来调用 FFmpeg 的功能。

利用 FFmpeg 解码视频并获取 YUV 数据

  • 在 JNI 层的代码中,使用 FFmpeg 的解码功能来解码 RTSP/RTMP 视频流。FFmpeg 提供了丰富的 API 来处理各种多媒体格式。
  • 在解码过程中,可以获取解码后的视频帧,并将其转换为 YUV 格式的数据。然后通过 JNI 回调将 YUV 数据传递到 Java 层。
  • 以下是一个简单的 JNI 方法示例,用于解码视频并回调 YUV 数据:
 #include <jni.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <android/log.h>
 #include <libavcodec/avcodec.h>
 #include <libavformat/avformat.h>
 #include <libswscale/swscale.h>

 #define LOG_TAG "FFmpegNative"
 #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)

 AVFormatContext *pFormatCtx = NULL;
 AVCodecContext *pCodecCtx = NULL;
 AVCodec *pCodec = NULL;
 AVFrame *pFrame = NULL;
 AVPacket packet;
 struct SwsContext *img_convert_ctx = NULL;

 jobject yuvCallbackObj;
 jmethodID yuvCallbackMethod;

 void Java_com_example_myapp_MyNativeLib_init(JNIEnv *env, jobject obj, jstring url, jobject callbackObj, jmethodID callbackMethod) {
	 const char *input_filename = (*env)->GetStringUTFChars(env, url, NULL);
	 yuvCallbackObj = (*env)->NewGlobalRef(env, callbackObj);
	 yuvCallbackMethod = callbackMethod;

	 av_register_all();
	 avformat_network_init();

	 if (avformat_open_input(&pFormatCtx, input_filename, NULL, NULL)!= 0) {
		 LOGE("Could not open input file.");
		 return;
	 }

	 if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
		 LOGE("Could not find stream information.");
		 return;
	 }

	 int videoStream = -1;
	 for (int i = 0; i < pFormatCtx->nb_streams; i++) {
		 if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
			 videoStream = i;
			 break;
		 }
	 }

	 if (videoStream == -1) {
		 LOGE("Could not find video stream.");
		 return;
	 }

	 pCodecCtx = avcodec_alloc_context3(NULL);
	 if (!pCodecCtx) {
		 LOGE("Could not allocate video codec context.");
		 return;
	 }

	 avcodec_parameters_to_context(pCodecCtx, pFormatCtx->streams[videoStream]->codecpar);

	 pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
	 if (!pCodec) {
		 LOGE("Could not find codec.");
		 return;
	 }

	 if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
		 LOGE("Could not open codec.");
		 return;
	 }

	 pFrame = av_frame_alloc();
	 if (!pFrame) {
		 LOGE("Could not allocate video frame.");
		 return;
	 }
 }

 void Java_com_example_myapp_MyNativeLib_decodeAndCallback(JNIEnv *env, jobject obj) {
	 while (av_read_frame(pFormatCtx, &packet) >= 0) {
		 if (packet.stream_index == videoStream) {
			 int ret = avcodec_send_packet(pCodecCtx, &packet);
			 if (ret < 0) {
				 LOGE("Error sending a packet for decoding.");
				 av_packet_unref(&packet);
				 continue;
			 }

			 while (ret >= 0) {
				 ret = avcodec_receive_frame(pCodecCtx, pFrame);
				 if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
					 break;
				 } else if (ret < 0) {
					 LOGE("Error during decoding.");
					 break;
				 }

				 // 将解码后的帧转换为 YUV 格式
				 if (img_convert_ctx == NULL) {
					 img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height,
													   pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height,
													   AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);
					 if (!img_convert_ctx) {
						 LOGE("Could not initialize the conversion context.");
						 return;
					 }
				 }

				 AVFrame *yuvFrame = av_frame_alloc();
				 if (!yuvFrame) {
					 LOGE("Could not allocate YUV frame.");
					 return;
				 }

				 yuvFrame->format = AV_PIX_FMT_YUV420P;
				 yuvFrame->width = pCodecCtx->width;
				 yuvFrame->height = pCodecCtx->height;

				 int bufferSize = av_image_get_buffer_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1);
				 uint8_t *buffer = (uint8_t *)av_malloc(bufferSize);
				 av_image_fill_arrays(yuvFrame->data, yuvFrame->linesize, buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1);

				 sws_scale(img_convert_ctx, (const uint8_t *const *)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, yuvFrame->data, yuvFrame->linesize);

				 // 回调 YUV 数据到 Java 层
				 (*env)->CallVoidMethod(env, yuvCallbackObj, yuvCallbackMethod, buffer, bufferSize);

				 av_free(buffer);
				 av_frame_free(&yuvFrame);
			 }
		 }

		 av_packet_unref(&packet);
	 }
 }

 void Java_com_example_myapp_MyNativeLib_release(JNIEnv *env, jobject obj) {
	 if (pFormatCtx) {
		 avformat_close_input(&pFormatCtx);
	 }

	 if (pCodecCtx) {
		 avcodec_close(pCodecCtx);
		 avcodec_free_context(&pCodecCtx);
	 }

	 if (pFrame) {
		 av_frame_free(&pFrame);
	 }

	 if (img_convert_ctx) {
		 sws_freeContext(img_convert_ctx);
	 }

	 (*env)->DeleteGlobalRef(env, yuvCallbackObj);
 }

SmartPlayer

SmartPlayer是大牛直播SDK旗下全自研内核,行业内一致认可的跨平台RTSP、RTMP直播播放器SDK,功能齐全、高稳定、超低延迟,超低资源占用,适用于安防、教育、单兵指挥等行业。

功能设计如下:

  •  [支持播放协议]高稳定、超低延迟、业内首屈一指的RTSP直播播放器SDK;
  •  [多实例播放]支持多实例播放;
  •  [事件回调]支持网络状态、buffer状态等回调;
  •  [视频格式]支持H.265、H.264,此外,还支持RTSP MJPEG播放;
  •  [音频格式]支持AAC/PCMA/PCMU;
  •  [H.264/H.265软解码]支持H.264/H.265软解;
  •  [H.264硬解码]Windows/Android/iOS支持特定机型H.264硬解;
  •  [H.265硬解]Windows/Android/iOS支持特定机型H.265硬解;
  •  [H.264/H.265硬解码]Android支持设置Surface模式硬解和普通模式硬解码;
  •  [RTSP模式设置]支持RTSP TCP/UDP模式设置;
  •  [RTSP TCP/UDP自动切换]支持RTSP TCP、UDP模式自动切换;
  •  [RTSP超时设置]支持RTSP超时时间设置,单位:秒;
  •  [RTSP 401认证处理]支持上报RTSP 401事件,如URL携带鉴权信息,会自动处理;
  •  [缓冲时间设置]支持buffer time设置;
  •  [首屏秒开]支持首屏秒开模式;
  •  [复杂网络处理]支持断网重连等各种网络环境自动适配;
  •  [快速切换URL]支持播放过程中,快速切换其他URL,内容切换更快;
  •  [音视频多种render机制]Android平台,视频:surfaceview/OpenGL ES,音频:AudioTrack/OpenSL ES;
  •  [实时静音]支持播放过程中,实时静音/取消静音;
  •  [实时音量调节]支持播放过程中实时调节音量;
  •  [实时快照]支持播放过程中截取当前播放画面;
  •  [只播关键帧]Windows平台支持实时设置是否只播放关键帧;
  •  [渲染角度]支持0°,90°,180°和270°四个视频画面渲染角度设置;
  •  [渲染镜像]支持水平反转、垂直反转模式设置;
  •  [等比例缩放]支持图像等比例缩放绘制(Android设置surface模式硬解模式不支持);
  •  [实时下载速度更新]支持当前下载速度实时回调(支持设置回调时间间隔);
  •  [解码前视频数据回调]支持H.264/H.265数据回调;
  •  [解码后视频数据回调]支持解码后YUV/RGB数据回调;
  •  [解码前音频数据回调]支持AAC/PCMA/PCMU数据回调;
  •  [音视频自适应]支持播放过程中,音视频信息改变后自适应;
  •  [扩展录像功能]完美支持和录像SDK组合使用。

播放之前,设置YUV数据回调:

/*
 * SmartPlayer.java
 * Copyright © 2014~2024 daniusdk.com All rights reserved.
 */
private boolean StartPlay()
{
	if(isPlaying)
		return false;

	if(!isPulling)
	{
		if (!OpenPullHandle())
			return false;
	}

	// 如果第二个参数设置为null,则播放纯音频
	libPlayer.SmartPlayerSetSurface(player_handle_, sSurfaceView);
	//libPlayer.SmartPlayerSetSurface(player_handle_, null);

	libPlayer.SmartPlayerSetRenderScaleMode(player_handle_, 1);

	if(video_opt_ == 3)
	{
		libPlayer.SmartPlayerSetExternalRender(player_handle_, new I420ExternalRender(publisher_array_));
	}

	//libPlayer.SmartPlayerSetExternalAudioOutput(player_handle_, new PlayerExternalPCMOutput(stream_publisher_));

	libPlayer.SmartPlayerSetFastStartup(player_handle_, isFastStartup ? 1 : 0);

	libPlayer.SmartPlayerSetAudioOutputType(player_handle_, 1);

	if (isMute) {
		libPlayer.SmartPlayerSetMute(player_handle_, isMute ? 1	: 0);
	}

	if (isHardwareDecoder)
	{
		int isSupportH264HwDecoder = libPlayer.SetSmartPlayerVideoHWDecoder(player_handle_, 1);

		int isSupportHevcHwDecoder = libPlayer.SetSmartPlayerVideoHevcHWDecoder(player_handle_, 1);

		Log.i(TAG, "isSupportH264HwDecoder: " + isSupportH264HwDecoder + ", isSupportHevcHwDecoder: " + isSupportHevcHwDecoder);
	}

	libPlayer.SmartPlayerSetLowLatencyMode(player_handle_, isLowLatency ? 1	: 0);

	libPlayer.SmartPlayerSetRotation(player_handle_, rotate_degrees);

	int iPlaybackRet = libPlayer.SmartPlayerStartPlay(player_handle_);

	if (iPlaybackRet != 0 && !isPulling) {
		Log.e(TAG, "StartPlay failed!");

		releasePlayerHandle();
		return false;
	}

	isPlaying = true;


	return true;
}

YUV数据回调上层实现:

private static class I420ExternalRender implements NTExternalRender {
	// public static final int NT_FRAME_FORMAT_RGBA = 1;
	// public static final int NT_FRAME_FORMAT_ABGR = 2;
	// public static final int NT_FRAME_FORMAT_I420 = 3;
	private WeakReference<LibPublisherWrapper> publisher_;
	private ArrayList<WeakReference<LibPublisherWrapper> > publisher_list_;

	private int width_;
	private int height_;

	private int y_row_bytes_;
	private int u_row_bytes_;
	private int v_row_bytes_;

	private ByteBuffer y_buffer_;
	private ByteBuffer u_buffer_;
	private ByteBuffer v_buffer_;

	public I420ExternalRender(LibPublisherWrapper publisher) {
		if (publisher != null)
			publisher_ = new WeakReference<>(publisher);
	}

	public I420ExternalRender(LibPublisherWrapper[] publisher_list) {
		if (publisher_list != null && publisher_list.length > 0) {
			for (LibPublisherWrapper i : publisher_list) {
				if (i != null) {
					if (null == publisher_list_)
						publisher_list_ = new ArrayList<>();

					publisher_list_.add(new WeakReference<>(i));
				}
			}
		}
	}

	private final List<LibPublisherWrapper> get_publisher_list() {
		if (null == publisher_list_ || publisher_list_.isEmpty())
			return null;

		ArrayList<LibPublisherWrapper> list = new ArrayList<>(publisher_list_.size());
		for (WeakReference<LibPublisherWrapper> i : publisher_list_) {
			if (i != null) {
				LibPublisherWrapper o = i.get();
				if (o != null && !o.empty())
					list.add(o);
			}
		}

		return list;
	}

	@Override
	public int getNTFrameFormat() {
		Log.i(TAG, "I420ExternalRender::getNTFrameFormat return "
				+ NT_FRAME_FORMAT_I420);
		return NT_FRAME_FORMAT_I420;
	}

	private static int align(int d, int a) { return (d + (a - 1)) & ~(a - 1); }

	@Override
	public void onNTFrameSizeChanged(int width, int height) {
		width_ = width;
		height_ = height;

		y_row_bytes_ = width;
		u_row_bytes_ = (width+1)/2;
		v_row_bytes_ = (width+1)/2;

		y_buffer_ = ByteBuffer.allocateDirect(y_row_bytes_*height_);
		u_buffer_ = ByteBuffer.allocateDirect(u_row_bytes_*((height_ + 1) / 2));
		v_buffer_ = ByteBuffer.allocateDirect(v_row_bytes_*((height_ + 1) / 2));

	}

	@Override
	public ByteBuffer getNTPlaneByteBuffer(int index) {
		switch (index) {
			case 0:
				return y_buffer_;
			case 1:
				return u_buffer_;
			case 2:
				return v_buffer_;
			default:
				Log.e(TAG, "I420ExternalRender::getNTPlaneByteBuffer index error:" + index);
				return null;
		}
	}

	@Override
	public int getNTPlanePerRowBytes(int index) {
		switch (index) {
			case 0:
				return y_row_bytes_;
			case 1:
				return u_row_bytes_;
			case 2:
				return v_row_bytes_;
			default:
				Log.e(TAG, "I420ExternalRender::getNTPlanePerRowBytes index error:" + index);
				return 0;
		}
	}

	public void onNTRenderFrame(int width, int height, long timestamp)
	{
		if (null == y_buffer_ || null == u_buffer_ || null == v_buffer_)
			return;

		Log.i(TAG, "I420ExternalRender::onNTRenderFrame " + width + "*" + height + ", t:" + timestamp);


		y_buffer_.rewind();
		u_buffer_.rewind();
		v_buffer_.rewind();

		List<LibPublisherWrapper> publisher_list = get_publisher_list();
		if (null == publisher_list || publisher_list.isEmpty())
			return;

		for (LibPublisherWrapper i : publisher_list) {
			i.PostLayerImageI420ByteBuffer(0, 0, 0,
					y_buffer_, 0, y_row_bytes_,
					u_buffer_, 0, u_row_bytes_,
					v_buffer_, 0, v_row_bytes_,
					width_, height_, 0, 0,
					0,0, 0,0);
		}

	}
}

总结

Android平台RTSP、RTMP播放器回调yuv数据,意义非常重大,既保证了低延迟传输解码,又可以通过回调解码后数据,高效率的投递给AI算法,实现视觉处理。ffmpeg实现还是SmartPlayer,各有利弊,感兴趣的开发者,可以单独跟我探讨。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/907303.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

个体营业执照经营异常多久解除

经营异常是怎么回事&#xff1f;是什么意思&#xff1f;首先&#xff0c;我们要明确什么是公司经营异常。简单来说&#xff0c;就是公司在经营过程中出现了一些问题&#xff0c;导致公司无法正常运营。这些问题可能包括未按规定报送年度报告、未按规定公示有关信息等。那么&…

Air780E如何发送SMS?一文详解!

今天一起来学习使用合宙低功耗4G模组Air780E发送SMS短消息&#xff1a; 一、SMS简介 SMS&#xff08;短消息服务&#xff0c;ShortMessageService&#xff09;功能主要用于在蜂窝网络中传输短消息。 在4G网络中&#xff0c;短信可以在数据传输的同时进行&#xff0c;不会因数…

金华迪加 现场大屏互动系统 mobile.do.php 任意文件上传漏洞复现

0x01 产品简介 金华迪加现场大屏互动系统是一种集成了先进技术和创意设计的互动展示解决方案,旨在通过大屏幕和多种交互方式,为观众提供沉浸式的互动体验。该系统广泛应用于各类活动、展览、会议等场合,能够显著提升现场氛围和参与者的体验感。 0x02 漏洞概述 金华迪加 现…

基于Java+SpringBoot+Vue的视频网站系统的设计与实现

基于JavaSpringBootVue的视频网站系统的设计与实现 前言 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN[新星计划]导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末附源码下载链接&#x1…

ComfyUI - ComfyUI 工作流中集成 SAM2 + GroundingDINO 处理图像与视频 教程

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/143359538 免责声明&#xff1a;本文来源于个人知识与公开资料&#xff0c;仅用于学术交流&#xff0c;欢迎讨论&#xff0c;不支持转载。 SAM2 与…

【华硕天选5开机黑屏只有鼠标,调用资源管理器也无法黑屏状态的一种解决方式】

华硕天选5开机黑屏只有鼠标&#xff0c;调用资源管理器也无法黑屏状态的一种解决方式 1.问题描述2.解决方法3.重启如下图 1.问题描述 华硕天选5开机黑屏只有鼠标&#xff0c;调用资源管理器&#xff08;ctrlalt.&#xff09;也无法黑屏状态。 2.解决方法 ctrl shitf10 就能正…

CAN通讯中常见的接口异常问题及其解决方案

控制器局域网(CAN)是一种广泛应用于汽车、工业自动化等领域的串行通信网络。由德国博世公司(Bosch)和英特尔(Intel)共同开发的CAN协议因其高效的实时性、良好的抗干扰能力和可靠的多主结构,成为了现代汽车电子系统中不可或缺的一部分。随着CAN控制器价格的逐渐降低,越来…

Jmeter基础篇(19)JSR223预处理器

前言 JSR223预处理器是Apache JMeter中的一个组件&#xff0c;它允许用户使用任何支持Java Scripting API (JSR 223) 的脚本语言来执行预处理任务。这个功能非常强大&#xff0c;因为它让测试人员能够利用如Groovy、JavaScript&#xff08;Nashorn引擎&#xff09;、BeanShell…

Unity简易版成就系统

转自&#xff1a; https://learn.u3d.cn/tutorial/achievements-onecredit?chapterId63562b28edca72001f21d129# 脚本AchievementSystem using System.Collections; using UnityEngine; using UnityEngine.UI;public class AchievementSystem : MonoBehaviour {public Achi…

shell脚本案例:RAC配置多路径时获取磁盘设备WWID和磁盘大小

使用场景 在RAC配置多路径时&#xff0c;需要获取到磁盘设备的wwid。因为RAC的磁盘配置是提前规划好的&#xff0c;只知道wwid&#xff0c;不知道磁盘对应大小&#xff0c;是不知道应该如何配置多路径的mutipath.conf文件的&#xff1b;而凭借肉眼手工去对应磁盘设备的wwid和大…

数据建模圣经|数据模型资源手册卷3,数据建模最佳实践

简介 本书采用了类设计模式的方式对数据模型进行高度抽象总结&#xff0c;展现了常见的数据模型构建模型等模型的作用、层次、分类、地位、沟通方式&#xff0c;和业务规则。使用一个强大的数据模型模式的数据建模&#xff0c;评估特定与广义模型的优缺点&#xff0c;有助于你改…

Diving into the STM32 HAL-----Interrupts

硬件管理就是处理异步事件。其中大部分来自硬件外围设备。例如&#xff0c;计时器达到配置的 period 值&#xff0c;或者 UART 在数据到达时发出警告。 中断是一个异步事件&#xff0c;它会导致按优先级停止执行当前代码&#xff08;中断越重要&#xff0c;其优先级越高;这将导…

clickhouse运维篇(三):生产环境一键生成配置并快速部署ck集群

前提条件&#xff1a;先了解集群搭建流程是什么样&#xff0c;需要改哪些配置&#xff0c;有哪些环境&#xff0c;这个文章目的是简化部署。 clickhouse运维篇&#xff08;一&#xff09;&#xff1a;docker-compose 快速部署clickhouse集群 clickhouse运维篇&#xff08;二&am…

「C/C++」C++11 之<thread>多线程编程

✨博客主页何曾参静谧的博客📌文章专栏「C/C++」C/C++程序设计📚全部专栏「VS」Visual Studio「C/C++」C/C++程序设计「UG/NX」BlockUI集合「Win」Windows程序设计「DSA」数据结构与算法「UG/NX」NX二次开发「QT」QT5程序设计「File」数据文件格式「PK」Parasolid函数说明目…

【微服务】Spring AI 使用详解

目录 一、前言 二、Spring AI 概述 2.1 什么是Spring AI 2.2 Spring AI 特点 2.3 Spring AI 带来的便利 2.4 Spring AI 应用领域 2.4.1 聊天模型 2.4.2 文本到图像模型 2.4.3 音频转文本 2.4.4 嵌入大模型使用 2.4.5 矢量数据库支持 2.4.6 数据工程ETL框架 三、Sp…

【浪潮商城-注册安全分析报告-无验证方式导致安全隐患】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 1. 暴力破解密码&#xff0c;造成用户信息泄露 2. 短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉 3. 带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造…

[SAP ABAP] SMW0上传模板

通常来说&#xff0c;一个批量导入的程序必须使用指定的模板&#xff0c;我们需要将模板保存到SAP系统中&#xff0c;以便用户下载并更改。这里我们可以使用事务码SMW0解决上述的问题 1.选择二进制类型 2.输入存放的包 3.创建对象 选择需要进行上传的本地模板文件到SAP系统中 …

HTML前端页面设计静态网站

浅浅分享一下前端作业&#xff0c;大佬轻喷~ <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>一个网…

金媒婚恋相亲系统10.4择爱开源旗舰版支持微信小程和抖音小程序上架

最近大家应该注意到了&#xff0c;金媒婚恋相亲系统已经更新至最新的10.4版本了&#xff01;本人作为商业用户也已经更新至最新的旗舰版了&#xff0c;更新的内容是啥&#xff01;这个官方都有列出&#xff0c;一个方面就是更新了多端的登录逻辑和UI 和后台CRM及很多细节的优化…

Flink CDC 同步 Mysql 数据

文章目录 一、Flink CDC、Flink、CDC各有啥关系1.1 概述1.2 和 jdbc Connectors 对比 二、使用2.1 Mysql 打开 bin-log 功能2.2 在 Mysql 中建库建表准备2.3 遇到的坑2.4 测试 三、番外 一、Flink CDC、Flink、CDC各有啥关系 Flink&#xff1a;流式计算框架&#xff0c;不包含 …