本文资源下载:(91条消息) dxgi桌面屏幕录像(windows屏幕录像,硬件编码,声音捕获,音视频同步)-编解码文档类资源-CSDN文库https://download.csdn.net/download/xjb2006/85109025
(91条消息) DXGI抓屏优化扩展:GPU硬件编码保存文件即录像为MP4和FLV,外加麦克风+计算机声音_周星星的星的博客-CSDN博客_dxgi 录屏https://blog.csdn.net/xjb2006/article/details/117849944由于篇幅有限,分为4篇发表:
1、SDK接口一览:
2、声音采集部分:
3、屏幕捕获部分:
4、编码,录像部分:
距离上篇文章已经过了快1年了,才有时间把正式DEMO传上来,直接上个截图看看吧:
该DEMO演示了win10屏幕录像的核心功能,包含音源选择(支持麦克风,计算机声音和2者混合),屏幕选择(主副屏选择),鼠标,帧率,码率,硬件编码,实时预览,双录制(同时录制为flv,mp4)等基本功能。为了扩展需要,程序核心模块做成DLL动态库,可以多语言扩展,适用于C++,C#,JAVA,VB,Python等等其他语言。
一、音源选择+声音采集:
运用的技术为directshow(directsound)技术和Core Audio APIs(也叫WSAAPI,vista,win7以后适用),也用了ACM采样率转换(也可以用ffmpeg,看自己喜好),Wave API录音,也不容易,看似简单的声音捕获用到这么多技术。贴代码:
麦克风声音捕获 :
#include "stdafx.h"
#include "WaveRecorder.h"
//
// Construction/Destruction
//
//
//函数功能:
//构造函数,初始化工作
//参数说明:
//
//返回值:
//
CWaveRecorder::CWaveRecorder()
{
m_hRecord=NULL;
//m_Format.cbSize =0; //
//m_Format.wFormatTag =WAVE_FORMAT_PCM; //最常用的音频格式
//m_Format.nAvgBytesPerSec=8000; //平均数据传输率Bps: = nSamplesPerSec*nBlockAlign
//m_Format.nBlockAlign =1; //wFormatTag格式下最小的原子单位(byte)
//m_Format.nChannels =1; //音频数据通道
//m_Format.nSamplesPerSec =8000; //音频采样率8000hz
//m_Format.wBitsPerSample= 8; //每次采样数据的位数bit
//
//初始化源声音格式
m_Format.cbSize =0;
m_Format.wFormatTag = WAVE_FORMAT_PCM;
m_Format.nChannels = 2;
m_Format.wBitsPerSample= 16;
m_Format.nSamplesPerSec = 44100;
m_Format.nBlockAlign = m_Format.nChannels * m_Format.wBitsPerSample / 8;
m_Format.nAvgBytesPerSec=m_Format.nSamplesPerSec * m_Format.nBlockAlign;
m_bRecording=false;
//BUFFER_NUM个音频数据缓冲
for(int i=0;ilpData;
}
return NULL;
}
//
//函数功能:
//重新设置声音缓冲区
//参数说明:
//pHdr: [in][out] 声音缓冲区结构体指针
//返回值:
//
void CWaveRecorder::SetBuffer(LPWAVEHDR pHdr)
{
MMRESULT mmReturn = 0;
if(m_bRecording)
{
mmReturn = ::waveInPrepareHeader(m_hRecord,pHdr, sizeof(WAVEHDR));
if(mmReturn)
{
return ;
}
else
{
mmReturn = ::waveInAddBuffer(m_hRecord, pHdr, sizeof(WAVEHDR));
}
}
}
//
//函数功能:
//增加声音输入缓冲区
//参数说明:
//pHdr:
//返回值:
//若成功返回TRUE,否则返回FALSE
BOOL CWaveRecorder::AddInputBuffer(LPWAVEHDR pHdr,WAVEFORMATEX *format)
{
MMRESULT mmReturn = 0;
ZeroMemory(pHdr, sizeof(WAVEHDR));
int nSize=format->nBlockAlign*m_BufferSize;
char* pBuf=new char[nSize];
if(pHdr->lpData)
delete pHdr->lpData;
pHdr->lpData = pBuf;
pHdr->dwBufferLength = nSize;
//为声音输入设备准备一个声音缓冲
mmReturn = ::waveInPrepareHeader(m_hRecord,pHdr, sizeof(WAVEHDR)); //m_hRecord为声音输入设备的句柄
if(mmReturn)
{
return false;
}
//发送声音缓冲区给数据输入设备
mmReturn = ::waveInAddBuffer(m_hRecord, pHdr, sizeof(WAVEHDR));
if(mmReturn)
{
return false;
}
return true;
}
计算机声音采集:这里有个问题,core audio api(WSAAPI)采集时 ,只能采集声卡非静音的情况,如果声卡静音,是没有数据的。我这里解决的方法是不停向声卡播放声音静音数据,这样既不会发生采集不到数据,又不会影响声音数据。如果您有更好的办法,请告诉我,谢谢
#include "stdafx.h"
#ifdef _WIN7
#include "PlaybackAudioCapture.h"
//#include "ClassRegister.h"
//#include "TimeCostDebug.h"
#include "Admin.h"
#include
#include
#include
#include
extern CAdmin *g_pMain;
CPlaybackCapture *m_pCap=0;
#define AUDIO_CAPTURE_CLASS _T("audio_cpature_message_class")
enum CAPTURE_STATUS
{
CAPTURE_START,
CAPTURE_STOP,
CAPTURE_ERROR
};
#define WM_CAPTUE_STATUS WM_USER+100
#define WM_WAVE_FORMAT WM_USER+101
#define WM_CAPTURE_DATA WM_USER+102
CPlaybackCaptureImpl /
struct capture_thread_data
{
HANDLE hEventStarted;
HANDLE hEventStop;
HWND hWndMessage;
IMMDevice* pDevice;
};
class CPlaybackCaptureImpl
{
public:
CPlaybackCaptureImpl();
~CPlaybackCaptureImpl();
BOOL Initialize(IPlaybackCaptureEvent* pHandler);
VOID Destroy();
BOOL Start();
VOID Stop();
BOOL IsInited() const;
BOOL IsCapturing() const;
IPlaybackCaptureEvent* GetEventHandler() const { return m_pEventHandler;}
VOID OnThreadEnd();
private:
IMMDevice* GetDefaultDevice();
private:
HWND m_hWndMessage;
HANDLE m_hEventStarted;
HANDLE m_hEventStop;
IMMDevice* m_pDevice;
HANDLE m_hThreadCapture;
//static CClassRegister m_sClassRegister;
BOOL m_bInited;
IPlaybackCaptureEvent* m_pEventHandler;
};
LRESULT CALLBACK AudioCaptureMessageProc(HWND hWnd, UINT uMsg,WPARAM wParam, LPARAM lParam)
{
BOOL bHandled(FALSE);
LRESULT lRet(0);
IPlaybackCaptureEvent* pEventHandler = NULL;
CPlaybackCaptureImpl* pThis = (CPlaybackCaptureImpl*)GetWindowLong(hWnd, GWL_USERDATA);
if(pThis != NULL)
{
pEventHandler = pThis->GetEventHandler();
}
switch(uMsg)
{
case WM_NCCREATE:
{
CREATESTRUCT* pSC = (CREATESTRUCT*)lParam;
if(pSC != NULL)
{
SetWindowLong(hWnd, GWL_USERDATA, (LONG)pSC->lpCreateParams);
}
}
break;
case WM_CAPTUE_STATUS:
{
if(wParam == CAPTURE_START)
{
if(pEventHandler != NULL) pEventHandler->OnCatpureStart(lParam);
}
else if(wParam == CAPTURE_STOP)
{
if(pEventHandler != NULL) pEventHandler->OnCaptureStop();
if(pThis != NULL) pThis->OnThreadEnd();
}
bHandled = TRUE;
}
break;
case WM_WAVE_FORMAT:
{
if(pEventHandler != NULL)
{
pEventHandler->OnAdjustCaptureFormat((WAVEFORMATEX*)lParam);
}
bHandled = TRUE;
}
break;
case WM_CAPTURE_DATA:
{
if(pEventHandler != NULL)
{
pEventHandler->OnCatpureData((LPBYTE)lParam, wParam);
}
bHandled = TRUE;
}
break;
default:
break;
}
if(!bHandled)
{
lRet = ::DefWindowProc(hWnd, uMsg, wParam, lParam);
}
return lRet;
}
//CClassRegister CPlaybackCaptureImpl::m_sClassRegister(AUDIO_CAPTURE_CLASS, AudioCaptureMessageProc);
//static VOID NotifyStatus(HWND hWndMesasge, CAPTURE_STATUS eStatus, DWORD dwUserData = 0)
//{
// ::SendMessage(hWndMesasge, WM_CAPTUE_STATUS, (WPARAM)eStatus, dwUserData);
//}
//
//static VOID NotifyWaveFormat(HWND hWndMessage, WAVEFORMATEX* pFormat)
//{
// ::SendMessage(hWndMessage, WM_WAVE_FORMAT, 0, (LPARAM)(WAVEFORMATEX*)pFormat);
//}
//
//static VOID NotifyData(HWND hWndMessage, LPBYTE pData, INT nDataLen)
//{
// ::SendMessage(hWndMessage, WM_CAPTURE_DATA, nDataLen, (LPARAM)pData);
//}
BOOL AdjustFormatTo16Bits(WAVEFORMATEX *pwfx)
{
BOOL bRet(FALSE);
if(pwfx->wFormatTag == WAVE_FORMAT_IEEE_FLOAT)
{
pwfx->wFormatTag = WAVE_FORMAT_PCM;
pwfx->wBitsPerSample = 16;
pwfx->nBlockAlign = pwfx->nChannels * pwfx->wBitsPerSample / 8;
pwfx->nAvgBytesPerSec = pwfx->nBlockAlign * pwfx->nSamplesPerSec;
bRet = TRUE;
}
else if(pwfx->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
{
PWAVEFORMATEXTENSIBLE pEx = reinterpret_cast(pwfx);
if (IsEqualGUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, pEx->SubFormat))
{
pEx->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
pEx->Samples.wValidBitsPerSample = 16;
pwfx->wBitsPerSample = 16;
pwfx->nBlockAlign = pwfx->nChannels * pwfx->wBitsPerSample / 8;
pwfx->nAvgBytesPerSec = pwfx->nBlockAlign * pwfx->nSamplesPerSec;
bRet = TRUE;
}
}
return bRet;
}
UINT CaptureAudio(HWND hWndMessage, IMMDevice* pDevice, HANDLE hEventStarted, HANDLE hEventStop)
{
HRESULT hr;
IAudioClient *pAudioClient = NULL;
WAVEFORMATEX *pwfx = NULL;
REFERENCE_TIME hnsDefaultDevicePeriod(0);
HANDLE hTimerWakeUp = NULL;
IAudioCaptureClient *pAudioCaptureClient = NULL;
DWORD nTaskIndex = 0;
HANDLE hTask = NULL;
BYTE ABuf[10*1024];
int nALen=0;
BOOL bStarted(FALSE);
do
{
hr = pDevice->Activate(__uuidof(IAudioClient), CLSCTX_ALL, NULL, (void**)&pAudioClient);
if(FAILED(hr)) break;
hr = pAudioClient->GetDevicePeriod(&hnsDefaultDevicePeriod, NULL);
if(FAILED(hr)) break;
hr = pAudioClient->GetMixFormat(&pwfx);
if (FAILED(hr)) break;
if(!AdjustFormatTo16Bits(pwfx)) break;
WAVEFORMATEX format;
format.cbSize=0;
format.wFormatTag=1;
format.nChannels = 2;
format.wBitsPerSample=16;
format.nSamplesPerSec=44100;
format.nBlockAlign = format.nChannels * (format.wBitsPerSample / 8);
format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
pwfx->wFormatTag=1;
pwfx->cbSize=0;
BOOL bb=g_pMain->m_acm.BeginConvert(pwfx,&format);
hTimerWakeUp = CreateWaitableTimer(NULL, FALSE, NULL);
if(hTimerWakeUp == NULL) break;
SetEvent(hEventStarted);
//NotifyWaveFormat(hWndMessage, pwfx);
hr = pAudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_LOOPBACK, 0, 0, pwfx, 0);
if(FAILED(hr)) break;
hr = pAudioClient->GetService(__uuidof(IAudioCaptureClient), (void**)&pAudioCaptureClient);
if(FAILED(hr)) break;
hTask = AvSetMmThreadCharacteristics(L"Capture", &nTaskIndex);
if (NULL == hTask) break;
LARGE_INTEGER liFirstFire;
liFirstFire.QuadPart = -hnsDefaultDevicePeriod / 2; // negative means relative time
LONG lTimeBetweenFires = (LONG)hnsDefaultDevicePeriod / 2 / (10 * 1000); // convert to milliseconds
BOOL bOK = SetWaitableTimer(hTimerWakeUp,&liFirstFire,lTimeBetweenFires,NULL, NULL, FALSE);
if(!bOK) break;
hr = pAudioClient->Start();
if(FAILED(hr)) break;
//NotifyStatus(hWndMessage, CAPTURE_START, lTimeBetweenFires);
bStarted = TRUE;
HANDLE waitArray[2] = { hEventStop, hTimerWakeUp };
DWORD dwWaitResult;
UINT32 nNextPacketSize(0);
BYTE *pData = NULL;
UINT32 nNumFramesToRead;
DWORD dwFlags;
while(TRUE)
{
dwWaitResult = WaitForMultipleObjects(sizeof(waitArray)/sizeof(waitArray[0]), waitArray, FALSE, INFINITE);
if(WAIT_OBJECT_0 == dwWaitResult) break;
if (WAIT_OBJECT_0 + 1 != dwWaitResult)
{
//NotifyStatus(hWndMessage, CAPTURE_ERROR);
break;
}
hr = pAudioCaptureClient->GetNextPacketSize(&nNextPacketSize);
if(FAILED(hr))
{
//NotifyStatus(hWndMessage, CAPTURE_ERROR);
break;
}
if (nNextPacketSize == 0) continue;
hr = pAudioCaptureClient->GetBuffer(
&pData,
&nNumFramesToRead,
&dwFlags,
NULL,
NULL
);
if(FAILED(hr))
{
//NotifyStatus(hWndMessage, CAPTURE_ERROR);
break;
}
if (0 != nNumFramesToRead)
{
memcpy(ABuf+nALen,pData,nNumFramesToRead * pwfx->nBlockAlign);
nALen+=nNumFramesToRead * pwfx->nBlockAlign;
if(nALen>=8*1024)
{
BYTE *pDes=0;
DWORD dwLen=0;
BOOL bOK=g_pMain->m_acm.ConvertSample(ABuf,nALen,(void**)&pDes,&dwLen);
if(bOK)
{
g_pMain->AddBuffer_Teacher((BYTE*)pDes,dwLen);
}
nALen=0;
}
}
pAudioCaptureClient->ReleaseBuffer(nNumFramesToRead);
}
}while(FALSE);
g_pMain->m_acm.Close();
if(hTask != NULL)
{
AvRevertMmThreadCharacteristics(hTask);
hTask = NULL;
}
if(pAudioCaptureClient != NULL)
{
pAudioCaptureClient->Release();
pAudioCaptureClient = NULL;
}
if(pwfx != NULL)
{
CoTaskMemFree(pwfx);
pwfx = NULL;
}
if(hTimerWakeUp != NULL)
{
CancelWaitableTimer(hTimerWakeUp);
CloseHandle(hTimerWakeUp);
hTimerWakeUp = NULL;
}
if(pAudioClient != NULL)
{
if(bStarted)
{
pAudioClient->Stop();
//NotifyStatus(hWndMessage, CAPTURE_STOP);
}
pAudioClient->Release();
pAudioClient = NULL;
}
return 0;
}
UINT __stdcall CaptureTheadProc(LPVOID param)
{
CoInitialize(NULL);
capture_thread_data* pData = (capture_thread_data*)param;
HWND hWndMessage = pData->hWndMessage;
HANDLE hEventStop = pData->hEventStop;
IMMDevice* pDevice = pData->pDevice;
HANDLE hEventStarted = pData->hEventStarted;
UINT nRet = CaptureAudio(hWndMessage, pDevice, hEventStarted, hEventStop);
CoUninitialize();
return nRet;
}
CPlaybackCaptureImpl::CPlaybackCaptureImpl()
:m_hWndMessage(NULL), m_bInited(FALSE), m_pDevice(NULL),
m_pEventHandler(NULL), m_hEventStarted(NULL), m_hEventStop(NULL)
{
m_hThreadCapture=0;
}
CPlaybackCaptureImpl::~CPlaybackCaptureImpl()
{
if(m_bInited) Destroy();
}
VOID CPlaybackCaptureImpl::OnThreadEnd()
{
if(m_hThreadCapture != NULL)
{
CloseHandle(m_hThreadCapture);
m_hThreadCapture = NULL;
}
}
IMMDevice* CPlaybackCaptureImpl::GetDefaultDevice()
{
IMMDevice* pDevice = NULL;
IMMDeviceEnumerator *pMMDeviceEnumerator = NULL;
HRESULT hr = CoCreateInstance(
__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL,
__uuidof(IMMDeviceEnumerator),
(void**)&pMMDeviceEnumerator);
if(FAILED(hr)) return NULL;
hr = pMMDeviceEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &pDevice);
pMMDeviceEnumerator->Release();
return pDevice;
}
BOOL CPlaybackCaptureImpl::Initialize(IPlaybackCaptureEvent* pHandler)
{
if(m_bInited) return TRUE;
m_pEventHandler = pHandler;
do
{
//if(!m_sClassRegister.IsRegistered())
//{
// m_sClassRegister.Register();
//}
//m_hWndMessage = CreateWindow(AUDIO_CAPTURE_CLASS, NULL, WS_POPUP,
//0, 0, 0, 0, HWND_MESSAGE, NULL, g_hInstance, this);
//if(m_hWndMessage == NULL) break;
m_pDevice = GetDefaultDevice();
if(m_pDevice == NULL) break;
m_hEventStop = CreateEvent(NULL, TRUE, FALSE, NULL);
if(m_hEventStop == NULL) break;
m_hEventStarted = CreateEvent(NULL, TRUE, FALSE, NULL);
if(m_hEventStarted == NULL) break;
m_bInited = TRUE;
}while(FALSE);
if(!m_bInited)
{
Destroy();
}
return m_bInited;
}
VOID CPlaybackCaptureImpl::Destroy()
{
if(m_hWndMessage != NULL
&& ::IsWindow(m_hWndMessage))
{
DestroyWindow(m_hWndMessage);
}
m_hWndMessage = NULL;
if(m_pDevice != NULL)
{
m_pDevice->Release();
m_pDevice = NULL;
}
if(m_hEventStop != NULL)
{
CloseHandle(m_hEventStop);
m_hEventStop = NULL;
}
if(m_hEventStarted != NULL)
{
CloseHandle(m_hEventStarted);
m_hEventStarted = NULL;
}
m_bInited = FALSE;
}
BOOL CPlaybackCaptureImpl::IsInited() const
{
return m_bInited;
}
BOOL CPlaybackCaptureImpl::IsCapturing() const
{
return m_hThreadCapture != NULL;
}
BOOL CPlaybackCaptureImpl::Start()
{
if(!m_bInited) return FALSE;
if(m_hThreadCapture != NULL) return TRUE;
capture_thread_data data;
data.hEventStop = m_hEventStop;
data.hWndMessage = m_hWndMessage;
data.pDevice = m_pDevice;
data.hEventStarted = m_hEventStarted;
m_hThreadCapture = (HANDLE)_beginthreadex(NULL, 0, &CaptureTheadProc, &data, 0, NULL);
if(m_hThreadCapture == NULL) return FALSE;
SetThreadPriority(m_hThreadCapture, THREAD_PRIORITY_HIGHEST);
HANDLE arWaits[2] = {m_hEventStarted, m_hThreadCapture};
DWORD dwWaitResult = WaitForMultipleObjects(sizeof(arWaits)/sizeof(arWaits[0]), arWaits, FALSE, INFINITE);
if(dwWaitResult != WAIT_OBJECT_0)
{
Stop();
return FALSE;
}
return TRUE;
}
VOID CPlaybackCaptureImpl::Stop()
{
if(!m_bInited) return;
if(m_hEventStop != NULL
&& m_hThreadCapture != NULL)
{
SetEvent(m_hEventStop);
OnThreadEnd();
}
}
CPlaybackCaptureImpl /
CPlaybackCapture
CPlaybackCapture::CPlaybackCapture()
{
m_pImpl = new CPlaybackCaptureImpl();
}
CPlaybackCapture::~CPlaybackCapture()
{
if(m_pImpl != NULL)
{
delete m_pImpl;
}
}
BOOL CPlaybackCapture::Initialize(IPlaybackCaptureEvent* pHandler)
{
if(m_pImpl != NULL)
return m_pImpl->Initialize(pHandler);
else
return FALSE;
}
VOID CPlaybackCapture::Destroy()
{
if(m_pImpl != NULL)
m_pImpl->Destroy();
}
BOOL CPlaybackCapture::Start()
{
//if(!m_pwav)
//{
// bool ret;
// m_pwav=new WavOutFile("1.wav",48000,16,2,ret);
//}
if(m_pImpl != NULL)
{
return m_pImpl->Start();
}
else
return FALSE;
}
VOID CPlaybackCapture::Stop()
{
//if(m_pwav)
//{
// m_pwav->close();
// delete m_pwav;
// m_pwav=0;
//}
if(m_pImpl != NULL)
m_pImpl->Stop();
}
BOOL CPlaybackCapture::IsInited() const
{
if(m_pImpl != NULL)
return m_pImpl->IsInited();
else
return FALSE;
}
BOOL CPlaybackCapture::IsCapturing() const
{
if(m_pImpl != NULL)
return m_pImpl->IsCapturing();
else
return FALSE;
}
CPlaybackCapture
#endif
虚拟声音播放(向声卡送静音数据):
void CAdmin::AudioTimePlay()
{
::CoInitialize(0);
m_pOut_DS=0;
char XUNIAudio[30*1024];//虚拟声音
memset(XUNIAudio,0,sizeof(XUNIAudio));
InitOutPutAudio(0);
const int nTime=30;//160519改成30,原来是40
DWORD dwTime1=::timeGetTime();
while (m_bAudioTimePlay) //repeatedly loop
{
if(::timeGetTime()-dwTime1>=nTime)//40ms(25帧)
{
int nLen= 44100*2/(1000/nTime);
m_pOut_DS->AddBuffer(XUNIAudio,nLen);
dwTime1=::timeGetTime();
}
Sleep(5);
}
if(m_pOut_DS)
{
m_pOut_DS->StopGraph();
delete m_pOut_DS;
m_pOut_DS=0;
}
}
void CAdmin::CloseAudioTimePlayThread()
{
m_bAudioTimePlay=false;
if (m_hAudioTimePlayThread != NULL)
{
WaitForSingleObject(m_hAudioTimePlayThread, INFINITE);
m_hAudioTimePlayThread = NULL;
}
if(m_pOut_DS)
{
m_pOut_DS->StopGraph();
delete m_pOut_DS;
m_pOut_DS=0;
}
}
void CAdmin::InitOutPutAudio(BYTE* pAudioFormat)
{
WAVEFORMATEX format;
if(pAudioFormat)
memcpy(&format,pAudioFormat,sizeof(WAVEFORMATEX));
else
{
format.nChannels=2;
format.wBitsPerSample=16;
format.nSamplesPerSec=44100;
}
回放
if(m_pOut_DS==0)
{
m_pOut_DS=new COutPut_DirectShow();
m_pOut_DS->CreateGraph();//为什么要加这里,否则不能点播wav文件??????????????????
format.cbSize=0;
format.wFormatTag=1;
format.nBlockAlign = format.nChannels * (format.wBitsPerSample / 8);
format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
m_pOut_DS->SetupMediaType((char*)&format,sizeof(WAVEFORMATEX));
m_pOut_DS->CompleteAudioReceivingGraph();
}
}
好吧,今天就到此为止,贴个DEMO链接
(91条消息) dxgi桌面屏幕录像(windows屏幕录像,硬件编码,声音捕获,音视频同步)-编解码文档类资源-CSDN文库https://download.csdn.net/download/xjb2006/85109025
DXGI屏幕录像演示软件(QQ35744025)