为捕获过滤器添加音频功能

2023-12-06

我正在尝试向捕获源过滤器添加音频功能,以便制作带有音频的虚拟摄像机。开始于TMH's and rdp的代码我用另一个引脚扩展了它,称为“音频”:

CUnknown * WINAPI CVCam::CreateInstance(LPUNKNOWN lpunk, HRESULT *phr)
{
    ASSERT(phr);
    CUnknown *punk = new CVCam(lpunk, phr);
    return punk;
}

CVCam::CVCam(LPUNKNOWN lpunk, HRESULT *phr) : CSource(LPCSTR(FILTER_NAME), lpunk, CLSID_VirtualCam)
{
    ASSERT(phr);
    CAutoLock cAutoLock(&m_cStateLock);
    m_paStreams = (CSourceStream **) new CVCamStream*[2];
    m_paStreams[0] = new CVCamStream(phr, this, L"Video");
    m_paStreams[1] = new CVAudioStream(phr, this, L"Audio");
}

HRESULT CVCam::QueryInterface(REFIID riid, void **ppv)
{
    if (riid == _uuidof(IAMStreamConfig) || riid == _uuidof(IKsPropertySet))
    {
        HRESULT hr;
        hr = m_paStreams[0]->QueryInterface(riid, ppv);
        if (hr != S_OK) return hr;
        hr = m_paStreams[1]->QueryInterface(riid, ppv);
        if (hr != S_OK) return hr;
    }
    else return CSource::QueryInterface(riid, ppv);

    return S_OK;
}

CVAudioStream::CVAudioStream(HRESULT *phr, CVCam *pParent, LPCWSTR pPinName) : CSourceStream(LPCSTR(pPinName), phr, pParent, pPinName), m_pParent(pParent)
{
    GetMediaType(0, &m_mt);
}

CVAudioStream::~CVAudioStream()
{
}

HRESULT CVAudioStream::QueryInterface(REFIID riid, void **ppv)
{
    if (riid == _uuidof(IAMStreamConfig)) *ppv = (IAMStreamConfig*)this;
    else if (riid == _uuidof(IKsPropertySet)) *ppv = (IKsPropertySet*)this;
    else if (riid == _uuidof(IAMBufferNegotiation)) *ppv = (IAMBufferNegotiation*)this;
    else return CSourceStream::QueryInterface(riid, ppv);

    AddRef();
    return S_OK;
}

HRESULT CVAudioStream::FillBuffer(IMediaSample *pms)
{
    // fill buffer with Windows audio samples
    return NOERROR;
}

STDMETHODIMP CVAudioStream::Notify(IBaseFilter * pSender, Quality q)
{
    return E_NOTIMPL;
}

HRESULT CVAudioStream::SetMediaType(const CMediaType *pmt)
{
    HRESULT hr = CSourceStream::SetMediaType(pmt);
    return hr;
}

HRESULT setupPwfex(WAVEFORMATEX *pwfex, AM_MEDIA_TYPE *pmt) {
    pwfex->wFormatTag = WAVE_FORMAT_PCM;
    pwfex->cbSize = 0;              
    pwfex->nChannels = 2;

    HRESULT hr;
    pwfex->nSamplesPerSec = 11025;
    pwfex->wBitsPerSample = 16;       
    pwfex->nBlockAlign = (WORD)((pwfex->wBitsPerSample * pwfex->nChannels) / 8);
    pwfex->nAvgBytesPerSec = pwfex->nSamplesPerSec * pwfex->nBlockAlign;
    hr = ::CreateAudioMediaType(pwfex, pmt, FALSE);
    return hr;
}

/*HRESULT CVAudioStream::setAsNormal(CMediaType *pmt) 
{
    WAVEFORMATEX *pwfex;
    pwfex = (WAVEFORMATEX *)pmt->AllocFormatBuffer(sizeof(WAVEFORMATEX));
    ZeroMemory(pwfex, sizeof(WAVEFORMATEX));
    if (NULL == pwfex) return E_OUTOFMEMORY;
    return setupPwfex(pwfex, pmt);
}*/

HRESULT CVAudioStream::GetMediaType(int iPosition, CMediaType *pmt)
{
    if (iPosition < 0) return E_INVALIDARG;
    if (iPosition > 0) return VFW_S_NO_MORE_ITEMS;

    if (iPosition == 0)
    {
        *pmt = m_mt;
        return S_OK;
    }

    WAVEFORMATEX *pwfex = (WAVEFORMATEX *)pmt->AllocFormatBuffer(sizeof(WAVEFORMATEX));
    setupPwfex(pwfex, pmt);
    return S_OK;
}

HRESULT CVAudioStream::CheckMediaType(const CMediaType *pMediaType)
{
    int cbFormat = pMediaType->cbFormat;
    if (*pMediaType != m_mt) return E_INVALIDARG;
    return S_OK;
}

const int WaveBufferChunkSize = 16 * 1024;

HRESULT CVAudioStream::DecideBufferSize(IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *pProperties)
{
    CheckPointer(pAlloc, E_POINTER);
    CheckPointer(pProperties, E_POINTER);

    WAVEFORMATEX *pwfexCurrent = (WAVEFORMATEX*)m_mt.Format();

    pProperties->cBuffers = 1;
    pProperties->cbBuffer = expectedMaxBufferSize;

    ALLOCATOR_PROPERTIES Actual;
    HRESULT hr = pAlloc->SetProperties(pProperties, &Actual);
    if (FAILED(hr)) return hr;

    if (Actual.cbBuffer < pProperties->cbBuffer) return E_FAIL;
    return NOERROR; 
}

HRESULT CVAudioStream::OnThreadCreate()
{
    //GetMediaType(0, &m_mt); 

    //HRESULT hr = LoopbackCaptureSetup();
    //if (FAILED(hr)) return hr;
    return NOERROR;
} 

HRESULT STDMETHODCALLTYPE CVAudioStream::SetFormat(AM_MEDIA_TYPE *pmt)
{
    if (!pmt) return S_OK;
    if (CheckMediaType((CMediaType *)pmt) != S_OK) return E_FAIL; 
    m_mt = *pmt;

    IPin* pin;
    ConnectedTo(&pin);
    if (pin)
    {
        IFilterGraph *pGraph = m_pParent->GetGraph();
        pGraph->Reconnect(this);
    }

    return S_OK;
}

HRESULT STDMETHODCALLTYPE CVAudioStream::GetFormat(AM_MEDIA_TYPE **ppmt)
{
    *ppmt = CreateMediaType(&m_mt);
    return S_OK;
}

HRESULT STDMETHODCALLTYPE CVAudioStream::GetNumberOfCapabilities(int *piCount, int *piSize)
{
    *piCount = 1;
    *piSize = sizeof(AUDIO_STREAM_CONFIG_CAPS);
    return S_OK;
}

HRESULT STDMETHODCALLTYPE CVAudioStream::GetStreamCaps(int iIndex, AM_MEDIA_TYPE **pmt, BYTE *pSCC)
{
    if (iIndex < 0) return E_INVALIDARG;
    if (iIndex > 0) return S_FALSE;
    if (pSCC == NULL) return E_POINTER;

    *pmt = CreateMediaType(&m_mt);
    if (*pmt == NULL) return E_OUTOFMEMORY;

    DECLARE_PTR(WAVEFORMATEX, pAudioFormat, (*pmt)->pbFormat);
    AM_MEDIA_TYPE * pm = *pmt;
    setupPwfex(pAudioFormat, pm);

    AUDIO_STREAM_CONFIG_CAPS* pASCC = (AUDIO_STREAM_CONFIG_CAPS*)pSCC;
    ZeroMemory(pSCC, sizeof(AUDIO_STREAM_CONFIG_CAPS));

    pASCC->guid = MEDIATYPE_Audio;
    pASCC->MaximumChannels = pAudioFormat->nChannels;
    pASCC->MinimumChannels = pAudioFormat->nChannels;
    pASCC->ChannelsGranularity = 1; // doesn't matter
    pASCC->MaximumSampleFrequency = pAudioFormat->nSamplesPerSec;
    pASCC->MinimumSampleFrequency = pAudioFormat->nSamplesPerSec;
    pASCC->SampleFrequencyGranularity = 11025; // doesn't matter
    pASCC->MaximumBitsPerSample = pAudioFormat->wBitsPerSample;
    pASCC->MinimumBitsPerSample = pAudioFormat->wBitsPerSample;
    pASCC->BitsPerSampleGranularity = 16; // doesn't matter

    return S_OK;
}

HRESULT CVAudioStream::Set(REFGUID guidPropSet, DWORD dwID, void *pInstanceData, DWORD cbInstanceData, void *pPropData, DWORD cbPropData)
{
    return E_NOTIMPL;
}

HRESULT CVAudioStream::Get(
    REFGUID guidPropSet,
    DWORD dwPropID,     
    void *pInstanceData,
    DWORD cbInstanceData,
    void *pPropData,     
    DWORD cbPropData,    
    DWORD *pcbReturned   
)
{
    if (guidPropSet != AMPROPSETID_Pin)             return E_PROP_SET_UNSUPPORTED;
    if (dwPropID != AMPROPERTY_PIN_CATEGORY)        return E_PROP_ID_UNSUPPORTED;
    if (pPropData == NULL && pcbReturned == NULL)   return E_POINTER;

    if (pcbReturned) *pcbReturned = sizeof(GUID);
    if (pPropData == NULL)          return S_OK; 
    if (cbPropData < sizeof(GUID))  return E_UNEXPECTED;

    *(GUID *)pPropData = PIN_CATEGORY_CAPTURE;
    return S_OK;
}

HRESULT CVAudioStream::QuerySupported(REFGUID guidPropSet, DWORD dwPropID, DWORD *pTypeSupport)
{
    if (guidPropSet != AMPROPSETID_Pin) return E_PROP_SET_UNSUPPORTED;
    if (dwPropID != AMPROPERTY_PIN_CATEGORY) return E_PROP_ID_UNSUPPORTED;
    if (pTypeSupport) *pTypeSupport = KSPROPERTY_SUPPORT_GET;
    return S_OK;
}

我的第一个问题是当我在 GraphStudioNext 中插入过滤器并打开其属性页面时。音频引脚显示以下(不正确)信息:

majorType = GUID_NULL
subType = GUID_NULL
formattype = GUID_NULL

当然,我无法将任何内容连接到该引脚,因为它无效。 我期待类似的事情MEDIATYPE_Audio因为我设置了它:

DEFINE_GUID(CLSID_VirtualCam, 0x8e14549a, 0xdb61, 0x4309, 0xaf, 0xa1, 0x35, 0x78, 0xe9, 0x27, 0xe9, 0x33);

const AMOVIESETUP_MEDIATYPE AMSMediaTypesVideo = 
{
    &MEDIATYPE_Video,
    &MEDIASUBTYPE_NULL
};

const AMOVIESETUP_MEDIATYPE AMSMediaTypesAudio =
{
    &MEDIATYPE_Audio,
    &MEDIASUBTYPE_NULL
};

const AMOVIESETUP_PIN AMSPinVCam[] =
{
    {
        L"Video",             // Pin string name
        FALSE,                 // Is it rendered
        TRUE,                  // Is it an output
        FALSE,                 // Can we have none
        FALSE,                 // Can we have many
        &CLSID_NULL,           // Connects to filter
        NULL,                  // Connects to pin
        1,                     // Number of types
        &AMSMediaTypesVideo      // Pin Media types
    },
    {
        L"Audio",             // Pin string name
        FALSE,                 // Is it rendered
        TRUE,                  // Is it an output
        FALSE,                 // Can we have none
        FALSE,                 // Can we have many
        &CLSID_NULL,           // Connects to filter
        NULL,                  // Connects to pin
        1,                     // Number of types
        &AMSMediaTypesAudio      // Pin Media types
    }
};

const AMOVIESETUP_FILTER AMSFilterVCam =
{
    &CLSID_VirtualCam,  // Filter CLSID
    FILTER_NAME,     // String name
    MERIT_DO_NOT_USE,      // Filter merit
    2,                     // Number pins
    AMSPinVCam             // Pin details
};

CFactoryTemplate g_Templates[] = 
{
    {
        FILTER_NAME,
        &CLSID_VirtualCam,
        CVCam::CreateInstance,
        NULL,
        &AMSFilterVCam
    },
};

int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]);

STDAPI RegisterFilters( BOOL bRegister )
{
    HRESULT hr = NOERROR;
    WCHAR achFileName[MAX_PATH];
    char achTemp[MAX_PATH];
    ASSERT(g_hInst != 0);

    if( 0 == GetModuleFileNameA(g_hInst, achTemp, sizeof(achTemp))) return AmHresultFromWin32(GetLastError());
    MultiByteToWideChar(CP_ACP, 0L, achTemp, lstrlenA(achTemp) + 1, achFileName, NUMELMS(achFileName));

    hr = CoInitialize(0);
    if(bRegister)
    {
        hr = AMovieSetupRegisterServer(CLSID_VirtualCam, FILTER_NAME, achFileName, L"Both", L"InprocServer32");
    }

    if( SUCCEEDED(hr) )
    {
        IFilterMapper2 *fm = 0;
        hr = CreateComObject( CLSID_FilterMapper2, IID_IFilterMapper2, fm );
        if( SUCCEEDED(hr) )
        {
            if(bRegister)
            {
                IMoniker *pMoniker = 0;
                REGFILTER2 rf2;
                rf2.dwVersion = 1;
                rf2.dwMerit = MERIT_DO_NOT_USE;
                rf2.cPins = 2;
                rf2.rgPins = AMSPinVCam;
                hr = fm->RegisterFilter(CLSID_VirtualCam, FILTER_NAME, &pMoniker, &CLSID_VideoInputDeviceCategory, NULL, &rf2);
            }
            else
            {
                hr = fm->UnregisterFilter(&CLSID_VideoInputDeviceCategory, 0, CLSID_VirtualCam);
            }
        }

      if(fm) fm->Release();
    }

    if( SUCCEEDED(hr) && !bRegister ) hr = AMovieSetupUnregisterServer( CLSID_VirtualCam );

    CoFreeUnusedLibraries();
    CoUninitialize();
    return hr;
}

第二个问题:还有一个“延迟”选项卡,但当我单击它时,GraphStudioNext 挂起foreverVS 调试器(附加到该进程)什么也没说。哪段代码控制这个选项卡?

UPDATE

解决了第一个问题:

HRESULT CVAudioStream::GetMediaType(int iPosition, CMediaType *pmt)
{
    if (iPosition < 0) return E_INVALIDARG;
    if (iPosition > 0) return VFW_S_NO_MORE_ITEMS;

    WAVEFORMATEX *pwfex = (WAVEFORMATEX *)pmt->AllocFormatBuffer(sizeof(WAVEFORMATEX));
    setupPwfex(pwfex, pmt);

    pmt->SetType(&MEDIATYPE_Audio);
    pmt->SetFormatType(&FORMAT_WaveFormatEx);
    pmt->SetTemporalCompression(FALSE);

    pmt->SetSubtype(&MEDIASUBTYPE_PCM);
    pmt->SetSampleSize(pwfex->nBlockAlign);

    return S_OK;
}

简而言之:微软并没有真正提供 API 来提供虚拟音频设备,因此它可以很好地被应用程序接受,就好像它是真正的音频捕获设备一样。

如果虚拟视频捕捉过滤器由于历史原因经常起作用,那么音频的情况却并非如此。实现音频设备的内核级驱动程序是添加应用程序可以识别的音频设备的方法。


显示延迟选项卡是因为您假装正在实施IAMBufferNegotiation界面:

if (riid == _uuidof(IAMBufferNegotiation)) *ppv = (IAMBufferNegotiation*)this;

实现可能不正确,从而导致某些意外行为(冻结、崩溃等)。

在同一过滤器上添加音频引脚是可能的,但如果您希望将流选为人工源,则可能不是最好的主意。一般来说,这是有意义的,但实际设备几乎不会像这样公开音频流。

长话短说,唯一可以像这样利用音频流的应用程序是您自己开发的应用程序:没有众所周知的应用程序尝试在视频源过滤器上定位音频引脚。为此,实施IAMStreamConfig尤其是IKsPropertySet在这样的引脚上是没有用的。

您将无法在“音频捕获源”类别下注册该过滤器,因为您注册了一个过滤器,并且该过滤器首先公开视频输出引脚,然后才有一些辅助音频。如果您的目标是通过 DirectShow 使用音频的应用程序(由于超出本问题范围的原因,这种情况已经相当罕见),您应该开发一个单独的源过滤器。当然,您可以让两个过滤器在幕后相互通信,以协作交付某些提要,但就 DirectShow 而言,过滤器通常显示为独立的。

...真实的网络摄像头也会暴露两个不同的过滤器,这就是为什么在 Skype 这样的应用程序中我们必须在视频和音频设备下选择两者。
是否应该创建两个完全不同的项目和过滤器:一个用于视频,一个用于音频?

真实和典型的相机:

enter image description here

由于“真正的”物理相机通常提供内核级驱动程序,因此它们在 DirectShow 中的存在是通过WDM 视频捕获滤波器它充当代理并枚举与您注册虚拟相机的视频捕获源相同类别下的相机驱动程序的“DirectShow 包装器”。

也就是说,这种设计使您能够在可用设备列表中混合真实和虚拟摄像机,基于 DirectShow 的应用程序在视频捕获时使用这些设备。这种方法有其局限性,我之前已经描述过,例如在这个问题以及引用的帖子虚拟 DirectShow 源的适用性.

由于DirectShow的后继者Media Foundation总体上反响不佳,而且Media Foundation既没有提供良好的向后兼容性,也没有提供视频捕获的可扩展性,因此包括微软自己的应用程序在内的众多应用程序仍然通过DirectShow使用视频捕获。反之亦然,那些研究 Windows 视频捕获 API 的人通常也对 DirectShow 感兴趣,而不是“当前”API,因为示例和相关信息、API 可扩展性、应用程序集成选项的可用性。

然而,音频的情况并非如此。在 DirectShow 开发停止时,DirectShow 音频捕获还不是一流的。 Windows Vista 引入了新的音频 APIWASAPIDirectShow 没有收到与新 API 的相应连接,无论是音频捕获还是播放。音频本身更简单,而且 WASAPI 功能强大且对开发人员友好,因此开发人员开始切换到新的 API 来执行音频相关任务。使用 DirectShow 进行音频捕获的应用程序要少得多,并且您的虚拟音频源实现可能会失败:对于通过 WASAPI 消费音频捕获的应用程序来说,您的设备将保持“不可见”状态。即使应用程序具有 Windows XP 的后备代码补丁以通过 DirectShow 进行音频捕获,在较新的操作系统中也很难让您松一口气。

继续阅读 StackOverflow 上的音频:

  • Directshow.net 未检测到 Windows 7 中的所有麦克风
  • 编写音频源过滤器以用作 Lync 麦克风
  • Windows 音频和视频捕获软件范例

此外,您不必为视频和音频过滤器创建单独的项目。您可以将它们混合在同一个项目中,它们可以只是单独注册的独立过滤器。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

为捕获过滤器添加音频功能 的相关文章

随机推荐

  • ggplotly 从单个方面删除数据

    我正在尝试为闪亮的应用程序创建一个绘图 我遇到了有关多面图的某些布局的问题 每当有四个小平面并且它们位于 2x2 网格中时 左下小平面不会显示任何数据 即使数据存在于ggplot数字 当 3x2 网格中有 6 个面且左下角再次被丢弃时 也会
  • 类型同义词与类型类约束是否可能?

    请随意更改标题 我只是经验不足 不知道到底发生了什么 所以 我正在松散地编写一个程序this 并写下了这个 与原文一样 type Row a a type Matrix a Row a 那里没什么特别的 然而 我发现自己编写了几个具有如下类
  • 在宏运行结束时打开 NUMLOCK

    什么代码的作用 我有一个代码可以在屏幕上移动鼠标 打印屏幕并将其粘贴到 Excel 中 Problem 由于某种原因 我的代码总是 绝对没有例外 在每次运行后关闭 NUMLOCK 键 到目前为止我尝试过的 我四处搜寻 发现了 SendKey
  • 查询为空 PHP 错误

    我正在尝试使用 MySQL 构建一个购物车 当我运行此代码时 我不断收到此错误 查询为空 请帮助我尝试了几种方法 例如将变量放入字符串中而不是连接它
  • HTML 链接不会转到外部网站

    我在构建网站时一直使用react js运行本地主机网站 当我尝试链接到外部网站 例如youtube 时 它最终会转到如下链接 http localhost 3000 www youtube com 当我试图去时 https www yout
  • 我可以立即打印循环中的每次迭代吗?

    我的部署服务器为每个新的数据库构建运行一个部署脚本 部分脚本会阻塞以等待另一个异步操作完成 阻塞代码如下所示 DECLARE i INT 0 DECLARE laststatus NVARCHAR MAX N WHILE i lt 5 BE
  • cusparse csrsv_analysis 的性能非常慢

    我编写了一个带有 LU 预处理的共轭梯度求解器 用于线性方程组 我使用了 Maxim Naumov 博士的papers以nvidia的研究社区为指导 残差更新步骤需要先求解下三角矩阵系统 然后求解上三角矩阵系统 分为两个阶段 分析阶段 利用
  • 选择工资高于其部门平均水平的每位员工

    我只有 1 个名为EMPLOYEE在我的数据库中包含以下 3 列 Employee Name Employee Salary Department ID 现在我必须选择每个工资高于其部门平均水平的员工 我怎么做 我遇到的主要问题是 当将每个
  • 错误:只能在初始化程序中访问静态成员,这是什么意思?

    我有这样的东西 我很难理解这个错误 为什么访问filterController这里给出这个错误 但是如果我移动当前的整个内容 它不会给出这个错误TextFormField在构建方法中创建 在注释 A 和 B 之间 整个搬家如何TextFor
  • 从 MVC 的 DependencyResolver 转换到 AutofacWebApiDependencyResolver - .Current 在哪里?

    我让 AutoFac 与 MVC4 一起正常工作 我正在尝试过渡到 Web API 2 以下是我设置 AutoFac 的方法 public class AutofacRegistrations public static void Regi
  • Python 2.5 上选择模块的问题

    我有一个 Python 2 5 中的应用程序 用于监听 beanstalk 队列 到目前为止 除了我新买的 MacBook Pro 之外 它在我测试过的所有机器上都运行良好 在那台计算机上 当我尝试运行它时 出现以下错误 Traceback
  • 如何从使用 LINQ to SQL 的方法返回查询结果

    这是我正在使用的代码 我对 LINQ 还是有点陌生 所以这是一项正在进行的工作 具体来说 我想从此查询中获取结果 大约 7 列字符串 整数和日期时间 并将它们返回到调用包含此 LINQ to SQL 查询的方法的方法 一个简单的代码示例将非
  • 使用 QuickBooks Online (QBO) Intuit 合作伙伴平台 (IPP) DevKit 查询具有未结余额的所有发票

    我正在尝试使用 IPP 查询所有具有未结余额的发票 但我不断收到 0 个结果 我在代码中做错了什么吗 这是我尝试使用应用的过滤执行的 C 代码片段 InvoiceQuery qboInvoiceQuery new InvoiceQuery
  • R:UseMethod(“tbl_vars”)中的错误

    所以我在 R Studio 中运行下面的代码并收到此错误 UseMethod tbl vars 中的错误 tbl vars 没有适用的方法 应用于 字符 类的对象 我不知道如何修复它 因为没有 tbl vars 函数 有人可以帮忙吗 for
  • 由于 MIME 类型不匹配而阻止资源(X-Content-Type-Options:nosniff)

    我正在使用 JavaScript 和 HTML 开发网页 一切正常 当我从 HTML 页面收到以下错误列表时 这很好 The resource from https raw githubusercontent com dataarts da
  • 运行 endpointscfg.py get_swagger_spec 时出错

    我正在尝试按照本指南使用 Google Cloud Endpoints 构建一个项目 App Engine 上的云端点框架快速入门 我陷入了生成 OpenAPI 配置文件的步骤 需要运行以下命令 尝试一 lib endpoints endp
  • 为什么armeabi-v7a与另一个模块的armeabi冲突?

    我的项目中有 2 个模块 模块 1 libs armeabi 模块 2 libs armeabi libs armeabi v7a 为了成功运行该应用程序 我必须删除armeabi v7a完全文件夹 否则 so库位于Module 1 arm
  • MySQL - 时间戳之间的平均差异,不包括周末和非工作时间

    我正在寻找能够平均时间戳之间差异的能力 排除周末和排除非工作时间 仅在 08 00 00 17 00 00 之间 我试图仅使用查询来实现此功能 但如果无法使用 MySQL 则可以回退到 PHP 函数 下面是我当前用来获取平均时间戳差异的函数
  • Delphi 中的 Jabber 有完整的库吗?

    我正在寻找 Delphi 的 Jabber 库 我可以看到一些基于 COM 的 jabber 组件 但我正在寻找一个真正的 VCL 库 有人认识一个吗 您可以将 Exodus 的基础代码编译为 VCL 该代码称为 JOPL 可以在以下位置找
  • 为捕获过滤器添加音频功能

    我正在尝试向捕获源过滤器添加音频功能 以便制作带有音频的虚拟摄像机 开始于TMH s and rdp的代码我用另一个引脚扩展了它 称为 音频 CUnknown WINAPI CVCam CreateInstance LPUNKNOWN lp