蓝牙HID——将android设备变成蓝牙鼠标/触控板(BluetoothHidDevice)

news2024/10/2 8:28:12

前言

本篇为蓝牙HID系列篇章之一,本篇以红米K30(MIUI13即Android 12)手机作为蓝牙HID设备,可以与电脑、手机、平板等其他蓝牙主机进行配对从而实现鼠标触控板的功能。
蓝牙HID系列篇章:
蓝牙HID——将android设备变成蓝牙键盘(BluetoothHidDevice)
蓝牙HID——android利用手机来解锁电脑/平板/iPhone
蓝牙HID——Android手机注册HID时出现 Could not bind to Bluetooth (HID Device) Service with Intent * 的问题分析

HID开发

Android 9开放了 BluetoothHidDevice 等HID相关的API,通过与系统蓝牙HID服务通信注册成蓝牙HID设备。首先通过 BluetoothProfile.HID_DEVICE 的描述类型得到 BluetoothHidDevice 抽象实例:

    private BluetoothAdapter mBtAdapter;
    private BluetoothHidDevice mHidDevice;
    
    private void callBluetooth() {
        Log.d(TAG, "callBluetooth");
        mBtAdapter = BluetoothAdapter.getDefaultAdapter();
        mBtAdapter.getProfileProxy(mContext, new BluetoothProfile.ServiceListener() {
            @Override
            public void onServiceConnected(int i, BluetoothProfile bluetoothProfile) {
                Log.d(TAG, "onServiceConnected:" + i);
                if (i == BluetoothProfile.HID_DEVICE) {
                    if (!(bluetoothProfile instanceof BluetoothHidDevice)) {
                        Log.e(TAG, "Proxy received but it's not BluetoothHidDevice");
                        return;
                    }
                    mHidDevice = (BluetoothHidDevice) bluetoothProfile;
                    registerBluetoothHid();
                }
            }

            @Override
            public void onServiceDisconnected(int i) {
                Log.d(TAG, "onServiceDisconnected:" + i);
            }
        }, BluetoothProfile.HID_DEVICE);
    }

再调用 BluetoothHidDevice.registerApp() 将 Android 设备注册成蓝牙HID设备:

    private BluetoothDevice mHostDevice;
    
    private final BluetoothHidDeviceAppQosSettings qosSettings
            = new BluetoothHidDeviceAppQosSettings(BluetoothHidDeviceAppQosSettings.SERVICE_BEST_EFFORT,
            800, 9, 0, 11250, BluetoothHidDeviceAppQosSettings.MAX
    );

    private final BluetoothHidDeviceAppSdpSettings mouseSdpSettings = new BluetoothHidDeviceAppSdpSettings(
            HidConfig.MOUSE_NAME, HidConfig.DESCRIPTION, HidConfig.PROVIDER,
            BluetoothHidDevice.SUBCLASS1_MOUSE, HidConfig.MOUSE_COMBO);

    private void registerBluetoothHid() {
        if (mHidDevice == null) {
            Log.e(TAG, "hid device is null");
            return;
        }

        mHidDevice.registerApp(mouseSdpSettings, null, qosSettings, Executors.newCachedThreadPool(), new BluetoothHidDevice.Callback() {
            @Override
            public void onAppStatusChanged(BluetoothDevice pluggedDevice, boolean registered) {
                Log.d(TAG, "onAppStatusChanged:" + (pluggedDevice != null ? pluggedDevice.getName() : "null") + " registered:" + registered);
                if (registered) {
                    Log.d(TAG, "paired devices: " + mHidDevice.getConnectionState(pluggedDevice));
                    if (pluggedDevice != null && mHidDevice.getConnectionState(pluggedDevice) != BluetoothProfile.STATE_CONNECTED) {
                        boolean result = mHidDevice.connect(pluggedDevice);
                        Log.d(TAG, "hidDevice connect:" + result);
                    }
                }
                if (mBluetoothHidStateListener != null) {
                    mBluetoothHidStateListener.onRegisterStateChanged(registered, pluggedDevice != null);
                }
            }

            @Override
            public void onConnectionStateChanged(BluetoothDevice device, int state) {
                Log.d(TAG, "onConnectionStateChanged:" + device + "  state:" + state);
                if (state == BluetoothProfile.STATE_CONNECTED) {
                    mHostDevice = device;
                }
                if (state == BluetoothProfile.STATE_DISCONNECTED) {
                    mHostDevice = null;
                }
                if (mBluetoothHidStateListener != null) {
                    mBluetoothHidStateListener.onConnectionStateChanged(state);
                }
            }
        });
    }

蓝牙鼠标Mouse的描述信息如下,主要 为 MOUSE_COMBO 的描述协议,正确的描述协议才能成功与其他设备通信。

public class HidConfig {
    public final static String MOUSE_NAME = "VV Mouse";

    public final static String DESCRIPTION = "VV for you";

    public final static String PROVIDER = "VV";

    public static final byte[] MOUSE_COMBO = {
            (byte) 0x05, (byte) 0x01,              // USAGE_PAGE (Generic Desktop)
            (byte) 0x09, (byte) 0x02,              // USAGE (Mouse)
            (byte) 0xa1, (byte) 0x01,              // COLLECTION (Application)
            (byte) 0x85, (byte) 0x04,              // REPORT_ID (4)
            (byte) 0x09, (byte) 0x01,              //  USAGE (Pointer)
            (byte) 0xa1, (byte) 0x00,              //  COLLECTION (Physical)
            (byte) 0x05, (byte) 0x09,              //   USAGE_PAGE (Button)
            (byte) 0x19, (byte) 0x01,              //   USAGE_MINIMUM (Button 1)
            (byte) 0x29, (byte) 0x02,              //   USAGE_MAXIMUM (Button 2)
            (byte) 0x15, (byte) 0x00,              //   LOGICAL_MINIMUM (0)
            (byte) 0x25, (byte) 0x01,              //   LOGICAL_MAXIMUM (1)
            (byte) 0x95, (byte) 0x03,              //   REPORT_COUNT (3)
            (byte) 0x75, (byte) 0x01,              //   REPORT_SIZE (1)
            (byte) 0x81, (byte) 0x02,              //   INPUT (Data,Var,Abs)
            (byte) 0x95, (byte) 0x01,              //   REPORT_COUNT (1)
            (byte) 0x75, (byte) 0x05,              //   REPORT_SIZE (5)
            (byte) 0x81, (byte) 0x03,              //   INPUT (Cnst,Var,Abs)
            (byte) 0x05, (byte) 0x01,              //   USAGE_PAGE (Generic Desktop)
            (byte) 0x09, (byte) 0x30,              //   USAGE (X)
            (byte) 0x09, (byte) 0x31,              //   USAGE (Y)
            (byte) 0x09, (byte) 0x38,              //   USAGE (Wheel)
            (byte) 0x15, (byte) 0x81,              //   LOGICAL_MINIMUM (-127)
            (byte) 0x25, (byte) 0x7F,              //   LOGICAL_MAXIMUM (127)
            (byte) 0x75, (byte) 0x08,              //   REPORT_SIZE (8)
            (byte) 0x95, (byte) 0x03,              //   REPORT_COUNT (3)
            (byte) 0x81, (byte) 0x06,              //   INPUT (Data,Var,Rel)
            //水平滚轮
            (byte) 0x05, (byte) 0x0c,              //   USAGE_PAGE (Consumer Devices)
            (byte) 0x0a, (byte) 0x38, (byte) 0x02, //   USAGE (AC Pan)
            (byte) 0x15, (byte) 0x81,              //   LOGICAL_MINIMUM (-127)
            (byte) 0x25, (byte) 0x7f,              //   LOGICAL_MAXIMUM (127)
            (byte) 0x75, (byte) 0x08,              //   REPORT_SIZE (8)
            (byte) 0x95, (byte) 0x01,              //   REPORT_COUNT (1)
            (byte) 0x81, (byte) 0x06,              //   INPUT (Data,Var,Rel)

            (byte) 0xc0,                           //  END_COLLECTION
            (byte) 0xc0,                           // END_COLLECTION
    };

在注册完成后启动设备发现,让HID能被其他设备发现,下面ActivityResultLauncher.launch(new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE) 相当于调用 BluetoothAdapter.setScanMode() 的隐藏API

    private ActivityResultLauncher<Intent> mActivityResultLauncher;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_mouse);
      
        mActivityResultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
            Log.d(TAG, "onActivityResult:" + result.toString());
        });
    }

   @Override
    public void onRegisterStateChanged(boolean registered, boolean hasDevice) {
        if (registered) {
            if (!hasDevice) {
                // startActivityForResult(new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE), 1);
                mActivityResultLauncher.launch(new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE));
            }
        }
    }

ActivityResultLauncher 的相关方法也可用 startActivityForResult(new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE), REQUEST_CODE) 来替代,但 startActivityForResult() 是废弃的方法,不建议使用。
接下来与蓝牙主机(电脑、手机等)进行蓝牙配对,已配对过需要取消配对。配对完成即可实现对蓝牙主机的鼠标触摸控制。

手势识别

手势识别通过对触摸事件以及手势监听进行各种手势的判断(移动鼠标、左键单击、左键双击、右键双指单击、双指垂直/水平滚动)。

CustomMotionListener customMotionListener = new CustomMotionListener(this, mBluetoothHidManager);
findViewById(R.id.layout_touch).setOnTouchListener(customMotionListener);

手势逻辑处理代码如下:

package com.example.bluetoothproject;

import android.content.Context;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;

import org.apache.commons.lang3.concurrent.BasicThreadFactory;

import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class CustomMotionListener implements View.OnTouchListener, GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener {

    private final GestureDetector mGestureDetector;
    private BluetoothHidManager mBluetoothHidManager;
    private int mPointCount;

    private long mDoubleFingerTime;

    private final ScheduledExecutorService mExecutorService;

    private float mPreX;
    private float mPreY;
    private boolean mLongPress;

    public CustomMotionListener(Context context, BluetoothHidManager bluetoothHidManager) {
        mBluetoothHidManager = bluetoothHidManager;
        mGestureDetector = new GestureDetector(context, this);
        mGestureDetector.setOnDoubleTapListener(this);
        mExecutorService = new ScheduledThreadPoolExecutor(1,
                new BasicThreadFactory.Builder().namingPattern("mouse-schedule-pool-%d").daemon(true).build());
    }

    @Override
    public boolean onSingleTapConfirmed(MotionEvent e) {
        return false;
    }

    @Override
    public boolean onDoubleTap(MotionEvent e) {
        return false;
    }

    @Override
    public boolean onDoubleTapEvent(MotionEvent e) {
        //左键单指双击(选中文本的效果)
        if (e.getAction() == MotionEvent.ACTION_DOWN) {
            mBluetoothHidManager.sendLeftClick(true);
        } else if (e.getAction() == MotionEvent.ACTION_UP) {
            mBluetoothHidManager.sendLeftClick(false);
        }
        return true;
    }

    @Override
    public boolean onDown(MotionEvent e) {
        return false;
    }

    @Override
    public void onShowPress(MotionEvent e) {
    }

    @Override
    public boolean onSingleTapUp(MotionEvent e) {
        //左键单击
        mBluetoothHidManager.sendLeftClick(true);
        mBluetoothHidManager.sendLeftClick(false);
        return true;
    }

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        //双指滚动,x为水平滚动,y为垂直滚动,消抖处理
        if (mPointCount == 2) {
            if (Math.abs(distanceX) > Math.abs(distanceY))  {
                distanceX = distanceX > 0 ? 1 : distanceX < 0 ? -1 : 0;
                distanceY = 0;
            } else {
                distanceY = distanceY > 0 ? -1 : distanceY < 0 ? 1 : 0;
                distanceX = 0;
            }

            mBluetoothHidManager.sendWheel((byte) (distanceX), (byte) (distanceY));
        }
        return false;
    }

    @Override
    public void onLongPress(MotionEvent e) {
        //单键长按效果
        mBluetoothHidManager.sendLeftClick(true);
        mLongPress = true;
    }

    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        return false;
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        float x = event.getX();
        float y = event.getY();
        if (mGestureDetector.onTouchEvent(event)) {
            return true;
        }
        mPointCount = event.getPointerCount();

        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_POINTER_DOWN:
                //双指单击代表右键记录时间
                if (event.getPointerCount() == 2) {
                    mDoubleFingerTime = System.currentTimeMillis();
                }
                break;
            case MotionEvent.ACTION_MOVE:
                //单指代表移动鼠标
                if (event.getPointerCount() == 1) {
                    float dx = x - mPreX;
                    if (dx > 127) dx = 127;
                    if (dx < -128) dx = -128;

                    float dy = y - mPreY;
                    if (dy > 127) dy = 127;
                    if (dy < -128) dy = -128;
                  
                    mBluetoothHidManager.senMouse((byte) dx, (byte) dy);
                } else {
                    mBluetoothHidManager.senMouse((byte) 0, (byte) 0);
                }
                break;
            case MotionEvent.ACTION_UP:
                if (mLongPress) {
                    mBluetoothHidManager.sendLeftClick(false);
                    mLongPress = false;
                }
                break;
            case MotionEvent.ACTION_POINTER_UP:
                //双指按下代表右键
                if (event.getPointerCount() == 2 && System.currentTimeMillis() - mDoubleFingerTime < ViewConfiguration.getDoubleTapTimeout()) {
                    mBluetoothHidManager.sendRightClick(true);
                    //延时释放避免无效
                    mExecutorService.scheduleWithFixedDelay(new Runnable() {
                        @Override
                        public void run() {
                            mBluetoothHidManager.sendRightClick(false);
                        }
                    }, 0, 50, TimeUnit.MILLISECONDS);                }
                break;
            default:
                break;
        }
        mPreX = x;
        mPreY = y;
        return true;
    }
}

向蓝牙主机发送的鼠标触摸按键的报告如下:

    private boolean mLeftClick;
    private boolean mRightClick;

    public void sendLeftClick(boolean click) {
        mLeftClick = click;
        senMouse((byte) 0x00, (byte) 0x00);
    }

    public void sendRightClick(boolean click) {
        mRightClick = click;
        senMouse((byte) 0x00, (byte) 0x00);
    }

    public void senMouse(byte dx, byte dy) {
        if (mHidDevice == null) {
            Log.e(TAG, "senMouse failed,  hid device is null!");
            return;
        }
        if (mHostDevice == null) {
            Log.e(TAG, "senMouse failed,  hid device is not connected!");
            return;
        }

        byte[] bytes = new byte[5];
        //bytes[0]字节:bit0: 1表示左键按下 0表示左键抬起 | bit1: 1表示右键按下 0表示右键抬起 | bit2: 1表示中键按下 | bit7~3:补充的常数,无意义,这里为0即可
        bytes[0] = (byte) (bytes[0] | (mLeftClick ? 1 : 0));
        bytes[0] = (byte) (bytes[0] | (mRightClick ? 1 : 0) << 1);
        bytes[1] = dx;
        bytes[2] = dy;
        Log.d(TAG, "senMouse   Left:" + mLeftClick+ ",Right:" + mRightClick + ",bytes: " + BluetoothUtils.bytesToHexString(bytes));
        mHidDevice.sendReport(mHostDevice, 4, bytes);
    }

    public void sendWheel(byte hWheel, byte vWheel) {
        if (mHidDevice == null) {
            Log.e(TAG, "sendWheel failed,  hid device is null!");
            return;
        }
        if (mHostDevice == null) {
            Log.e(TAG, "sendWheel failed,  hid device is not connected!");
            return;
        }

        byte[] bytes = new byte[5];
        bytes[3] = vWheel; //垂直滚轮
        bytes[4] = hWheel; //水平滚轮
        Log.d(TAG, "sendWheel vWheel:" + vWheel + ",hWheel:" + hWheel);
        mHidDevice.sendReport(mHostDevice, 4, bytes);
    }

效果

实现以上步骤即可将手机变成蓝牙鼠标/触控板,下面是实现的效果:
在这里插入图片描述

鼠标移动:
请添加图片描述

左键单击:
请添加图片描述

左键单指快速双击:
请添加图片描述

右键双指单击:
请添加图片描述

双指垂直上下滚动:
请添加图片描述

双指水平左右滚动:
请添加图片描述
完整视频效果展示:

蓝牙HID——将android设备变成蓝牙鼠标/触控板

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

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

相关文章

babel-plugin-import 实现按需引入

官方文档&#xff1a;https://github.com/umijs/babel-plugin-import#usage 为什么需要这个插件&#xff1f; 在 antd 和 element 两个组件库中&#xff0c;index.js 分别是这样的&#xff1a; // antd export { default as Button } from ./button; export { default as Tab…

智能云门禁解决方案来了

传统门禁存在的问题 01、安全性差&#xff1a;传统门禁卡易被复制和盗用无法精准识别进出人员身份造成较大安全隐患。 02、通行不便&#xff1a;要求人员近距离操作&#xff0c;当使 用者双手被占用时通行不便 门禁卡丢失或密码遗忘造成 无法开门。 03、管理困难&#xff1a…

Multilevel Cooperative Coevolution for Large Scale Optimization

0、论文背景 本文在CCEA_G的基础上&#xff0c;提出了MLCC框架。在MLCC中&#xff0c;基于不同组大小的随机分组策略构造了一组问题分解器。演化过程分为若干个循环&#xff0c;在每个周期开始时&#xff0c;MLCC使用自适应机制根据其历史性能选择分解器。由于不同的组大小捕获…

数据结构-图的存储结构

目录 图的存储结构邻接矩阵邻接表图的邻接矩阵和邻接表两种存储结构各有什么优缺点?图的存储结构 邻接矩阵 邻接矩阵的主要特点:

spring7:总结56

1.handler的形参解析&#xff08;即如何把请求参数转化为形参&#xff09; 注解参数解析原理 model and map解析原理 自定义对象处理原理 2.数据响应原理&#xff08;即如何把返回值传给前端&#xff09; 整体返回原理 详解其中的内容协商流程&#xff08;基于请求头&#x…

数据库分区的通俗解释

关于数据库分区&#xff0c;分表&#xff0c;分库&#xff0c;我通俗易懂的来举几个栗子&#xff0c;看过还不懂&#xff0c;你打我。。。 村里一家四口人(老爹叫A)有两儿子(分别是A1&#xff0c;A2)&#xff0c;长大了要自己种地了&#xff0c;就嚷嚷着要分家&#xff0c;把村…

虚拟机(Vmware)磁盘扩容(xfs格式)

先将虚拟机关机&#xff0c;按上图调整虚拟磁盘大小。 1.开启并进入虚拟机&#xff0c;打开终端&#xff0c;输入命令 df -Th 查看格式&#xff0c;图示中 /dev/mapper/centos-root 类型为xfs。 [mangolocalhost ~]$ df -Th Filesystem Type Size Used Ava…

luffy-(9)

内容概览 redis图形化客户端redis字符串操作redis hash操作redis列表操作redis管道redis其他操作django中集成rediscelery介绍 redis图形化客户端 安装图形化客户端redis-desktop-manager 新版本收费&#xff0c;可以使用老版本 QT平台&#xff1a;可以写图形化界面 python&…

腾讯云~Kafka 监控 Kafka Eagle 图形化版本

文章目录1. 安装包下载2. 开启kafka JMX3. 安装JDK&#xff0c;配置JAVA_HOME4. 上传安装包、解压5. 配置Kafka-eagle环境变量6. 配置Kafka_eagle7. 配置ke.sh8. 启动Kafka_eagle9. 防火墙10. 访问Kafka eagle1. 安装包下载 官网地址&#xff1a;EFAK 本文使用3.0.1版本 2. …

【保姆级·创建对象】如何利用resolveBeforeInstantiation()在预处理阶段返回一个Bean的实例对象

前情回顾 之前有篇文章我们有详细介绍了prepareMethodOverrides()方法并详细例举了一个lookup-method标签的例子 【保姆级】lookup-method标签实践与分析_AQin1012的博客-CSDN博客 本文我们来盘盘prepareMethodOverrides()方法后面的resolveBeaforeInstantiation()的函数&…

Android中SQLite数据库增删改查/使用ListView显示数据库内容(有完整源码)

android作业笔记 文章目录效果展示一、前言源码获取实验功能描述注意事项实现步骤二、代码展示activity_main.xml布局文件MyOpenHelper.javaMainActivity.javaList_item.xml三、&#xff08;补充&#xff09;ListView实现数据列表显示效果展示 编写SQLite数据库相关操作的代码…

【附源码】Python计算机毕业设计数据时代下的疫情管理系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

达梦数据库,数据库重置主键id从1开始

一、前言 今天中国国寿XX项目XC环境达梦遇到id主键自增顺序不对的问题&#xff0c;那么如何修改一个表的自增主键顺序呢&#xff1f;下边通过具体测试案例进行深入分析&#xff0c;通过delete/update/truncate/alter观察数据的变换总结出结论&#xff0c;欢迎各位喜欢达梦数据…

值得推荐的小型 C 语言开源项目:Triggerhappy

这几天在知乎上看到了一个好问题&#xff1a; 有哪些值得推荐的小型 C 语言开源项目&#xff1f; 题主很可能是想要一个这样的开源项目&#xff1a;功能小巧、代码质量高&#xff0c;可读性好&#xff0c;以便自己循序渐进地学习 C 语言。 作为一个嵌入式开发人员&#xff0…

趁年轻,大胆闯

趁年轻&#xff0c;大胆闯如果我是20岁&#xff0c;我会拿出未来的十年&#xff0c;全力已赴的赚钱&#xff0c;折腾&#xff0c;不要任何安全感。 出来创业&#xff0c;就是为100倍以上的赔率来的。

HTTPS

一、HTTPS是什么 HTTPS也是一个应用层协议&#xff0c;是在HTTP协议的基础上引入了一个加密层。 由于HTTP协议内容一般都是本文方式明文传输的&#xff0c;这就导致它在传输过程中会出现被篡改的情况。 经典案例就是万恶的“运营商劫持”&#xff01; 除了运营商可以劫持&a…

如何快速从零开始搭建一个前端项目

2022 年了&#xff0c;如何快速从零开始搭建一个合适的前端项目&#xff1f; 准备工作 首先本地需要安装好 node 环境以及包管理工具&#xff0c;推荐直接使用 pnpm&#xff0c;也可以通过 pnpm 来直接管理 nodejs 版本。 pnpm 安装&#xff1a; # Mac or Linux curl -fsSL…

SpringMVC 环境配置

文章目录引入1、MVC的概念2、Spring MVC基本原理一、导入坐标&#xff08;导包&#xff09;导入Spring MVC所需要的jar包二、新建springmvc-config.xml文件三、配置web.xml四、 创建Controller五、配置SpringMVC配置文件六、配置页面其他引入 Spring Web MVC是一种基于Java的实…

cv算法工程师学习教程

前言一&#xff0c;计算机系统 1.1&#xff0c;计算机系统书籍1.2&#xff0c;设计模式教程 二&#xff0c;编程语言 2.1&#xff0c;C 学习资料2.2&#xff0c;Python 学习资料 三&#xff0c;数据结构与算法 3.1&#xff0c;数据结构与算法课程3.2&#xff0c;算法题解 四&am…

数字信号处理及python实现(三)

数字信号处理及python实现三抽样引起的混叠抽样的频域视图样本重建信号拟合正弦波线性与多项式内插理想低通滤波器这是参考知乎的数字信号处理及matlab实现的python实现版本&#xff0c;参考连接 上一期:数字信号处理及python实现(二) 项目文件结构 test为测试文件&#xff…