1、Kinect设备信息获取
2、音频功能探索
由于Kinect支持多台设备同连,所以,理论上,我们应该可以获取所有的设备信息,这里我们探索一下。
通过官方的AudioBasics-D2D这个Demo发现,获取Kinect设备只需要一步:
IKinectSensor* m_pKinectSensor; GetDefaultKinectSensor(&m_pKinectSensor);
我们在接口列表中(https://msdn.microsoft.com/en-us/library/dn791996.aspx)查找,发现一个叫做 IKinectSensorCollection 的类,很显眼的是关于Sensor收集的类,我们的脚步就从这里开始。
新建一个工程,按照第二节的内容,设置属性,加入头文件。好啦,我们开始:
额,不好意思!为什么呢?因为坑x的微软在去年八月份的时候把这个功能给Cut了,所以...你是不可能用的,将来可能还会加上。我们了解下就好了,下面我找了一份旧代码大家看看就可以了。
IKinectSensorCollection* pKinectCollection = nullptr; IEnumKinectSensor* pEnumKinect = nullptr; IKinectSensor* pKinect = nullptr; // 获取Kinect集合 HRESULT hr = ::GetKinectSensorCollection(&pKinectCollection); // 获取Kinect枚举器 if (SUCCEEDED(hr)){ hr = pKinectCollection->get_Enumerator(&pEnumKinect); } // 枚举Kinect if (SUCCEEDED(hr)){ BOOLEAN available = false; while (true){ // 获取下一个 if (SUCCEEDED(pEnumKinect->GetNext(&pKinect))){ // 判断有效性 pKinect->get_IsAvailable(&available); if (available && YourJudgmentFunc(pKinect)){ break; } SafeRelease(pKinect); } else break; } } SafeRelease(pEnumKinect); SafeRelease(pKinectCollection);
不要在意这些细节,这都是小事。我们可以通过 GetDefaultKinectSensor(_COM_Outptr_ IKinectSensor** defaultKinectSensor); 这个函数获取默认的Kinect设备,哈哈哈!
下面是一段测试的小程序:
#include "stdafx.h" #include "kinect.h" int _tmain(int argc, _TCHAR* argv[]) { printf("Hello, Wellcome to kinect world!\n"); //IKinectSensorCollection aa; IKinectSensor* bb; HRESULT hr = GetDefaultKinectSensor(&bb); if ( FAILED(hr) ) { printf("No Kinect connect to your pc!\n"); goto endstop; } BOOLEAN bAvaliable = 0; bb->get_IsAvailable(&bAvaliable); printf("bAvaliable: %d\n", bAvaliable); BOOLEAN bIsOpen = 0; bb->get_IsOpen(&bIsOpen); printf("bIsOpen: %d\n", bIsOpen); DWORD dwCapability = 0; bb->get_KinectCapabilities(&dwCapability); printf("dwCapability: %d\n", dwCapability); TCHAR bbuid[256] = { 0 }; bb->get_UniqueKinectId(256, bbuid); printf("UID: %s\n",bbuid); endstop: system("pause"); return 0; }
在没有插上Kinect时的结果:
这里很容易发现一个奇怪的问题,获取默认Kinect设备成功,但是这个设备是不可用的,什么信息都获取不到,看来 GetDefaultKinectSensor 很不靠谱啊,需要通过 isAvaliable 判断一下才能使用。
下面,我们插上Kinect设备继续测试:
晕啊,傻眼了,为什么会这样子呢?我调试啊调试,然后拿着SDK中的Demo调试,甚至加上了bb->Open()调试,还是那个样子。经多方折腾,最终发现了Kinect不得不说的秘密啊!
Kinect默认是关闭状态,你必须Open之后才能判断是否有效等等内容。但是呢,Kinect的Open是需要时间的,在我的电脑上需要3S左右,否则即便isOpen为1,但是avaliable可能还是0。
再继续研究Kinect的头文件发现,它使用了rpc技术,这样导致函数的状态的回复不可能像本地机器码跑得那么快,所以你需要一定的时间等待Kinect更新自身的状态!
新的代码如下:
#include "stdafx.h" #include "kinect.h" int _tmain(int argc, _TCHAR* argv[]) { printf("Hello, Wellcome to kinect world!\n"); IKinectSensor* bb; //申请一个Sensor指针 HRESULT hr = GetDefaultKinectSensor(&bb); // 获取一个默认的Sensor if ( FAILED(hr) ) { printf("No Kinect connect to your pc!\n"); goto endstop; } BOOLEAN bIsOpen = 0; bb->get_IsOpen(&bIsOpen); // 查看下是否已经打开 printf("bIsOpen: %d\n", bIsOpen); if ( !bIsOpen ) // 没打开,则尝试打开 { hr = bb->Open(); if ( FAILED(hr) ) { printf("Kinect Open Failed!\n"); goto endstop; } printf("Kinect opened! But it need sometime to work!\n"); // 这里一定要多等会,否则下面的判断都是错误的 printf("Wait For 3000 ms...\n"); Sleep(3000); } bIsOpen = 0; bb->get_IsOpen(&bIsOpen); // 是否已经打开 printf("bIsOpen: %d\n", bIsOpen); BOOLEAN bAvaliable = 0; bb->get_IsAvailable(&bAvaliable); // 是否可用 printf("bAvaliable: %d\n", bAvaliable); DWORD dwCapability = 0; bb->get_KinectCapabilities(&dwCapability); // 获取容量 printf("dwCapability: %d\n", dwCapability); TCHAR bbuid[256] = { 0 }; bb->get_UniqueKinectId(256, bbuid); // 获取唯一ID printf("UID: %s\n",bbuid); bb->Close(); endstop: system("pause"); return 0; }
接上Kinect结果如下:
哈哈,圆满完成任务。只不过,实际情况应该轮询检查Kinect的状态,不应该使用Sleep()草草替代了。
由于Kinect音频是一个麦克风矩阵,所以可以进行声音方向的探测,这个功能比较喜人。当然,你也可以使用它结合微软的语音识别引擎,进行语音识别和控制,这个是个比较大的专题,这里不做讨论。
我们先来进行声源的判断。接着上面的内容,通过GetDefaultKinectSensor 获取默认Kinect设备,然后Open,接着就可以通过它的成员函数 get_AudioSource 获取音频源,然后根据音频源的成员函数 get_AudioBeams 获取波束列表,然后 OpenAudioBeam 获取第一个波束,为什么是第一个呢?因为现在只支持第一个。最后波束beam的成员函数get_BeamAngle 获取角度,get_BeamAngleConfidence 获取对应的可信度。
#include "stdafx.h" #include "kinect.h" int _tmain(int argc, _TCHAR* argv[]) { printf("Hello, Wellcome to kinect world!\n"); IKinectSensor* bb; //申请一个Sensor指针 HRESULT hr = GetDefaultKinectSensor(&bb); // 获取一个默认的Sensor if ( FAILED(hr) ) { printf("No Kinect connect to your pc!\n"); goto endstop; } BOOLEAN bIsOpen = 0; bb->get_IsOpen(&bIsOpen); // 查看下是否已经打开 printf("bIsOpen: %d\n", bIsOpen); if ( !bIsOpen ) // 没打开,则尝试打开 { hr = bb->Open(); if ( FAILED(hr) ) { printf("Kinect Open Failed!\n"); goto endstop; } printf("Kinect opened! But it need sometime to work!\n"); // 这里一定要多等会,否则下面的判断都是错误的 printf("Wait For 3000 ms...\n"); Sleep(3000); } bIsOpen = 0; bb->get_IsOpen(&bIsOpen); // 是否已经打开 printf("bIsOpen: %d\n", bIsOpen); BOOLEAN bAvaliable = 0; bb->get_IsAvailable(&bAvaliable); // 是否可用 printf("bAvaliable: %d\n", bAvaliable); DWORD dwCapability = 0; bb->get_KinectCapabilities(&dwCapability); // 获取容量 printf("dwCapability: %d\n", dwCapability); TCHAR bbuid[256] = { 0 }; bb->get_UniqueKinectId(256, bbuid); // 获取唯一ID printf("UID: %s\n",bbuid); // 音频数据获取 IAudioSource* audios = nullptr; UINT nAudioCount = 0; hr = bb->get_AudioSource(&audios); if ( FAILED(hr) ) { printf("Audio Source get failed!\n"); goto endclose; } IAudioBeam* audiobm = nullptr; IAudioBeamList* audiobml = nullptr; audios->get_AudioBeams(&audiobml); audiobml->OpenAudioBeam(0, &audiobm); // 目前只支持第一个 float fAngle = 0.0f; float fAngleConfidence = 0.0f; while (true) { fAngle = 0.0f; fAngleConfidence = 0.0f; audiobm->get_BeamAngle(&fAngle); // 获取音频的角度,[ -0.872665f, 0.8726665f ] audiobm->get_BeamAngleConfidence(&fAngleConfidence); // 获取音频的可信度(0 - 1) printf("Angle: %3.2f (%1.2f)\n", (fAngle/3.1415926f)*180.0f, fAngleConfidence); Sleep(200); } endclose: bb->Close(); endstop: system("pause"); return 0; }
下面进行音频数据的获取。
我们可以用一般获取录音一样获取音频流,请注意,从这里获取的音频流是原始数据:麦克风列阵获取的多声道音频,并且没有利用麦克风列阵进行降噪处理。代码可以查看SDK自带的获取原始数据的例子,因为与通用设备打交道,很麻烦,这里不做说明。
这里说的是利用自带的方法,获取经处理的音频数据。经过处理的数据信息如下:
l 编码: 32位标准浮点(IEEE FLOAT)
l 声道: 1
l 采样率: 16000Hz
SDK中获取处理后的音频流有两种方法,一种是音频帧,和之前的各种帧差不多:
// 获取音频源(AudioSource) if (SUCCEEDED(hr)){ hr = m_pKinect->get_AudioSource(&pAudioSource); } // 再获取音频帧读取器 if (SUCCEEDED(hr)){ hr = pAudioSource->OpenReader(&m_pAudioBeamFrameReader); } // 注册临帧事件 if (SUCCEEDED(hr)){ m_pAudioBeamFrameReader->SubscribeFrameArrived(&m_hAudioBeamFrameArrived); }
这样初始化。使用后,像之前那样,根据事件获取 AudioBeamFrameArrivedEventArgs, 再获取 AudioBeamFrameReference 音频帧引用,再获取 AudioBeamFrameList 音频帧链表,目前链表只有一个元素,直接获取 AudioBeamFrame音频帧。音频帧可能包含复数 AudioBeamSubFrame 音频副帧(比如本人这里包含2个),这个东西才能获取音频流的真正信息。
还有就是IStream,前面的这不是指C++标准库的输入流,而是COM组件的“流接口”,可读可写。初始化代码如下:
if (SUCCEEDED(hr)) { hr = m_pKinectSensor->get_AudioSource(&pAudioSource); } if (SUCCEEDED(hr)) { hr = pAudioSource->get_AudioBeams(&pAudioBeamList); } if (SUCCEEDED(hr)) { hr = pAudioBeamList->OpenAudioBeam(0, &m_pAudioBeam); } if (SUCCEEDED(hr)) { hr = m_pAudioBeam->OpenInputStream(&m_pAudioStream); }
m_pAudioSteam就是Steam对象,使用时ISteam::Read(void*, ULONG, ULONG*)主动获取音频数据。相比而言,使用音频帧既可以主动获取,又能使用事件机制,而Stream只能主动获取。
我们这里使用第二种流的方式,编写一个Demo,代码如下:
#include "stdafx.h" #include "kinect.h" #define _USE_MATH_DEFINES #include <math.h> int _tmain(int argc, _TCHAR* argv[]) { printf("Hello, Wellcome to kinect world!\n"); IKinectSensor* bb; //申请一个Sensor指针 HRESULT hr = GetDefaultKinectSensor(&bb); // 获取一个默认的Sensor if ( FAILED(hr) ) { printf("No Kinect connect to your pc!\n"); goto endstop; } BOOLEAN bIsOpen = 0; bb->get_IsOpen(&bIsOpen); // 查看下是否已经打开 printf("bIsOpen: %d\n", bIsOpen); if ( !bIsOpen ) // 没打开,则尝试打开 { hr = bb->Open(); if ( FAILED(hr) ) { printf("Kinect Open Failed!\n"); goto endstop; } printf("Kinect opened! But it need sometime to work!\n"); // 这里一定要多等会,否则下面的判断都是错误的 printf("Wait For 3000 ms...\n"); Sleep(3000); } bIsOpen = 0; bb->get_IsOpen(&bIsOpen); // 是否已经打开 printf("bIsOpen: %d\n", bIsOpen); BOOLEAN bAvaliable = 0; bb->get_IsAvailable(&bAvaliable); // 是否可用 printf("bAvaliable: %d\n", bAvaliable); DWORD dwCapability = 0; bb->get_KinectCapabilities(&dwCapability); // 获取容量 printf("dwCapability: %d\n", dwCapability); TCHAR bbuid[256] = { 0 }; bb->get_UniqueKinectId(256, bbuid); // 获取唯一ID printf("UID: %s\n",bbuid); // 音频数据获取 IAudioSource* audios = nullptr; UINT nAudioCount = 0; hr = bb->get_AudioSource(&audios); if ( FAILED(hr) ) { printf("Audio Source get failed!\n"); goto endclose; } IAudioBeam* audiobm = nullptr; IAudioBeamList* audiobml = nullptr; audios->get_AudioBeams(&audiobml); audiobml->OpenAudioBeam(0, &audiobm); // 目前只支持第一个 IStream* stm = nullptr; audiobm->OpenInputStream(&stm); audios->Release(); audiobm->Release(); float fAngle = 0.0f; float fAngleConfidence = 0.0f; ULONG lRead = 0; const ULONG lBufferSize =3200; float* fDataArr = new float[lBufferSize]; while (true) { fAngle = 0.0f; fAngleConfidence = 0.0f; audiobm->get_BeamAngle(&fAngle); // 获取音频的角度,[ -0.872665f, 0.8726665f ] audiobm->get_BeamAngleConfidence(&fAngleConfidence); // 获取音频的可信度(0 - 1) if ( fAngleConfidence > 0.5f ) printf("Angle: %3.2f (%1.2f)\n", (fAngle)*180.0f/static_cast<float>(M_PI), fAngleConfidence); // audio data lRead = 0; memset(fDataArr, 0, lBufferSize); stm->Read(fDataArr, lBufferSize, &lRead); if ( lRead > 0 ) { printf("Audio Buffer: %d\n", lRead); } Sleep(200); } endclose: bb->Close(); endstop: system("pause"); return 0; }
完毕