Добавьте аудиовозможности в фильтр захвата

Я пытаюсь добавить возможность звука в фильтр источника захвата, чтобы сделать виртуальную камеру со звуком. Начиная с TMH и rdp, я расширил его другим выводом, который называется "Audio":

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 и открываю страницу его свойств. На выводе Audio отображается следующая (неверная) информация:

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 зависает навсегда, а отладчик VS (который подключен к этому процессу) ничего не говорит. Какой фрагмент кода управляет этой вкладкой?

ОБНОВИТЬ

Решена первая проблема:

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;
}

person Mark    schedule 29.01.2018    source источник


Ответы (1)


Краткая версия: Microsoft на самом деле не предлагает API для предоставления виртуального аудиоустройства, чтобы приложения хорошо воспринимали его, как если бы это было реальное устройство захвата звука.

Если фильтры захвата виртуального видео часто работают по историческим причинам, то со звуком это не так. Драйвер уровня ядра, реализующий аудиоустройство, — это способ добавить аудиоустройство, которое распознают приложения.


Вкладка «Задержка» появляется, потому что вы притворились, что реализуете интерфейс IAMBufferNegotiation:

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

Реализация, скорее всего, будет некорректной, что приведет к непредвиденному поведению (зависанию, сбою и т. д.).

Добавление аудиовывода в тот же фильтр возможно, но это не лучшая идея, если вы ожидаете, что поток будет выбран как искусственный источник. В целом это имеет смысл, но реальные устройства почти никогда не показывают такие аудиопотоки.

Короче говоря, единственное приложение, которое может использовать подобный аудиопоток, — это то, которое вы сами разрабатываете: ни одно известное приложение не пытается найти аудиовывод на фильтре источника видео. По этой причине реализация IAMStreamConfig и тем более IKsPropertySet на таком выводе бесполезна.

Вы не сможете зарегистрировать фильтр в категории «Источники захвата звука», потому что вы регистрируете фильтр, и этот фильтр сначала выставляет выходной контакт видео, и только потом появляется какое-то вторичное аудио. Если вы нацелены на приложение, которое потребляет звук через DirectShow (что уже довольно редко по причинам, выходящим за рамки этого вопроса), вам следует разработать отдельный фильтр источника. Конечно, вы можете заставить два фильтра общаться друг с другом за кулисами для совместной доставки определенного потока, но с точки зрения DirectShow обычно фильтры отображаются как независимые.

...также настоящие веб-камеры выставляют два разных фильтра, и поэтому в таких приложениях, как Skype, мы должны выбирать как под видео, так и под аудио устройства.
Не лучше ли создать два совершенно разных проекта и фильтра: один для видео и один для аудио?

Реальная и типичная камера:

введите здесь описание изображения

Поскольку «настоящие» физические камеры обычно поставляются с драйверами уровня ядра, их присутствие в DirectShow происходит через Фильтр видеозахвата WDM, который действует как прокси и перечисляет "оболочки DirectShow" драйверов камер в той же категории "Источники видеозахвата", где вы регистрируете виртуальные камеры.

То есть такой дизайн позволяет смешивать реальные и виртуальные камеры в списке доступных устройств, которые использует приложение на базе DirectShow при захвате видео. Этот подход имеет свои ограничения, которые я описал ранее, например. в этом вопросе и указанном сообщении Применимость виртуальных источников DirectShow.

Поскольку Media Foundation, преемник DirectShow, в целом не получил хорошего приема, и, кроме того, Media Foundation не предлагает ни хорошей обратной совместимости, ни расширяемости захвата видео, множество приложений, включая собственное приложение Microsoft, по-прежнему используют захват видео через DirectShow. И наоборот, тех, кто изучает API видеозахвата для Windows, также часто интересует DirectShow, а не «текущий» API из-за наличия примеров и соответствующей информации, расширяемости API, возможностей интеграции приложений.

Однако это не относится к аудио. Захват звука DirectShow не был первоклассным уже в то время, когда разработка DirectShow была остановлена. Windows Vista представляет новый API для аудио WASAPI, а DirectShow — нет. получить соответствующее подключение к новому API ни для захвата звука, ни для воспроизведения. Звук сам по себе проще, а WASAPI был мощным и удобным для разработчиков, поэтому разработчики начали переключаться на новый API для задач, связанных со звуком. Гораздо меньше приложений используют DirectShow для захвата звука, и ваша реализация виртуального источника звука, скорее всего, будет промахом: ваше устройство останется «невидимым» для приложений, использующих захват звука через WASAPI. Даже если в приложении есть запасной патч кода для Windows XP для захвата звука через DirectShow, вряд ли это станет для вас облегчением в более новых ОС.

Продолжить чтение аудио на StackOverflow:

Кроме того, вам не нужно иметь отдельные проекты для видео и аудио фильтров. Вы можете смешивать их в одном проекте, они могут быть просто независимыми фильтрами, зарегистрированными отдельно.

person Roman R.    schedule 29.01.2018
comment
Спасибо за четкий ответ. Итак, если я правильно понимаю, настоящие веб-камеры также выставляют два разных фильтра, и поэтому в приложении, таком как Skype, мы должны выбирать как под видео, так и под аудиоустройства. - person Mark; 29.01.2018
comment
Итог: лучше создать два совершенно разных проекта и фильтра: один для видео и один для аудио. - person Mark; 29.01.2018
comment
Я добавил текст в тело ответа, так как это слишком много для комментария :) - person Roman R.; 29.01.2018