【Android App】给App集成WebRTC实现视频发送和接受实战(附源码和演示 超详细)

news2024/9/21 18:33:49

需要源码请点赞关注收藏后评论区留言私信~~~

一、引入WebRTC开源库

WebRTC开源库的集成步骤如下:

(1)给App模块的build.gradle添加WebRTC的依赖库配置;

(2)App得申请录音和相机权限,还得申请互联网权限;

(3)在代码中配置STUN/TURN服务器信息,并将它作为ICE候选者;

Peer对象的功能实现

每台接入WebRTC的设备都拥有自己的Peer对象,通过Peer对象完成点对点连接的相关操作。Peer对象主要实现下列几项功能:

(1)根据连接工厂、媒体流和ICE服务器初始化点对点连接。

(2)实现接口PeerConnection.Observer,主要重写onIceCandidate和onAddStream两个方法,其中前者在收到ICE候选者时回调,后者在添加媒体流时回调。

(3)实现接口SdpObserver,主要重写onCreateSuccess方法,该方法在SDP连接创建成功时回调,此时不但要设置本地连接的会话描述,还要把媒体能力的会话描述送给信令服务器。

二、实现WebRTC的发起方

初始化发起方音视频的媒体流之时,主要完成下列三项任务:

(1)创建并初始化视频捕捉器,以便通过摄像头实时获取视频画面;

(2)创建音视频的媒体流,并给媒体流先后添加音频轨道和视频轨道;

(3)指定视频轨道中我方的渲染图层,也就是关联SurfaceViewRenderer控件;

发起方界面如下

 

代码如下 

package com.example.live;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;

import com.example.live.bean.ContactInfo;
import com.example.live.constant.ChatConst;
import com.example.live.util.SocketUtil;
import com.example.live.webrtc.Peer;
import com.example.live.webrtc.ProxyVideoSink;

import org.json.JSONException;
import org.json.JSONObject;
import org.webrtc.AudioSource;
import org.webrtc.AudioTrack;
import org.webrtc.Camera1Enumerator;
import org.webrtc.Camera2Enumerator;
import org.webrtc.CameraEnumerator;
import org.webrtc.DefaultVideoDecoderFactory;
import org.webrtc.DefaultVideoEncoderFactory;
import org.webrtc.EglBase;
import org.webrtc.IceCandidate;
import org.webrtc.MediaConstraints;
import org.webrtc.MediaStream;
import org.webrtc.PeerConnection;
import org.webrtc.PeerConnectionFactory;
import org.webrtc.RendererCommon;
import org.webrtc.SessionDescription;
import org.webrtc.SurfaceTextureHelper;
import org.webrtc.SurfaceViewRenderer;
import org.webrtc.VideoCapturer;
import org.webrtc.VideoDecoderFactory;
import org.webrtc.VideoEncoderFactory;
import org.webrtc.VideoSource;
import org.webrtc.VideoTrack;
import org.webrtc.audio.AudioDeviceModule;
import org.webrtc.audio.JavaAudioDeviceModule;

import java.util.List;

import io.socket.client.Socket;

public class VideoOfferActivity extends AppCompatActivity {
    private final static String TAG = "VideoOfferActivity";
    private Socket mSocket; // 声明一个套接字对象
    private SurfaceViewRenderer svr_local; // 本地的表面视图渲染器(我方)
    private PeerConnectionFactory mConnFactory; // 点对点连接工厂
    private EglBase mEglBase; // OpenGL ES 与本地设备之间的接口对象
    private MediaStream mMediaStream; // 媒体流
    private VideoCapturer mVideoCapturer; // 视频捕捉器
    private MediaConstraints mOfferConstraints; // 提供方的媒体条件
    private MediaConstraints mAudioConstraints; // 音频的媒体条件
    private List<PeerConnection.IceServer> mIceServers = ChatConst.getIceServerList(); // ICE服务器列表
    private Peer mPeer; // 点对点对象
    private ContactInfo mContact = new ContactInfo("提供方", "接收方");

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_video_offer);
        initRender(); // 初始化渲染图层
        initStream(); // 初始化音视频的媒体流
        initSocket(); // 初始化信令交互的套接字
        initView(); // 初始化视图界面
    }

    // 初始化视图界面
    private void initView() {
        TextView tv_title = findViewById(R.id.tv_title);
        tv_title.setText("这里是视频提供方");
        findViewById(R.id.iv_back).setOnClickListener(v -> dialOff()); // 挂断通话
    }

    // 挂断通话
    private void dialOff() {
        mSocket.off("other_hang_up"); // 取消监听对方的挂断请求
        SocketUtil.emit(mSocket, "self_hang_up", mContact); // 发出挂断通话消息
        finish(); // 关闭当前页面
    }

    @Override
    public void onBackPressed() {
        super.onBackPressed();
        dialOff(); // 挂断通话
    }

    // 初始化渲染图层
    private void initRender() {
        svr_local = findViewById(R.id.svr_local);
        mEglBase = EglBase.create(); // 创建EglBase实例
        // 以下初始化我方的渲染图层
        svr_local.init(mEglBase.getEglBaseContext(), null);
        svr_local.setMirror(true); // 是否设置镜像
        svr_local.setZOrderMediaOverlay(true); // 是否置于顶层
        // 设置缩放类型,SCALE_ASPECT_FILL表示充满视图
        svr_local.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT);
        svr_local.setEnableHardwareScaler(false); // 是否开启硬件缩放
    }

    // 初始化音视频的媒体流
    private void initStream() {
        Log.d(TAG, "initStream");
        // 初始化点对点连接工厂
        PeerConnectionFactory.initialize(
                PeerConnectionFactory.InitializationOptions.builder(getApplicationContext())
                        .createInitializationOptions());
        // 创建视频的编解码方式
        VideoEncoderFactory encoderFactory;
        VideoDecoderFactory decoderFactory;
        encoderFactory = new DefaultVideoEncoderFactory(
                mEglBase.getEglBaseContext(), true, true);
        decoderFactory = new DefaultVideoDecoderFactory(mEglBase.getEglBaseContext());
        AudioDeviceModule audioModule = JavaAudioDeviceModule.builder(this).createAudioDeviceModule();
        // 创建点对点连接工厂
        PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();
        mConnFactory = PeerConnectionFactory.builder()
                .setOptions(options)
                .setAudioDeviceModule(audioModule)
                .setVideoEncoderFactory(encoderFactory)
                .setVideoDecoderFactory(decoderFactory)
                .createPeerConnectionFactory();
        initConstraints(); // 初始化视频通话的各项条件
        // 创建音视频的媒体流
        mMediaStream = mConnFactory.createLocalMediaStream("local_stream");
        // 以下创建并添加音频轨道
        AudioSource audioSource = mConnFactory.createAudioSource(mAudioConstraints);
        AudioTrack audioTrack = mConnFactory.createAudioTrack("audio_track", audioSource);
        mMediaStream.addTrack(audioTrack);
        // 以下创建并初始化视频捕捉器
        mVideoCapturer = createVideoCapture();
        VideoSource videoSource = mConnFactory.createVideoSource(mVideoCapturer.isScreencast());
        SurfaceTextureHelper surfaceHelper = SurfaceTextureHelper.create("CaptureThread", mEglBase.getEglBaseContext());
        mVideoCapturer.initialize(surfaceHelper, this, videoSource.getCapturerObserver());
        // 设置视频画质。三个参数分别表示:视频宽度、视频高度、每秒传输帧数fps
        mVideoCapturer.startCapture(720, 1080, 15);
        // 以下创建并添加视频轨道
        VideoTrack videoTrack = mConnFactory.createVideoTrack("video_track", videoSource);
        mMediaStream.addTrack(videoTrack);
        ProxyVideoSink localSink = new ProxyVideoSink();
        localSink.setTarget(svr_local); // 指定视频轨道中我方的渲染图层
        mMediaStream.videoTracks.get(0).addSink(localSink);
    }

    // 初始化信令交互的套接字
    private void initSocket() {
        mSocket = MainApplication.getInstance().getSocket();
        mSocket.connect(); // 建立Socket连接
        // 等待接入ICE候选者,目的是打通流媒体传输网络
        mSocket.on("IceInfo", args -> {
            Log.d(TAG, "IceInfo");
            try {
                JSONObject json = (JSONObject) args[0];
                IceCandidate candidate = new IceCandidate(json.getString("id"),
                        json.getInt("label"), json.getString("candidate")
                );
                mPeer.getConnection().addIceCandidate(candidate); // 添加ICE候选者
            } catch (JSONException e) {
                e.printStackTrace();
            }
        });
        // 等待对方的会话连接,以便建立双方的通信链路
        mSocket.on("SdpInfo", args -> {
            Log.d(TAG, "SdpInfo");
            try {
                JSONObject json = (JSONObject) args[0];
                SessionDescription sd = new SessionDescription
                        (SessionDescription.Type.fromCanonicalForm(
                                json.getString("type")), json.getString("description"));
                mPeer.getConnection().setRemoteDescription(mPeer, sd); // 设置对方的会话描述
            } catch (JSONException e) {
                e.printStackTrace();
            }
        });
        mSocket.on("other_hang_up", (args) -> dialOff()); // 等待对方挂断通话
        Log.d(TAG, "self_dial_in");
        SocketUtil.emit(mSocket, "self_dial_in", mContact); // 我方发起了视频通话
        // 等待对方接受视频通话
        mSocket.on("other_dial_in", (args) -> {
            String other_name = (String) args[0];
            Log.d(TAG, mContact.from+" to "+mContact.to+", other_name="+other_name);
            // 第四个参数表示对方接受视频通话之后,如何显示对方的视频画面
            mPeer = new Peer(mSocket, mContact.from, mContact.to, (userId, remoteStream) -> Log.d(TAG, "new Peer"));
            mPeer.init(mConnFactory, mMediaStream, mIceServers); // 初始化点对点连接
            mPeer.getConnection().createOffer(mPeer, mOfferConstraints); // 创建供应
        });
    }

    // 初始化视频通话的各项条件
    private void initConstraints() {
        // 创建发起方的媒体条件
        mOfferConstraints = new MediaConstraints();
        // 是否接受音频流
        mOfferConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveAudio", "true"));
        // 是否接受视频流
        mOfferConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveVideo", "true"));
        // 创建音频流的媒体条件
        mAudioConstraints = new MediaConstraints();
        // 是否消除回声
        mAudioConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googEchoCancellation", "true"));
        // 是否自动增益
        mAudioConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googAutoGainControl", "true"));
        // 是否过滤高音
        mAudioConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googHighpassFilter", "true"));
        // 是否抑制噪音
        mAudioConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googNoiseSuppression", "true"));
    }

    // 根据相机类型创建对应的视频捕捉器
    private VideoCapturer createCameraCapture(CameraEnumerator enumerator) {
        final String[] deviceNames = enumerator.getDeviceNames();
        // 先使用前置摄像头
        for (String deviceName : deviceNames) {
            if (enumerator.isFrontFacing(deviceName)) {
                VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null);
                if (videoCapturer != null) {
                    return videoCapturer;
                }
            }
        }
        // 没有前置摄像头再找后置摄像头
        for (String deviceName : deviceNames) {
            if (!enumerator.isFrontFacing(deviceName)) {
                VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null);
                if (videoCapturer != null) {
                    return videoCapturer;
                }
            }
        }
        return null;
    }

    // 创建视频捕捉器
    private VideoCapturer createVideoCapture() {
        VideoCapturer videoCapturer;
        if (Camera2Enumerator.isSupported(this)) { // 优先使用二代相机
            videoCapturer = createCameraCapture(new Camera2Enumerator(this));
        } else { // 如果不支持二代相机,就使用传统相机
            videoCapturer = createCameraCapture(new Camera1Enumerator(true));
        }
        return videoCapturer;
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mSocket.off("other_dial_in"); // 取消监听对方的接入请求
        mSocket.off("other_hang_up"); // 取消监听对方的挂断请求
        mSocket.off("IceInfo"); // 取消监听流媒体传输
        mSocket.off("SdpInfo"); // 取消监听会话连接
        svr_local.release(); // 释放本地的渲染器资源(我方)
        try { // 停止视频捕捉,也就是关闭摄像头
            mVideoCapturer.stopCapture();
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (mSocket.connected()) { // 已经连上Socket服务器
            mSocket.disconnect(); // 断开Socket连接
        }
    }

}

三、实现WebRTC的接收方 

除了向信令服务器发送同意通话指令以外,接受方与发起方的处理逻辑还有下列两处区别:

(1)收到对方的会话连接后,要调用createAnswer方法创建应答,然后发起方才能传来音视频数据;

(2)创建Peer对象之时,第四个输入参数要收下对方远程的媒体流对象,并将其设置到视频轨道中对方的渲染图层;

接受后效果如下

 代码如下

package com.example.live;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;

import com.example.live.bean.ContactInfo;
import com.example.live.constant.ChatConst;
import com.example.live.util.SocketUtil;
import com.example.live.webrtc.Peer;
import com.example.live.webrtc.ProxyVideoSink;

import org.json.JSONException;
import org.json.JSONObject;
import org.webrtc.DefaultVideoDecoderFactory;
import org.webrtc.DefaultVideoEncoderFactory;
import org.webrtc.EglBase;
import org.webrtc.IceCandidate;
import org.webrtc.MediaConstraints;
import org.webrtc.MediaStream;
import org.webrtc.PeerConnection;
import org.webrtc.PeerConnectionFactory;
import org.webrtc.RendererCommon;
import org.webrtc.SessionDescription;
import org.webrtc.SurfaceViewRenderer;
import org.webrtc.VideoDecoderFactory;
import org.webrtc.VideoEncoderFactory;
import org.webrtc.VideoTrack;
import org.webrtc.audio.AudioDeviceModule;
import org.webrtc.audio.JavaAudioDeviceModule;

import java.util.List;

import io.socket.client.Socket;

public class VideoRecipientActivity extends AppCompatActivity {
    private final static String TAG = "VideoRecipientActivity";
    private Socket mSocket; // 声明一个套接字对象
    private SurfaceViewRenderer svr_remote; // 远程的表面视图渲染器(对方)
    private PeerConnectionFactory mConnFactory; // 点对点连接工厂
    private EglBase mEglBase; // OpenGL ES 与本地设备之间的接口对象
    private MediaStream mMediaStream; // 媒体流
    private List<PeerConnection.IceServer> mIceServers = ChatConst.getIceServerList(); // ICE服务器列表
    private Peer mPeer; // 点对点对象
    private ContactInfo mContact = new ContactInfo("接收方", "提供方");

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_video_recipient);
        initRender(); // 初始化渲染图层
        initStream(); // 初始化音视频的媒体流
        initSocket(); // 初始化信令交互的套接字
        initView(); // 初始化视图界面
    }

    // 初始化视图界面
    private void initView() {
        TextView tv_title = findViewById(R.id.tv_title);
        tv_title.setText("这里是视频接收方");
        findViewById(R.id.iv_back).setOnClickListener(v -> dialOff()); // 挂断通话
    }

    // 挂断通话
    private void dialOff() {
        mSocket.off("other_hang_up"); // 取消监听对方的挂断请求
        SocketUtil.emit(mSocket, "self_hang_up", mContact); // 发出挂断通话消息
        finish(); // 关闭当前页面
    }

    @Override
    public void onBackPressed() {
        super.onBackPressed();
        dialOff(); // 挂断通话
    }

    // 初始化渲染图层
    private void initRender() {
        svr_remote = findViewById(R.id.svr_remote);
        mEglBase = EglBase.create(); // 创建EglBase实例
        // 以下初始化对方的渲染图层
        svr_remote.init(mEglBase.getEglBaseContext(), null);
        svr_remote.setMirror(false); // 是否设置镜像
        svr_remote.setZOrderMediaOverlay(false); // 是否置于顶层
        // 设置缩放类型,SCALE_ASPECT_FILL表示充满视图
        svr_remote.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL);
        svr_remote.setEnableHardwareScaler(false); // 是否开启硬件缩放
    }

    // 初始化音视频的媒体流
    private void initStream() {
        Log.d(TAG, "initStream");
        // 初始化点对点连接工厂
        PeerConnectionFactory.initialize(
                PeerConnectionFactory.InitializationOptions.builder(getApplicationContext())
                        .createInitializationOptions());
        // 创建视频的编解码方式
        VideoEncoderFactory encoderFactory;
        VideoDecoderFactory decoderFactory;
        encoderFactory = new DefaultVideoEncoderFactory(
                mEglBase.getEglBaseContext(), true, true);
        decoderFactory = new DefaultVideoDecoderFactory(mEglBase.getEglBaseContext());
        AudioDeviceModule audioModule = JavaAudioDeviceModule.builder(this).createAudioDeviceModule();
        // 创建点对点连接工厂
        PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();
        mConnFactory = PeerConnectionFactory.builder()
                .setOptions(options)
                .setAudioDeviceModule(audioModule)
                .setVideoEncoderFactory(encoderFactory)
                .setVideoDecoderFactory(decoderFactory)
                .createPeerConnectionFactory();
        // 创建音视频的媒体流
        mMediaStream = mConnFactory.createLocalMediaStream("local_stream");
    }

    // 初始化信令交互的套接字
    private void initSocket() {
        mSocket = MainApplication.getInstance().getSocket();
        mSocket.connect(); // 建立Socket连接
        // 等待接入ICE候选者,目的是打通流媒体传输网络
        mSocket.on("IceInfo", args -> {
            Log.d(TAG, "IceInfo");
            try {
                JSONObject json = (JSONObject) args[0];
                IceCandidate candidate = new IceCandidate(json.getString("id"),
                        json.getInt("label"), json.getString("candidate")
                );
                mPeer.getConnection().addIceCandidate(candidate); // 添加ICE候选者
            } catch (JSONException e) {
                e.printStackTrace();
            }
        });
        // 等待对方的会话连接,以便建立双方的通信链路
        mSocket.on("SdpInfo", args -> {
            Log.d(TAG, "SdpInfo");
            try {
                JSONObject json = (JSONObject) args[0];
                SessionDescription sd = new SessionDescription
                        (SessionDescription.Type.fromCanonicalForm(
                                json.getString("type")), json.getString("description"));
                mPeer.getConnection().setRemoteDescription(mPeer, sd); // 设置对方的会话描述
                // 接受方要创建应答
                mPeer.getConnection().createAnswer(mPeer, new MediaConstraints());
            } catch (JSONException e) {
                e.printStackTrace();
            }
        });
        // 第四个参数表示对方接受视频通话之后,如何显示对方的视频画面
        mPeer = new Peer(mSocket, mContact.from, mContact.to, (userId, remoteStream) -> {
            String desc = String.format("from=%s, to=%s", mContact.from, mContact.to);
            Log.d(TAG, "addRemoteStream "+desc);
            ProxyVideoSink remoteSink = new ProxyVideoSink();
            remoteSink.setTarget(svr_remote); // 设置视频轨道中对方的渲染图层
            VideoTrack videoTrack = remoteStream.videoTracks.get(0);
            videoTrack.addSink(remoteSink);
        });
        mPeer.init(mConnFactory, mMediaStream, mIceServers); // 初始化点对点连接
        mSocket.on("other_hang_up", (args) -> dialOff()); // 等待对方挂断通话
        Log.d(TAG, "self_dial_in");
        SocketUtil.emit(mSocket, "self_dial_in", mContact); // 我方同意了视频通话
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mSocket.off("other_dial_in"); // 取消监听对方的接入请求
        mSocket.off("other_hang_up"); // 取消监听对方的挂断请求
        mSocket.off("IceInfo"); // 取消监听流媒体传输
        mSocket.off("SdpInfo"); // 取消监听会话连接
        svr_remote.release(); // 释放远程的渲染器资源(对方)
        if (mSocket.connected()) { // 已经连上Socket服务器
            mSocket.disconnect(); // 断开Socket连接
        }
    }

}

创作不易 觉得有帮助请点赞关注收藏~~~

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[附源码]计算机毕业设计springboot自行车租赁管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

K-means聚类算法及Python代码实现

K-means聚类算法&#xff08;事先数据并没有类别之分&#xff01;所有的数据都是一样的&#xff09; 1、概述 K-means算法是集简单和经典于一身的基于距离的聚类算法 采用距离作为相似性的评价指标&#xff0c;即认为两个对象的距离越近&#xff0c;其相似度就越大。 该算法…

享元设计模式

一、享元模式 1、定义 享元模式&#xff08;Flyweight Pattern&#xff09;又称作轻量级模式&#xff0c;是指提供减少对象数量从而改善应用所需的对象结构的方式。其宗旨是共享细粒度的对象&#xff0c;将多个对同一对象的访问集中起来&#xff0c;不必为每个访问者都创建一个…

C++智能指针shared_ptr用法

目录shared_ptr功能介绍shared_ptr提供的接口shared_ptr初始化shared_ptr管理指针的构造和析构shared_ptr获取原始指针shared_ptr的线程安全shared_ptr应用之enable_shared_from_this写在前面的总结&#xff1a;一个shared_ptr对象管理一个指针&#xff08;new T&#xff0c;在…

TCP/IP五层协议栈(3)

1.网络层 1.1.IP协议 IP协议格式:报头数据 4位版本 :IP协议的版本号.当前只有两个取值,4和6(0100 0110).( 这里讨论IPv4 )4位首部长度 :IP报头和TCP类似,都是可变的,带有选项.8位TOS :只有4位有效,那四位TOS分别表示( 最小延时,最大吞吐量,最高可靠性,最小成本 )(同一时刻只能…

使用 Qt for Android 获取并利用手机传感器数据(下篇)使用C++实现功能

在上一篇&#xff0c;我们搭建了开发环境。本篇&#xff0c;使用C代码真正实现功能。我们使用UDP协议从手机上指定发送的目的地、端口。效果如下图&#xff0c;完整工程参考https://gitcode.net/coloreaglestdio/qtcpp_demo/-/tree/master/android/sensors2pc&#xff1a; 移动…

全志T3 ARM+Ethercat+Codesys工业控制器设计方案

目前codesys EtherCAT驱动 做运动控制很有优势。现在总线式运动控制基本都是这种配置。 Codesys 号称PLC界的安卓&#xff0c;国内造PLC的 基本都用Codesys内核了。 如&#xff1a;汇川 &#xff0c;合信&#xff0c; 和利时 &#xff0c;英威腾&#xff0c; 台达。 包…

原子范数初探:以到达角估计为例

到达方向&#xff08;Direction-of-arrival, DOA&#xff09;估计是指从形成传感器阵列的多个接收天线的输出中检索若干电磁波/源的方向信息的过程。DOA估计是阵列信号处理中的一个主要问题&#xff0c;在雷达、声纳、无线通信中有着广泛的应用。 基本数学模型 考虑KKK个窄带…

Java项目:ssm流浪猫狗救助管理系统

作者主页&#xff1a;源码空间站2022 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文末获取源码 项目介绍 流浪猫狗救助管理系统。该项目分为前后台&#xff1b; 前台主要功能包括&#xff1a;会员的注册登陆,流浪猫狗知识&#xff0c;领养中心&#…

[附源码]计算机毕业设计JAVA学生实习管理系统

[附源码]计算机毕业设计JAVA学生实习管理系统 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybati…

80W美团架构师整理分享出了Spring5企业级开发实战文档

前言 都说程序员工资高、待遇好&#xff0c; 2022 金九银十到了&#xff0c;你的小目标是 30K、40K&#xff0c;还是 16薪的 20K&#xff1f;作为一名 Java 开发工程师&#xff0c;当能力可以满足公司业务需求时&#xff0c;拿到超预期的 Offer 并不算难。然而&#xff0c;提升…

U盘插入提示格式化才能使用,但里面有数据无法复制出来怎么解决?

U盘作为移动储存硬盘&#xff0c;避免不了出现各种问题&#xff0c;特别是莫名提示格式化&#xff0c;无法打开&#xff0c;要使用的话只能先将其格式化。 只要电脑还能正常识别出U盘&#xff0c;那都是有概率恢复出来数据的。先不要点“格式化”&#xff01; 如果一旦出现点…

4.JVM垃圾收集机制

1. 垃圾收集算法 1.1 分代收集理论 当前虚拟机的垃圾收集都采用分代收集算法&#xff0c;这种算法&#xff0c;没有什么新的思想&#xff0c;只是根据对象存活周期的不同将内存分成几块。一般将JAVA堆分为新生代、老年代&#xff0c;这样我们就可以根据各个年代的特点选择合适…

【注意力机制】Self-attention注意力机制理论知识

注意力机制目录输入输出类别&#xff08;N指向量个数&#xff09;&#xff1a;Self-attention引入self-attention架构self-attention怎么产生bbb例子&#xff1a;产生b1b^{1}b1例子&#xff1a;产生b2b^{2}b2self-attention 总结&#xff1a;Multi-head Self-attentionPosition…

使用 Learner Lab - 使用 API Gateway 与 Lambda 上传图片到 S3

使用 Learner Lab - 使用 API Gateway 与 Lambda 上传图片到 S3 AWS Academy Learner Lab 是提供一个帐号让学生可以自行使用 AWS 的服务&#xff0c;让学生可以在 100 USD的金额下&#xff0c;自行练习所要使用的 AWS 服务&#xff0c;如何进入 Learner Lab 请参考 使用 Lear…

【车载开发系列】UDS诊断---读取数据($0x22)

【车载开发系列】UDS诊断—读取数据&#xff08;$0x22&#xff09; UDS诊断---读取数据&#xff08;$0x22&#xff09;【车载开发系列】UDS诊断---读取数据&#xff08;$0x22&#xff09;一.概念定义二.报文格式1&#xff09;请求2&#xff09;肯定响应3&#xff09;否定响应三…

Vue中$nextTick实现源码解析

这篇文章主要为大家介绍了Vue中$nextTick实现源码解析&#xff0c;有需要的朋友可以借鉴参考下&#xff01; 先看一个简单的问题 {{ text }} 此时打印的结果是什么呢&#xff1f;是 old。如果想让它打印 new&#xff0c;使用 nextTick 稍加改造就可以 this.$nextTick(() >…

【计组笔记】06_指令系统

5.1 指令系统概述及指令格式 1. 指令的基本概念 2. 指令的分类 根据计算机层次结构分类 根据指令中地址码字段的个数分类 根据指令中操作数的物理位置分类 根据指令的功能分类 3. 指令格式 4. 指令的再认识 5. 指令格式举例 5.2 寻址方式及指令寻址 1. 寻址方式的概念 2.…

[论文阅读] 颜色迁移-Correlated Color Space

[论文阅读] 颜色迁移-Correlated Color Space 文章: Color transfer in correlated color space, [paper], [matlab code], [opencv code] 1-算法原理 本文算法比较简单, 其原理是把原始图像本身的空间分布进行归一化, 然后通过旋转平移缩放等变换, 变换到目标图像的空间分布…

WMS手动配货和自动配货的区别

手动配货 不知道配货流程的朋友可以看一下前面的文章链接: 深入浅出WMS之出库流程里面有对出库的解释说明&#xff0c;其中也有对配货的解释。前端页面也可以在前面的那篇文章中看到&#xff0c;这里我们来说一下后端部分。 查 手动配货是选中出库单的某条数据&#xff0c;然…