鉴于本人并非Win32专精,C++也只是学了个大概,因此涉及到底层的部分或有疏漏之处,拙文献丑,还请各位道友多多包涵体谅,能提出修改意见更是感激不尽。

0x00 现代化探索

早在以前写点GDI小程序的时候,巨硬同志就给我留下了这样的不良印象:

Windows很乱复杂,为windows写程序也很乱很复杂。

这导致了我很长一段时间都不敢碰C++,装了VS Community好久一次都没打开过,能用Java+写好的库就用。

可惜这种曲线救国的路终究走不长久,Stackoverflow大法也终究有不管用的时候。

由于早期的电脑设计是一个高度模块化的系统,声音处理被分为了单独的一块,理论上不管是耳机还是扬声器都是由声卡直接管理的。当然现在的声卡(日常笔记本)大都是板载的集成声卡,而且windows上也有Audio Endpoint Device (音频终端设备)这一概念,也不是完全没有路可以走。
然而只要打开过windows设备管理器的人都知道,耳机是没有作为一个设备出现在设备列表里面的。实际上在控制面板-声音中显示的音频输出并非扬声器或者耳机,而是声卡自己配置的音频输出通道,这也印证了“声卡直接接管音频IO的工作”。
(板载声卡是Conexant SmartAudio HD,其他比如Realtek家的估计也差不多,无法验证,欢迎补充)

接下来的这段是关于音频终端的设备的探索,由于对这一块的工作模式没什么了解,因此熟悉的读者可以直接跳过。

所幸打开Conexant SmartAudio HD的驱动详细信息可以看到,在集成声卡的下一层是\Driver\HDAudBus(见下图),度娘告诉我们这个东西的全名是Microsoft UAA Bus Driver for High Definition Audio(针对HD音频的微软UAA总线驱动),而这里的UAA则指的是Universal Audio Architecture (通用音频架构)。这是一个好消息,因为UAA标准是微软自己提出来的,也就意味着我们很可能可以通过windows API直接访问到上面的设备列表。

同样详细信息中的“总线关系”项提供了新的线索:
总线之下的设备包括两项,分别叫做SWD\MMDEVAPI\{0.0.0.00000000}.{c3efdd8a-e470-4841-9617-a42320dd6041}SWD\MMDEVAPI\{0.0.1.00000000}.{272497f4-319c-4cdd-a2ce-6288c70ebe0a},猜测两个分别是扬声器与耳机接口(后来被证实),而这个MMDEVAPI则是指的Windows Multimedia Device API (Windows多媒体设备API)。这个线索将我们引到了一个真正的突破:IMMNotificationClient。

0x01 三面红旗

有人说程序员有了一定境界的标志就是,能提出一个没有人研究过的问题。

IMMNotificationClient是何方神圣呢?英语好的道友大概能看出来,它能够提供一个通知设备改变事件的监听器。

事实也确实如此。
通过继承这个类,可以监听到音频终端设备的新增(OnDeviceAdded)、移除(OnDeviceRemoved)、状态更改(OnDeviceStateChanged)、属性值更改(OnPropertyValueChanged)以及默认设备更改(OnDefaultDeviceChanged)。这些方法的定义与用法在MSDN上面都有介绍,在此不多赘述了。

Talk is cheap, show me the code,什么都比不上一个热腾腾的Demo来的有用。
根据这两篇文章(1,2)写了一个简单的示例,代码如下:
(基本纯复制/粘贴,代码很乱,iostream和stdio一起用不能忍)

#define SAFE_RELEASE(punk)  \if ((punk) != NULL)  \{ (punk)->Release(); (punk) = NULL; }   #include <windows.h>
#include <setupapi.h>
#include <initguid.h>
#include <mmdeviceapi.h>
#include <Functiondiscoverykeys_devpkey.h>
#include "iostream"
#include <stdio.h>
using namespace std;    class CMMNotificationClient : public IMMNotificationClient
{
public:  IMMDeviceEnumerator *m_pEnumerator;  CMMNotificationClient():  _cRef(1),  m_pEnumerator(NULL)  {  // 初始化COM  ::CoInitialize(NULL);  HRESULT hr = S_OK;   // 创建接口hr = CoCreateInstance(   __uuidof(MMDeviceEnumerator), NULL,   CLSCTX_ALL, __uuidof(IMMDeviceEnumerator),   (void**)&m_pEnumerator);   if (hr==S_OK)   {   cout<<"接口创建成功"<<endl;   }   else   {   cout<<"接口创建失败"<<endl;   }   // 注册事件hr = m_pEnumerator->RegisterEndpointNotificationCallback((IMMNotificationClient*)this);   if (hr==S_OK){   cout<<"注册成功"<<endl;   }   else   {   cout<<"注册失败"<<endl;   }   }  ~CMMNotificationClient()  {  SAFE_RELEASE(m_pEnumerator)  ::CoUninitialize();   }  // IUnknown methods -- AddRef, Release, and QueryInterface
private:  LONG _cRef;   // Private function to print device-friendly nameHRESULT _PrintDeviceName(LPCWSTR  pwstrId);ULONG STDMETHODCALLTYPE AddRef()  {  return InterlockedIncrement(&_cRef);  }  ULONG STDMETHODCALLTYPE Release()  {  ULONG ulRef = InterlockedDecrement(&_cRef);  if (0 == ulRef)  {  delete this;  }  return ulRef;  }  HRESULT STDMETHODCALLTYPE QueryInterface(  REFIID riid, VOID **ppvInterface)  {  if (IID_IUnknown == riid)  {  AddRef();  *ppvInterface = (IUnknown*)this;  }  else if (__uuidof(IMMNotificationClient) == riid)  {  AddRef();  *ppvInterface = (IMMNotificationClient*)this;  }  else  {  *ppvInterface = NULL;  return E_NOINTERFACE;  }  return S_OK;  }  HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(   EDataFlow flow, ERole role,   LPCWSTR pwstrDeviceId)   {   cout<<"OnDefaultDeviceChanged"<<endl;return S_OK;   }   HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR pwstrDeviceId)   {   cout<<"OnDeviceAdded"<<endl;return S_OK;   };   HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR pwstrDeviceId)   {   cout<<"OnDeviceRemoved"<<endl;return S_OK;   }   HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(   LPCWSTR pwstrDeviceId,   DWORD dwNewState)   {   cout<<"OnDeviceStateChanged"<<endl;return S_OK;   }   HRESULT STDMETHODCALLTYPE OnPropertyValueChanged(   LPCWSTR pwstrDeviceId,   const PROPERTYKEY key)   {   cout<<"OnPropertyValueChanged"<<endl;_PrintDeviceName(pwstrDeviceId);return S_OK;   }
};   // Given an endpoint ID string, print the friendly device name.
HRESULT CMMNotificationClient::_PrintDeviceName(LPCWSTR pwstrId)
{HRESULT hr = S_OK;IMMDevice *pDevice = NULL;IPropertyStore *pProps = NULL;PROPVARIANT varString;CoInitialize(NULL);PropVariantInit(&varString);if (m_pEnumerator == NULL){// Get enumerator for audio endpoint devices.hr = CoCreateInstance(__uuidof(MMDeviceEnumerator),NULL, CLSCTX_INPROC_SERVER,__uuidof(IMMDeviceEnumerator),(void**)&m_pEnumerator);}if (hr == S_OK){hr = m_pEnumerator->GetDevice(pwstrId, &pDevice);}if (hr == S_OK){hr = pDevice->OpenPropertyStore(STGM_READ, &pProps);}if (hr == S_OK){// Get the endpoint device's friendly-name property.hr = pProps->GetValue(PKEY_Device_FriendlyName, &varString);}printf("----------------------\nDevice name: \"%S\"\n""  Endpoint ID string: \"%S\"\n",(hr == S_OK) ? varString.pwszVal : L"null device",(pwstrId != NULL) ? pwstrId : L"null ID");PropVariantClear(&varString);SAFE_RELEASE(pProps)SAFE_RELEASE(pDevice)return hr;
}int main(int argc, TCHAR* argv[], TCHAR* envp[])
{   CMMNotificationClient mmClient;cin.get();return 0;
}   

注:一开始导入windows.h、setupapi.h、initguid.h是因为_PrintDeviceName里面用的PKEY_Device_FriendlyName常量需要。理论上这个常量只要直接导入Functiondiscoverykeys_devpkey.h就可以了,不过可能因为Dev C++自带的MinGW版本问题,不加前面这三个会报undefined reference to 'PKEY_Device_FriendlyName',就按网上的解决方法加上了,各位有更好的欢迎提出。

编译运行结果如下,其间插拔两次耳机:
(为阅读效果做了部分处理,以//开头的行是注释)

接口创建成功
注册成功//第一次插入耳机OnPropertyValueChanged
----------------------
Device name: ""
  Endpoint ID string: "{0.0.0.00000000}.{c3efdd8a-e470-4841-9617-a42320dd6041}"
OnPropertyValueChanged
----------------------
Device name: ""
  Endpoint ID string: "{0.0.0.00000000}.{c3efdd8a-e470-4841-9617-a42320dd6041}"
OnPropertyValueChanged
----------------------
Device name: ""
  Endpoint ID string: "{0.0.0.00000000}.{c3efdd8a-e470-4841-9617-a42320dd6041}"
OnPropertyValueChanged
----------------------
Device name: ""
  Endpoint ID string: "{0.0.0.00000000}.{c3efdd8a-e470-4841-9617-a42320dd6041}"
OnPropertyValueChanged
----------------------
Device name: ""
  Endpoint ID string: "{0.0.0.00000000}.{c3efdd8a-e470-4841-9617-a42320dd6041}"
OnPropertyValueChanged
----------------------
Device name: ""
  Endpoint ID string: "{0.0.0.00000000}.{c3efdd8a-e470-4841-9617-a42320dd6041}"
OnPropertyValueChanged
----------------------
Device name: ""
  Endpoint ID string: "{0.0.0.00000000}.{c3efdd8a-e470-4841-9617-a42320dd6041}"// 注意这里是External
OnPropertyValueChanged
----------------------
Device name: "External Microphone (Conexant SmartAudio HD)"
  Endpoint ID string: "{0.0.1.00000000}.{272497f4-319c-4cdd-a2ce-6288c70ebe0a}"
OnPropertyValueChanged
----------------------
Device name: "External Microphone (Conexant SmartAudio HD)"
  Endpoint ID string: "{0.0.1.00000000}.{272497f4-319c-4cdd-a2ce-6288c70ebe0a}"
OnPropertyValueChanged
----------------------
Device name: "External Microphone (Conexant SmartAudio HD)"
  Endpoint ID string: "{0.0.1.00000000}.{272497f4-319c-4cdd-a2ce-6288c70ebe0a}"
OnPropertyValueChanged
----------------------
Device name: "External Microphone (Conexant SmartAudio HD)"
  Endpoint ID string: "{0.0.1.00000000}.{272497f4-319c-4cdd-a2ce-6288c70ebe0a}"
OnPropertyValueChanged
----------------------
Device name: "External Microphone (Conexant SmartAudio HD)"
  Endpoint ID string: "{0.0.1.00000000}.{272497f4-319c-4cdd-a2ce-6288c70ebe0a}"
OnPropertyValueChanged
----------------------
Device name: "External Microphone (Conexant SmartAudio HD)"
  Endpoint ID string: "{0.0.1.00000000}.{272497f4-319c-4cdd-a2ce-6288c70ebe0a}"// 第一次拔出耳机
OnPropertyValueChanged
----------------------
Device name: ""
  Endpoint ID string: "{0.0.0.00000000}.{c3efdd8a-e470-4841-9617-a42320dd6041}"
OnPropertyValueChanged
----------------------
Device name: ""
  Endpoint ID string: "{0.0.0.00000000}.{c3efdd8a-e470-4841-9617-a42320dd6041}"
OnPropertyValueChanged
----------------------
Device name: ""
  Endpoint ID string: "{0.0.0.00000000}.{c3efdd8a-e470-4841-9617-a42320dd6041}"
OnPropertyValueChanged
----------------------
Device name: ""
  Endpoint ID string: "{0.0.0.00000000}.{c3efdd8a-e470-4841-9617-a42320dd6041}"
OnPropertyValueChanged
----------------------
Device name: ""
  Endpoint ID string: "{0.0.0.00000000}.{c3efdd8a-e470-4841-9617-a42320dd6041}"
OnPropertyValueChanged
----------------------
Device name: ""
  Endpoint ID string: "{0.0.0.00000000}.{c3efdd8a-e470-4841-9617-a42320dd6041}"
OnPropertyValueChanged
----------------------
Device name: ""
  Endpoint ID string: "{0.0.0.00000000}.{c3efdd8a-e470-4841-9617-a42320dd6041}"
OnPropertyValueChanged
----------------------
Device name: ""
  Endpoint ID string: "{0.0.0.00000000}.{c3efdd8a-e470-4841-9617-a42320dd6041}"
OnPropertyValueChanged
----------------------
Device name: ""
  Endpoint ID string: "{0.0.0.00000000}.{c3efdd8a-e470-4841-9617-a42320dd6041}"
OnPropertyValueChanged
----------------------
Device name: ""
  Endpoint ID string: "{0.0.0.00000000}.{c3efdd8a-e470-4841-9617-a42320dd6041}"// 注意这里是Internal
OnPropertyValueChanged
----------------------
Device name: "Internal Microphone (Conexant SmartAudio HD)"
  Endpoint ID string: "{0.0.1.00000000}.{272497f4-319c-4cdd-a2ce-6288c70ebe0a}"
OnPropertyValueChanged
----------------------
Device name: "Internal Microphone (Conexant SmartAudio HD)"
  Endpoint ID string: "{0.0.1.00000000}.{272497f4-319c-4cdd-a2ce-6288c70ebe0a}"
OnPropertyValueChanged
----------------------
Device name: "Internal Microphone (Conexant SmartAudio HD)"
  Endpoint ID string: "{0.0.1.00000000}.{272497f4-319c-4cdd-a2ce-6288c70ebe0a}"
OnPropertyValueChanged
----------------------
Device name: "Internal Microphone (Conexant SmartAudio HD)"
  Endpoint ID string: "{0.0.1.00000000}.{272497f4-319c-4cdd-a2ce-6288c70ebe0a}"
OnPropertyValueChanged
----------------------
Device name: "Internal Microphone (Conexant SmartAudio HD)"
  Endpoint ID string: "{0.0.1.00000000}.{272497f4-319c-4cdd-a2ce-6288c70ebe0a}"
OnPropertyValueChanged
----------------------
Device name: "Internal Microphone (Conexant SmartAudio HD)"
  Endpoint ID string: "{0.0.1.00000000}.{272497f4-319c-4cdd-a2ce-6288c70ebe0a}"// 第二次插入耳机,输出内容与第一次基本相同
OnPropertyValueChanged
----------------------
Device name: ""
  Endpoint ID string: "{0.0.0.00000000}.{c3efdd8a-e470-4841-9617-a42320dd6041}"
OnPropertyValueChanged
----------------------
Device name: ""
  Endpoint ID string: "{0.0.0.00000000}.{c3efdd8a-e470-4841-9617-a42320dd6041}"
OnPropertyValueChanged
----------------------
Device name: ""
  Endpoint ID string: "{0.0.0.00000000}.{c3efdd8a-e470-4841-9617-a42320dd6041}"
OnPropertyValueChanged
----------------------
Device name: ""
  Endpoint ID string: "{0.0.0.00000000}.{c3efdd8a-e470-4841-9617-a42320dd6041}"
OnPropertyValueChanged
----------------------
Device name: ""
  Endpoint ID string: "{0.0.0.00000000}.{c3efdd8a-e470-4841-9617-a42320dd6041}"
OnPropertyValueChanged
----------------------
Device name: ""
  Endpoint ID string: "{0.0.0.00000000}.{c3efdd8a-e470-4841-9617-a42320dd6041}"
OnPropertyValueChanged
----------------------
Device name: ""
  Endpoint ID string: "{0.0.0.00000000}.{c3efdd8a-e470-4841-9617-a42320dd6041}"
OnPropertyValueChanged
----------------------
Device name: "External Microphone (Conexant SmartAudio HD)"
  Endpoint ID string: "{0.0.1.00000000}.{272497f4-319c-4cdd-a2ce-6288c70ebe0a}"
OnPropertyValueChanged
----------------------
Device name: "External Microphone (Conexant SmartAudio HD)"
  Endpoint ID string: "{0.0.1.00000000}.{272497f4-319c-4cdd-a2ce-6288c70ebe0a}"
OnPropertyValueChanged
----------------------
Device name: "External Microphone (Conexant SmartAudio HD)"
  Endpoint ID string: "{0.0.1.00000000}.{272497f4-319c-4cdd-a2ce-6288c70ebe0a}"
OnPropertyValueChanged
----------------------
Device name: "External Microphone (Conexant SmartAudio HD)"
  Endpoint ID string: "{0.0.1.00000000}.{272497f4-319c-4cdd-a2ce-6288c70ebe0a}"
OnPropertyValueChanged
----------------------
Device name: "External Microphone (Conexant SmartAudio HD)"
  Endpoint ID string: "{0.0.1.00000000}.{272497f4-319c-4cdd-a2ce-6288c70ebe0a}"
OnPropertyValueChanged
----------------------
Device name: "External Microphone (Conexant SmartAudio HD)"
  Endpoint ID string: "{0.0.1.00000000}.{272497f4-319c-4cdd-a2ce-6288c70ebe0a}"// 第二次拔出耳机
OnPropertyValueChanged
----------------------
Device name: ""
  Endpoint ID string: "{0.0.0.00000000}.{c3efdd8a-e470-4841-9617-a42320dd6041}"
OnPropertyValueChanged
----------------------
Device name: ""
  Endpoint ID string: "{0.0.0.00000000}.{c3efdd8a-e470-4841-9617-a42320dd6041}"
OnPropertyValueChanged
----------------------
Device name: ""
  Endpoint ID string: "{0.0.0.00000000}.{c3efdd8a-e470-4841-9617-a42320dd6041}"
OnPropertyValueChanged
----------------------
Device name: ""
  Endpoint ID string: "{0.0.0.00000000}.{c3efdd8a-e470-4841-9617-a42320dd6041}"
OnPropertyValueChanged
----------------------
Device name: ""
  Endpoint ID string: "{0.0.0.00000000}.{c3efdd8a-e470-4841-9617-a42320dd6041}"
OnPropertyValueChanged
----------------------
Device name: ""
  Endpoint ID string: "{0.0.0.00000000}.{c3efdd8a-e470-4841-9617-a42320dd6041}"
OnPropertyValueChanged
----------------------
Device name: ""
  Endpoint ID string: "{0.0.0.00000000}.{c3efdd8a-e470-4841-9617-a42320dd6041}"
OnPropertyValueChanged
----------------------
Device name: ""
  Endpoint ID string: "{0.0.0.00000000}.{c3efdd8a-e470-4841-9617-a42320dd6041}"
OnPropertyValueChanged
----------------------
Device name: ""
  Endpoint ID string: "{0.0.0.00000000}.{c3efdd8a-e470-4841-9617-a42320dd6041}"
OnPropertyValueChanged
----------------------
Device name: ""
  Endpoint ID string: "{0.0.0.00000000}.{c3efdd8a-e470-4841-9617-a42320dd6041}"
OnPropertyValueChanged
----------------------
Device name: "Internal Microphone (Conexant SmartAudio HD)"
  Endpoint ID string: "{0.0.1.00000000}.{272497f4-319c-4cdd-a2ce-6288c70ebe0a}"
OnPropertyValueChanged
----------------------
Device name: "Internal Microphone (Conexant SmartAudio HD)"
  Endpoint ID string: "{0.0.1.00000000}.{272497f4-319c-4cdd-a2ce-6288c70ebe0a}"
OnPropertyValueChanged
----------------------
Device name: "Internal Microphone (Conexant SmartAudio HD)"
  Endpoint ID string: "{0.0.1.00000000}.{272497f4-319c-4cdd-a2ce-6288c70ebe0a}"
OnPropertyValueChanged
----------------------
Device name: "Internal Microphone (Conexant SmartAudio HD)"
  Endpoint ID string: "{0.0.1.00000000}.{272497f4-319c-4cdd-a2ce-6288c70ebe0a}"
OnPropertyValueChanged
----------------------
Device name: "Internal Microphone (Conexant SmartAudio HD)"
  Endpoint ID string: "{0.0.1.00000000}.{272497f4-319c-4cdd-a2ce-6288c70ebe0a}"
OnPropertyValueChanged
----------------------
Device name: "Internal Microphone (Conexant SmartAudio HD)"
  Endpoint ID string: "{0.0.1.00000000}.{272497f4-319c-4cdd-a2ce-6288c70ebe0a}"

很可惜的是本来期望会有的反映的OnDeviceAdded/OnDeviceRemoved并没有输出,耳机插拔只在OnProperyValueChanged里面有所反映,而且唯一的不同点只在于设备名称以External还是Internal开头。

不过好消息是,这样作为检测耳机插拔就基本上够了。使用IMMNotificationClient在理论上是可行的。
另外就是,这也验证了我们前面的猜想:驱动详细信息中的总线关系的两个子项正是我们的扬声器和耳机——输出的Endpoint ID string和前面的值完全相同。

至于具体怎么实现,又怎么与Java代码相集成(见总起篇),由于篇幅所限,我们下篇文章不见不散。

【TopDesk】3.1.1. 利用IMMNotificationClient实现耳机插拔检测相关推荐

  1. WM8960耳机插拔检测

    WM8960支持耳机插拔检测功能,其中ADCLRC/GPIO1.LINPUT3/JD2和RINPUT3/JD3可以用来作为耳机插拔检测引脚. 需要配置的寄存器有如下几个: 1.R24的5.6位.第6位 ...

  2. Android4.×耳机插拔检测

    Android4.2耳机插拔检测实现方法 1. 耳机检测的硬件原理 一般的耳机检测包含普通的耳机检测和带mic的耳机检测两种,这两种耳机统称为Headset,而对于不带mic的耳机,一般称之为Head ...

  3. linux耳机插拔检测,Android应用开发之耳机插拔处理两种方式

    本文将带你了解Android应用开发[RK3288][Android6.0] 耳机插拔处理两种方式,希望本文对大家学Android有所帮助. [RK3288][Android6.0]   耳机插拔处理 ...

  4. 高通平台耳机插拔检测

    https://blog.csdn.net/u012899335/article/details/82312766 高通耳机的插拔检测需要配置NC或NO,并且使用匹配的耳机(欧标,美标). 欧标,美标 ...

  5. 【audio】耳机插拔 线控按键识别流程【转】

    耳机插拔/线控按键识别流程 耳机插拔/线控按键识别流程 1.文档概述 本文以msm8909平台,android N为例,介绍了通用情况下,耳机插拔的流程步骤,以及对耳机类型的识别逻辑.以方便在项目工作 ...

  6. 【audio】耳机插拔/线控按键识别流程

    耳机插拔/线控按键识别流程 1.文档概述 本文以msm8909平台,android N为例,介绍了通用情况下,耳机插拔的流程步骤,以及对耳机类型的识别逻辑.以方便在项目工作中经常会遇到耳机不被识别,或 ...

  7. Android耳机耳机,Android 耳机插拔流程源码跟踪浅析

    Android 开发过程中,使用耳机控制拍照,控制音乐播放,控制打电话等,线控再到蓝牙控... 耳机也在不断升级,耳机插拔的程序这一块也在不断完善.因此,在定制开发过程中,阅读这部分流程代码是必修的功 ...

  8. AVPlayer耳机插拔

    AVPlayer耳机插拔暂停播放. //耳机插拔监听 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector ...

  9. CoreAudioApi-音频端点设备-检测耳机插拔

    术语"端点设备"是指位于数据路径一端的硬件设备,该数据路径源自或终止于应用程序.音频终端设备的例子有扬声器.耳机.麦克风和CD播放器.沿着数据路径移动的音频数据可能在应用程序和端点 ...

最新文章

  1. ASP.NET中实现打印
  2. 电感计算软件_一文让你了解到共模电感和差模电感的差异
  3. 老铁666,快手突然“快”不动了?
  4. Linux安装Kafka-manager可视化
  5. 字符之间或者结构体之间比较
  6. Android之如何解决Android Studio左边的的project不见了
  7. jdk11换jdk8版本_在JDK 9(以及8)以及更高版本中,所有内容都可以作为一个流
  8. 华为鸿蒙与佳华,华为鸿蒙系统发布,带来三大好消息
  9. Android 应用开发(37)---RelativeLayout(相对布局)
  10. PAT甲级1024 ASCII码与整数转换
  11. ant基本命令和使用
  12. 对讲机在哪插卡?插卡对讲机是什么意思呢?5000公里对讲机的哪点事
  13. 解决接收 ACTION_PACKAGE_REPLACED 的广播会另外接收到 REMOVED 和 ADDED 的问题
  14. 欢迎来怼--第三十六次Scrum会议
  15. 前端--CSS选择器,盒子模型学习
  16. 利用Python turtle库制作夜空
  17. TensorFlow2学习七、使用MNIST手写体识别数据集识别自己手写图片
  18. iOS客户端的title不显示解决方案
  19. Web入门_朽木|学习笔记之第一章-数据库基本知识(1.1-1.7)
  20. 【浏览器插件推荐】Bookmarks clean up清除重复、废弃收藏夹

热门文章

  1. 图像质量评价学习笔记01:IQA的基本概念及分类
  2. Jetson TX2 内核重新编译
  3. 西安数字孪生智能工厂,数字工厂智能车间建设,3d可视化工业建模,三维数据可视化交互大屏
  4. could not load an entity问题
  5. elasticsearch数据类型--nested
  6. 重学巩固你的Vuejs知识体系(上)
  7. 500g java相关资料
  8. Android围住神经猫的实现
  9. 为什么 CSS 动画比 JavaScript 高效?
  10. 主轴承行业调研报告 - 市场现状分析与发展前景预测