【Android】蓝牙电话HFP连接源码分析

一、概述

在Android系统中,HF(Hands-Free Profile)客户端与AG(Audio Gateway)端之间的HFP(Hands-Free Profile)连接是蓝牙音频通信的重要组成部分。这一过程涉及多个层次和组件的协同工作,从Java层的BluetoothHeadsetClient开始,一直到C++层的蓝牙核心协议栈。

1.1. 初始连接请求

连接过程始于HF客户端(如车载蓝牙设备)上的应用程序调用BluetoothHeadsetClient的connect函数。这一调用标志着连接请求的发起,是整个连接流程的起点。

1.2. 状态机处理

随后,该连接请求被传递给一个状态机进行处理。状态机是Android蓝牙栈中用于管理蓝牙设备连接状态的重要组件。在接收到连接请求后,状态机会根据当前的状态和事件来决定下一步的动作。

在HFP连接过程中,状态机会经历多个状态的切换,包括但不限于:

  • IDLE:初始状态,等待连接请求。

  • CONNECTING:正在尝试建立连接。

  • CONNECTED:连接已成功建立。

  • DISCONNECTED:连接已断开。

1.3. C++接口调用

在状态机确定需要继续推进连接过程后,会调用到底层的C++接口。这些接口是Android蓝牙协议栈与蓝牙硬件之间的桥梁,负责执行实际的连接操作。

1.4. 蓝牙设备队列管理

在C++层,蓝牙设备的管理是通过一个设备队列来实现的。这个队列维护了所有待连接的蓝牙设备信息。当HF客户端发起连接请求时,该设备会被添加到队列中,并等待蓝牙核心栈的处理。

1.5. 实际连接操作

蓝牙核心栈在接收到连接请求后,会开始执行实际的连接操作。这包括:

  • SDP服务搜索:服务发现协议(SDP)用于在蓝牙设备间搜索可用的服务。在HFP连接中,SDP用于查找AG端提供的HFP服务。

  • 建立RFCOMM通道:RFCOMM是一种基于蓝牙的串行通信协议,用于在蓝牙设备间建立可靠的连接。在找到AG端的HFP服务后,HF客户端会与AG端建立一个RFCOMM通道作为通信的媒介。

  • L2CAP连接:逻辑链路控制和适配协议层(L2CAP)是蓝牙协议栈中的数据传输层。在RFCOMM通道建立之前,HF客户端和AG端之间需要先建立一个L2CAP连接。

1.6. 状态机切换与事件处理

在整个连接过程中,状态机会根据连接的状态和发生的事件进行切换和处理。例如,当SDP服务搜索完成时,状态机会从“搜索中”状态切换到“已找到服务”状态,并触发相应的事件处理逻辑。

二、源码分析

connect

packages/modules/Bluetooth/android/app/src/com/android/bluetooth/hfp/HeadsetService.java
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
public boolean connect(BluetoothDevice device) {if (getConnectionPolicy(device) == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {Log.w(TAG, "connect: CONNECTION_POLICY_FORBIDDEN, device=" + device + ", "+ Utils.getUidPidString());return false;}ParcelUuid[] featureUuids = mAdapterService.getRemoteUuids(device);if (!BluetoothUuid.containsAnyUuid(featureUuids, HEADSET_UUIDS)) { // 检查这些UUID中是否包含任何与HFP相关的UUIDLog.e(TAG, "connect: Cannot connect to " + device + ": no headset UUID, "+ Utils.getUidPidString());return false;}synchronized (mStateMachines) {Log.i(TAG, "connect: device=" + device + ", " + Utils.getUidPidString());HeadsetStateMachine stateMachine = mStateMachines.get(device);if (stateMachine == null) {stateMachine = HeadsetObjectsFactory.getInstance().makeStateMachine(device, mStateMachinesThread.getLooper(), this,mAdapterService, mNativeInterface, mSystemInterface);mStateMachines.put(device, stateMachine);}int connectionState = stateMachine.getConnectionState();if (connectionState == BluetoothProfile.STATE_CONNECTED|| connectionState == BluetoothProfile.STATE_CONNECTING) {Log.w(TAG, "connect: device " + device+ " is already connected/connecting, connectionState=" + connectionState);return false;}List<BluetoothDevice> connectingConnectedDevices =getDevicesMatchingConnectionStates(CONNECTING_CONNECTED_STATES);boolean disconnectExisting = false;if (connectingConnectedDevices.size() >= mMaxHeadsetConnections) {// When there is maximum one device, we automatically disconnect the current oneif (mMaxHeadsetConnections == 1) {disconnectExisting = true;} else {Log.w(TAG, "Max connection has reached, rejecting connection to " + device);return false;}}if (disconnectExisting) {for (BluetoothDevice connectingConnectedDevice : connectingConnectedDevices) {disconnect(connectingConnectedDevice);}setActiveDevice(null);}stateMachine.sendMessage(HeadsetStateMachine.CONNECT, device); // 向状态机发送一个连接消息}return true;
}

processMessage(CONNECT)

packages/modules/Bluetooth/android/app/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java
@Override
public synchronized boolean processMessage(Message message) {logD("Connected process message: " + message.what);if (mCurrentDevice == null) {Log.e(TAG, "ERROR: mCurrentDevice is null in Connected");return NOT_HANDLED;}switch (message.what) {case CONNECT:BluetoothDevice device = (BluetoothDevice) message.obj;if (mCurrentDevice.equals(device)) {// already connected to this device, do nothingbreak;}mNativeInterface.connect(device);break;...

处理与蓝牙耳机(HFP客户端)相关的各种状态变化。

mNativeInterface.connect

packages/modules/Bluetooth/android/app/src/com/android/bluetooth/hfpclient/NativeInterface.java
/*** Connect to the specified paired device** @param device target device* @return True on success, False on failure*/
@VisibleForTesting
public boolean connect(BluetoothDevice device) {return connectNative(getByteAddress(device));
}

Java层与底层原生代码(C/C++编写的)之间的桥梁。

connectNative

/packages/modules/Bluetooth/android/app/jni/com_android_bluetooth_hfpclient.cpp
static jboolean connectNative(JNIEnv* env, jobject /* object */,jbyteArray address) {std::shared_lock<std::shared_mutex> lock(interface_mutex);if (!sBluetoothHfpClientInterface) return JNI_FALSE;jbyte* addr = env->GetByteArrayElements(address, NULL);if (!addr) {jniThrowIOException(env, EINVAL);return JNI_FALSE;}bt_status_t status =sBluetoothHfpClientInterface->connect((const RawAddress*)addr);if (status != BT_STATUS_SUCCESS) {log::error("Failed AG connection, status: {}", bt_status_text(status));}env->ReleaseByteArrayElements(address, addr, 0);return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}

connectNative函数是一个native method,通过JNI(Java Native Interface)从Java层接收调用,并尝试建立到指定蓝牙地址的HFP(Hands-Free Profile)客户端连接。

协议栈代码分析见【Bluedroid】HFP连接流程源码分析(一)-CSDN博客

连接回调处理。

connection_state_cb

/packages/modules/Bluetooth/android/app/jni/com_android_bluetooth_hfpclient.cpp
static void connection_state_cb(const RawAddress* bd_addr,bthf_client_connection_state_t state,unsigned int peer_feat,unsigned int chld_feat) {std::shared_lock<std::shared_mutex> lock(callbacks_mutex);CallbackEnv sCallbackEnv(__func__);if (!sCallbackEnv.valid() || mCallbacksObj == NULL) return;ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));if (!addr.get()) return;log::debug("state {} peer_feat {} chld_feat {}", state, peer_feat, chld_feat);sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onConnectionStateChanged,(jint)state, (jint)peer_feat, (jint)chld_feat,addr.get());
}

connection_state_cb是一个回调函数,用于通知Java层的Bluetooth HFP客户端关于连接状态的变化。 

 

onConnectionStateChanged

/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/hfpclient/NativeInterface.java
// Callbacks from the native back into the java framework. All callbacks are routed via the
// Service which will disambiguate which state machine the message should be routed through.
@VisibleForTesting
void onConnectionStateChanged(int state, int peerFeat, int chldFeat, byte[] address) {StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);event.valueInt = state;event.valueInt2 = peerFeat;event.valueInt3 = chldFeat;event.device = getDevice(address);// BluetoothAdapter.getDefaultAdapter().getRemoteDevice(Utils.getAddressStringFromByte// (address));if (DBG) {Log.d(TAG, "Device addr " + event.device + " State " + state);}HeadsetClientService service = HeadsetClientService.getHeadsetClientService();if (service != null) {service.messageFromNative(event);} else {Log.w(TAG, "Ignoring message because service not available: " + event);}
}

Android Bluetooth HFP(Hands-Free Profile)客户端Java层与从本地(native)代码回调到Java框架相关的部分。onConnectionStateChanged方法是一个回调方法,它接收来自本地层的连接状态变化通知。

messageFromNative

packages/modules/Bluetooth/android/app/src/com/android/bluetooth/hfpclient/HeadsetClientService.java
// Handle messages from native (JNI) to java
public void messageFromNative(StackEvent stackEvent) {Objects.requireNonNull(stackEvent.device,"Device should never be null, event: " + stackEvent);HeadsetClientStateMachine sm = getStateMachine(stackEvent.device,isConnectionEvent(stackEvent));if (sm == null) {throw new IllegalStateException("State machine not found for stack event: " + stackEvent);}sm.sendMessage(StackEvent.STACK_EVENT, stackEvent);
}

Android Bluetooth HFP客户端Java层中处理来自本地(JNI)回调消息的一部分。messageFromNative方法负责接收一个封装了蓝牙堆栈事件的StackEvent对象,并根据该事件中的信息将事件分发给相应的状态机(HeadsetClientStateMachine)进行处理。

processMessage(EVENT_TYPE_CONNECTION_STATE_CHANGED)

packages/modules/Bluetooth/android/app/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java
@Override
public synchronized boolean processMessage(Message message) {logD("Connecting process message: " + message.what);switch (message.what) {case CONNECT:case CONNECT_AUDIO:case DISCONNECT:deferMessage(message);break;case StackEvent.STACK_EVENT:StackEvent event = (StackEvent) message.obj;logD("Connecting: event type: " + event.type);switch (event.type) {...case StackEvent.EVENT_TYPE_CMD_RESULT:logD("Connecting: CMD_RESULT valueInt:" + event.valueInt+ " mQueuedActions.size=" + mQueuedActions.size());if (!mQueuedActions.isEmpty()) {logD("queuedAction:" + mQueuedActions.peek().first);}Pair<Integer, Object> queuedAction = mQueuedActions.poll();if (queuedAction == null || queuedAction.first == NO_ACTION) {break;}switch (queuedAction.first) {case SEND_ANDROID_AT_COMMAND:if (event.valueInt == StackEvent.CMD_RESULT_TYPE_OK) {Log.w(TAG, "Received OK instead of +ANDROID");} else {Log.w(TAG, "Received ERROR instead of +ANDROID");}setAudioPolicyRemoteSupported(false);transitionTo(mConnected);break;default:Log.w(TAG, "Ignored CMD Result");break;}break;...default:Log.w(TAG, "Message not handled " + message);return NOT_HANDLED;}return HANDLED;
}

HeadsetClientStateMachine类中的processMessage方法负责处理从消息队列中接收到的消息,并根据消息类型执行相应的操作。

enter(Connected)

@Override
public void enter() {logD("Enter Connected: " + getCurrentMessage().what);mAudioWbs = false;mAudioSWB = false;mCommandedSpeakerVolume = -1;if (mPrevState == mConnecting) {broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_CONNECTED,BluetoothProfile.STATE_CONNECTING);if (mHeadsetService != null) {mHeadsetService.updateInbandRinging(mCurrentDevice, true);}MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.HEADSET_CLIENT);} else if (mPrevState != mAudioOn) {String prevStateName = mPrevState == null ? "null" : mPrevState.getName();Log.e(TAG, "Connected: Illegal state transition from " + prevStateName+ " to Connected, mCurrentDevice=" + mCurrentDevice);}mService.updateBatteryLevel();
}

HeadsetClientStateMachine状态机中的enter方法,该方法在状态机进入“Connected”(已连接)状态时被调用。enter方法通常用于执行进入新状态时需要立即执行的操作,如初始化状态变量、发送广播、更新服务等。

broadcastConnectionState

packages/modules/Bluetooth/android/app/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java
// This method does not check for error condition (newState == prevState)
private void broadcastConnectionState(BluetoothDevice device, int newState, int prevState) {logD("Connection state " + device + ": " + prevState + "->" + newState);/** Notifying the connection state change of the profile before sending* the intent for connection state change, as it was causing a race* condition, with the UI not being updated with the correct connection* state.*/Intent intent = new Intent(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED); // 表示蓝牙HFP客户端的连接状态发生了变化intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);// add feature extras when connectedif (newState == BluetoothProfile.STATE_CONNECTED) {if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_3WAY)== HeadsetClientHalConstants.PEER_FEAT_3WAY) {intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_3WAY_CALLING, true);}if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_VREC)== HeadsetClientHalConstants.PEER_FEAT_VREC) {intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_VOICE_RECOGNITION, true);}if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_REJECT)== HeadsetClientHalConstants.PEER_FEAT_REJECT) {intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_REJECT_CALL, true);}if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_ECC)== HeadsetClientHalConstants.PEER_FEAT_ECC) {intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_ECC, true);}// add individual CHLD support extrasif ((mChldFeatures & HeadsetClientHalConstants.CHLD_FEAT_HOLD_ACC)== HeadsetClientHalConstants.CHLD_FEAT_HOLD_ACC) {intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_ACCEPT_HELD_OR_WAITING_CALL,true);}if ((mChldFeatures & HeadsetClientHalConstants.CHLD_FEAT_REL)== HeadsetClientHalConstants.CHLD_FEAT_REL) {intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_RELEASE_HELD_OR_WAITING_CALL, true);}if ((mChldFeatures & HeadsetClientHalConstants.CHLD_FEAT_REL_ACC)== HeadsetClientHalConstants.CHLD_FEAT_REL_ACC) {intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_RELEASE_AND_ACCEPT, true);}if ((mChldFeatures & HeadsetClientHalConstants.CHLD_FEAT_MERGE)== HeadsetClientHalConstants.CHLD_FEAT_MERGE) {intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_MERGE, true);}if ((mChldFeatures & HeadsetClientHalConstants.CHLD_FEAT_MERGE_DETACH)== HeadsetClientHalConstants.CHLD_FEAT_MERGE_DETACH) {intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_MERGE_AND_DETACH, true);}}mService.sendBroadcastMultiplePermissions(intent,new String[] {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED},Utils.getTempBroadcastOptions()); // 广播了创建的Intent,同时确保了只有具有相应权限的应用程序才能接收到这个广播// 通知了HFP客户端连接服务关于连接状态的变化HfpClientConnectionService.onConnectionStateChanged(device, newState, prevState);
}

负责在蓝牙客户端的状态发生变化时广播这一变化,负责创建并广播包含连接状态和相关功能信息的Intent,并通知相关的服务关于状态的变化。确保蓝牙HFP客户端能够与其他应用程序和服务有效地交互,并提供正确的连接状态信息。

HfpClientConnectionService.onConnectionStateChanged

packages/modules/Bluetooth/android/app/src/com/android/bluetooth/hfpclient/HfpClientConnectionService.java
/*** Send a device connection state changed event to this service*/
public static void onConnectionStateChanged(BluetoothDevice device, int newState,int oldState) {HfpClientConnectionService service = getInstance();if (service == null) {Log.e(TAG, "onConnectionStateChanged: HFP Client Connection Service not started");return;}service.onConnectionStateChangedInternal(device, newState, oldState);
}

当蓝牙HFP设备的连接状态发生变化时,通过HfpClientConnectionService服务来处理这一变化。

onConnectionStateChangedInternal

packages/modules/Bluetooth/android/app/src/com/android/bluetooth/hfpclient/HfpClientConnectionService.java
private void onConnectionStateChangedInternal(BluetoothDevice device, int newState,int oldState) {if (newState == BluetoothProfile.STATE_CONNECTED) { // 已连接if (DBG) {Log.d(TAG, "Established connection with " + device);}HfpClientDeviceBlock block = createBlockForDevice(device);if (block == null) {Log.w(TAG, "Block already exists for device= " + device + ", ignoring.");}} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {if (DBG) {Log.d(TAG, "Disconnecting from " + device);}// Disconnect any inflight calls from the connection service.synchronized (HfpClientConnectionService.this) {HfpClientDeviceBlock block = mDeviceBlocks.remove(device);if (block == null) {Log.w(TAG, "Disconnect for device but no block, device=" + device);return;}block.cleanup();}}AdapterService adapterService = AdapterService.getAdapterService();if (adapterService != null && adapterService.getRemoteDevices() != null) {adapterService.getRemoteDevices().handleHeadsetClientConnectionStateChanged(device, oldState, newState);}// 通知GATT(Generic Attribute Profile)关于HFP客户端配置文件连接状态的变化adapterService.notifyProfileConnectionStateChangeToGatt(BluetoothProfile.HEADSET_CLIENT, oldState, newState);if (PbapClientService.getPbapClientService() != null) {PbapClientService.getPbapClientService().handleHeadsetClientConnectionStateChanged(device, oldState, newState);}if (adapterService != null) {adapterService.updateProfileConnectionAdapterProperties(device, BluetoothProfile.HEADSET_CLIENT, newState, oldState);}
}

负责处理蓝牙HFP客户端设备的连接状态变化,包括建立连接和断开连接时的逻辑处理,以及通知其他相关服务关于连接状态的变化。

至此,HFP连接一完成。

三、关键字

HeadsetClientService|HeadsetClientStateMachine

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

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

相关文章

【机器学习实战入门】使用Pandas和OpenCV进行颜色检测

Python 颜色检测项目 今天的项目将非常有趣和令人兴奋。我们将与颜色打交道&#xff0c;并在项目过程中学习许多概念。颜色检测对于识别物体来说是必要的&#xff0c;它也被用作各种图像编辑和绘图应用的工具。 什么是颜色检测&#xff1f; 颜色检测是检测任何颜色名称的过程…

《CPython Internals》阅读笔记:p232-p249

《CPython Internals》学习第 13天&#xff0c;p232-p249 总结&#xff0c;总计 18 页。 一、技术总结 无。 二、英语总结(生词&#xff1a;1) 1.overhead (1)overhead: over-(“above”) head(“top part, uppermost section”) overhead的字面意思是&#xff1a;above…

动手学大数据-3社区开源实践

目录 数据库概览&#xff1a; MaxComput&#xff1a; HAWQ&#xff1a; Hologres&#xff1a; TiDB&#xff1a; Spark&#xff1a; ClickHouse&#xff1a; Apache Calcite 概览 Calcite RBO HepPlanner 优化规则&#xff08;Rule&#xff09; 内置有100优化规则 …

多平台下Informatica在医疗数据抽取中的应用

一、引言 1.医疗数据抽取与 Informatica 概述 1.1 医疗数据的特点与来源 1.1.1 数据特点 医疗数据具有显著的多样性特点。从数据类型来看&#xff0c;涵盖了结构化数据&#xff0c;如患者的基本信息、检验检查结果等&#xff0c;这些数据通常以表格形式存储&#xff0c;便于…

HTTP / 2

序言 在之前的文章中我们介绍过了 HTTP/1.1 协议&#xff0c;现在再来认识一下迭代版本 2。了解比起 1.1 版本&#xff0c;后面的版本改进在哪里&#xff0c;特点在哪里&#xff1f;话不多说&#xff0c;开始吧⭐️&#xff01; 一、 HTTP / 1.1 存在的问题 很多时候新的版本的…

BUUCTF_Web(October 2019 Twice SQL injection)

October 2019 Twice SQL injection 知识点&#xff1a; 二次注入&#xff1a; 当用户提交的恶意数据被存入数据库后&#xff0c;应用程序再把它读取出来用于生成新的SQL语句时&#xff0c;如果没有相应的安全措施&#xff0c;是有可能发生SQL注入的&#xff0c;这种注入就叫…

Jenkins-pipeline语法说明

一. 简述&#xff1a; Jenkins Pipeline 是一种持续集成和持续交付&#xff08;CI/CD&#xff09;工具&#xff0c;它允许用户通过代码定义构建、测试和部署流程。 二. 关于jenkinsfile&#xff1a; 1. Sections部分&#xff1a; Pipeline里的Sections通常包含一个或多个Direc…

电商项目高级篇08-springCache

电商项目高级篇08-springCache 1、整合springCache2、Cacheable细节设置 1、整合springCache 1、引入依赖 <!--引入springCache--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifa…

【tailscale 和 ssh】当服务器建立好节点,但通过客户端无法通过 ssh 连接

背景 当服务器建立好节点&#xff0c;一切显示正常但通过客户端无法通过 vs code 中的 ssh 连接到服务器 问题解决 因为服务器是重装过的&#xff0c;所以忘记在服务器上下载 ssh 了。。。安装完成并启动 SSH 服务后便可正常连接&#xff01; sudo apt update sudo apt in…

python编程-OpenCV(图像读写-图像处理-图像滤波-角点检测-边缘检测)边缘检测

OpenCV中边缘检测四种常用算子&#xff1a; &#xff08;1&#xff09;Sobel算子 Sobel算子是一种基于梯度的边缘检测算法。它通过对图像进行卷积操作来计算图像的梯度&#xff0c;并将梯度的大小作为边缘的强度。它使用两个3x3的卷积核&#xff0c;分别用于计…

[实现Rpc] 环境搭建 | JsonCpp | Mudou库 | callBack()

目录 1. 项目介绍 2. 技术选型 3. 开发环境和环境搭建 Ubuntu-22.04环境搭建 1. 安装 wget&#xff08;一般情况下默认会自带&#xff09; 2. 更换国内软件源 ① 备份原始 /etc/apt/sources.list 文件 ② 编辑软件源文件 ③ 更新软件包列表 3. 安装常用工具 3.1 安装…

Golang Gin系列-1:Gin 框架总体概述

本文介绍了Gin框架&#xff0c;探索了它的关键特性&#xff0c;并建立了简单入门的应用程序。在这系列教程里&#xff0c;我们会探索Gin的主要特性&#xff0c;如路由、中间件、数据库集成等&#xff0c;最终能使用Gin框架构建健壮的web应用程序。 总体概述 Gin是Go编程语言的…

Node.js 与 JavaScript 是什么关系

JavaScript 是一种编程语言&#xff0c;而 Node.js 是 JavaScript 的一个运行环境&#xff0c;它们在不同的环境中使用&#xff0c;具有一些共同的语言基础&#xff0c;但也有各自独特的 API 和模块&#xff0c;共同推动着 JavaScript 在前后端开发中的广泛应用。 一、基础语言…

游戏引擎学习第81天

仓库:https://gitee.com/mrxiao_com/2d_game_2 或许我们应该尝试在地面上添加一些绘图 在这段时间的工作中&#xff0c;讨论了如何改进地面渲染的问题。虽然之前并没有专注于渲染部分&#xff0c;因为当时主要的工作重心不在这里&#xff0c;但在实现过程中&#xff0c;发现地…

IO多路复用详解-selectpollepoll

目录 1.IO多路复用概念 2.系统调用函数 2.1select 2.1.1select函数细节 2.2基于select实现并发处理 2.2.1处理流程 2.2.2服务端通信代码 2.2.3客户端通信代码 2.3基于poll函数实现并发处理 2.3.1select与poll函数区别 2.3.2poll函数 2.3.3服务器端代码实现 2.3.4客…

IDEA下载安装

目录 IDEAWin下载安装 Mac下载安装 IDEA中基本配置&注释修改背景主题为白色修改字体大小鼠标滚轮控制字体大小控制字母大小写提示&#xff08;取消勾选&#xff09;设置自动编译&#xff08;打勾&#xff09;自动保存&#xff08;参数为1&#xff09;设定参数提示&#xff…

A Dual-Module Denoising Approach 解读

系列博客目录 文章目录 系列博客目录1.这个GCN有什么用2.GCN是如何增强方面相关的情感表达 1.这个GCN有什么用 在本文中&#xff0c;图卷积网络&#xff08;GCN&#xff09;的作用可以总结为以下几点&#xff1a; 建模多模态依赖关系 GCN 利用 加权关联矩阵 (A)&#xff0c;将…

复用类(1):组合、继承

复用代码是java众多引人注目的功能之一。但要想成为极具革命性的语言&#xff0c;仅仅能够复制代码并对之加以改变是不够的&#xff0c;它还必须能够做更多的事情。 上述方法常为C这类过程型语言所使用&#xff0c;但收效不是很好。正如java中所有事物一样&#xff0c;问题解决…

.Net Core微服务入门全纪录(五)——Ocelot-API网关(下)

系列文章目录 1、.Net Core微服务入门系列&#xff08;一&#xff09;——项目搭建 2、.Net Core微服务入门全纪录&#xff08;二&#xff09;——Consul-服务注册与发现&#xff08;上&#xff09; 3、.Net Core微服务入门全纪录&#xff08;三&#xff09;——Consul-服务注…

RV1126+FFMPEG推流项目(9)AI和AENC模块绑定,并且开启线程采集

前面两篇已经交代AI和AENC模块的配置&#xff0c;这篇就让这两个模块绑定起来&#xff0c;绑定的原因是&#xff0c;Aenc从Ai模块拿到采集的原始数据进行编码。 使用 RK_MPI_SYS_Bind 把 AI 节点和 AENC 进行绑定&#xff0c;其中 enModId 是模块 ID 号选择的是 RK_ID_AI、s32C…