/* * Audio capture filter * * Copyright 2015 Damjan Jovanovic * Copyright 2023 Zeb Figura for CodeWeavers * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ #include "qcap_private.h" WINE_DEFAULT_DEBUG_CHANNEL(quartz); struct audio_record { struct strmbase_filter filter; IPersistPropertyBag IPersistPropertyBag_iface; struct strmbase_source source; IAMStreamConfig IAMStreamConfig_iface; }; static struct audio_record *impl_from_strmbase_filter(struct strmbase_filter *filter) { return CONTAINING_RECORD(filter, struct audio_record, filter); } static HRESULT audio_record_source_query_interface(struct strmbase_pin *iface, REFIID iid, void **out) { struct audio_record *filter = impl_from_strmbase_filter(iface->filter); if (IsEqualGUID(iid, &IID_IAMStreamConfig)) *out = &filter->IAMStreamConfig_iface; else return E_NOINTERFACE; IUnknown_AddRef((IUnknown *)*out); return S_OK; } static const struct { unsigned int rate; unsigned int depth; unsigned int channels; } audio_formats[] = { {44100, 16, 2}, {44100, 16, 1}, {32000, 16, 2}, {32000, 16, 1}, {22050, 16, 2}, {22050, 16, 1}, {11025, 16, 2}, {11025, 16, 1}, { 8000, 16, 2}, { 8000, 16, 1}, {44100, 8, 2}, {44100, 8, 1}, {22050, 8, 2}, {22050, 8, 1}, {11025, 8, 2}, {11025, 8, 1}, { 8000, 8, 2}, { 8000, 8, 1}, {48000, 16, 2}, {48000, 16, 1}, {96000, 16, 2}, {96000, 16, 1}, }; static HRESULT fill_media_type(unsigned int index, AM_MEDIA_TYPE *mt) { WAVEFORMATEX *format; if (index >= ARRAY_SIZE(audio_formats)) return VFW_S_NO_MORE_ITEMS; if (!(format = CoTaskMemAlloc(sizeof(*format)))) return E_OUTOFMEMORY; memset(format, 0, sizeof(WAVEFORMATEX)); format->wFormatTag = WAVE_FORMAT_PCM; format->nChannels = audio_formats[index].channels; format->nSamplesPerSec = audio_formats[index].rate; format->wBitsPerSample = audio_formats[index].depth; format->nBlockAlign = audio_formats[index].channels * audio_formats[index].depth / 8; format->nAvgBytesPerSec = format->nBlockAlign * format->nSamplesPerSec; memset(mt, 0, sizeof(AM_MEDIA_TYPE)); mt->majortype = MEDIATYPE_Audio; mt->subtype = MEDIASUBTYPE_PCM; mt->bFixedSizeSamples = TRUE; mt->lSampleSize = format->nBlockAlign; mt->formattype = FORMAT_WaveFormatEx; mt->cbFormat = sizeof(WAVEFORMATEX); mt->pbFormat = (BYTE *)format; return S_OK; } static HRESULT audio_record_source_get_media_type(struct strmbase_pin *iface, unsigned int index, AM_MEDIA_TYPE *mt) { return fill_media_type(index, mt); } static HRESULT WINAPI audio_record_source_DecideBufferSize(struct strmbase_source *iface, IMemAllocator *allocator, ALLOCATOR_PROPERTIES *props) { ALLOCATOR_PROPERTIES ret_props; return IMemAllocator_SetProperties(allocator, props, &ret_props); } static const struct strmbase_source_ops source_ops = { .base.pin_get_media_type = audio_record_source_get_media_type, .base.pin_query_interface = audio_record_source_query_interface, .pfnAttemptConnection = BaseOutputPinImpl_AttemptConnection, .pfnDecideAllocator = BaseOutputPinImpl_DecideAllocator, .pfnDecideBufferSize = audio_record_source_DecideBufferSize, }; static struct audio_record *impl_from_IAMStreamConfig(IAMStreamConfig *iface) { return CONTAINING_RECORD(iface, struct audio_record, IAMStreamConfig_iface); } static HRESULT WINAPI stream_config_QueryInterface(IAMStreamConfig *iface, REFIID iid, void **out) { struct audio_record *filter = impl_from_IAMStreamConfig(iface); return IPin_QueryInterface(&filter->source.pin.IPin_iface, iid, out); } static ULONG WINAPI stream_config_AddRef(IAMStreamConfig *iface) { struct audio_record *filter = impl_from_IAMStreamConfig(iface); return IPin_AddRef(&filter->source.pin.IPin_iface); } static ULONG WINAPI stream_config_Release(IAMStreamConfig *iface) { struct audio_record *filter = impl_from_IAMStreamConfig(iface); return IPin_Release(&filter->source.pin.IPin_iface); } static HRESULT WINAPI stream_config_SetFormat(IAMStreamConfig *iface, AM_MEDIA_TYPE *mt) { FIXME("iface %p, mt %p, stub!\n", iface, mt); strmbase_dump_media_type(mt); return E_NOTIMPL; } static HRESULT WINAPI stream_config_GetFormat(IAMStreamConfig *iface, AM_MEDIA_TYPE **mt) { FIXME("iface %p, mt %p, stub!\n", iface, mt); return E_NOTIMPL; } static HRESULT WINAPI stream_config_GetNumberOfCapabilities(IAMStreamConfig *iface, int *count, int *size) { struct audio_record *filter = impl_from_IAMStreamConfig(iface); TRACE("filter %p, count %p, size %p.\n", filter, count, size); *count = ARRAY_SIZE(audio_formats); *size = sizeof(AUDIO_STREAM_CONFIG_CAPS); return S_OK; } static HRESULT WINAPI stream_config_GetStreamCaps(IAMStreamConfig *iface, int index, AM_MEDIA_TYPE **ret_mt, BYTE *caps) { struct audio_record *filter = impl_from_IAMStreamConfig(iface); AUDIO_STREAM_CONFIG_CAPS *audio_caps = (void *)caps; AM_MEDIA_TYPE *mt; HRESULT hr; TRACE("filter %p, index %d, ret_mt %p, caps %p.\n", filter, index, ret_mt, caps); if (index >= ARRAY_SIZE(audio_formats)) return S_FALSE; if (!(mt = CoTaskMemAlloc(sizeof(AM_MEDIA_TYPE)))) return E_OUTOFMEMORY; if ((hr = fill_media_type(index, mt)) != S_OK) { CoTaskMemFree(mt); return hr; } *ret_mt = mt; audio_caps->guid = MEDIATYPE_Audio; audio_caps->MinimumChannels = 1; audio_caps->MaximumChannels = 2; audio_caps->ChannelsGranularity = 1; audio_caps->MinimumBitsPerSample = 8; audio_caps->MaximumBitsPerSample = 16; audio_caps->BitsPerSampleGranularity = 8; audio_caps->MinimumSampleFrequency = 11025; audio_caps->MaximumSampleFrequency = 44100; audio_caps->SampleFrequencyGranularity = 11025; return S_OK; } static const IAMStreamConfigVtbl stream_config_vtbl = { stream_config_QueryInterface, stream_config_AddRef, stream_config_Release, stream_config_SetFormat, stream_config_GetFormat, stream_config_GetNumberOfCapabilities, stream_config_GetStreamCaps, }; static struct audio_record *impl_from_IPersistPropertyBag(IPersistPropertyBag *iface) { return CONTAINING_RECORD(iface, struct audio_record, IPersistPropertyBag_iface); } static struct strmbase_pin *audio_record_get_pin(struct strmbase_filter *iface, unsigned int index) { struct audio_record *filter = impl_from_strmbase_filter(iface); if (!index) return &filter->source.pin; return NULL; } static void audio_record_destroy(struct strmbase_filter *iface) { struct audio_record *filter = impl_from_strmbase_filter(iface); strmbase_source_cleanup(&filter->source); strmbase_filter_cleanup(&filter->filter); free(filter); } static HRESULT audio_record_query_interface(struct strmbase_filter *iface, REFIID iid, void **out) { struct audio_record *filter = impl_from_strmbase_filter(iface); if (IsEqualGUID(iid, &IID_IPersistPropertyBag)) *out = &filter->IPersistPropertyBag_iface; else return E_NOINTERFACE; IUnknown_AddRef((IUnknown *)*out); return S_OK; } static const struct strmbase_filter_ops filter_ops = { .filter_get_pin = audio_record_get_pin, .filter_destroy = audio_record_destroy, .filter_query_interface = audio_record_query_interface, }; static HRESULT WINAPI PPB_QueryInterface(IPersistPropertyBag *iface, REFIID riid, LPVOID *ppv) { struct audio_record *filter = impl_from_IPersistPropertyBag(iface); return IUnknown_QueryInterface(filter->filter.outer_unk, riid, ppv); } static ULONG WINAPI PPB_AddRef(IPersistPropertyBag *iface) { struct audio_record *filter = impl_from_IPersistPropertyBag(iface); return IUnknown_AddRef(filter->filter.outer_unk); } static ULONG WINAPI PPB_Release(IPersistPropertyBag *iface) { struct audio_record *filter = impl_from_IPersistPropertyBag(iface); return IUnknown_Release(filter->filter.outer_unk); } static HRESULT WINAPI PPB_GetClassID(IPersistPropertyBag *iface, CLSID *pClassID) { struct audio_record *This = impl_from_IPersistPropertyBag(iface); TRACE("(%p/%p)->(%p)\n", iface, This, pClassID); return IBaseFilter_GetClassID(&This->filter.IBaseFilter_iface, pClassID); } static HRESULT WINAPI PPB_InitNew(IPersistPropertyBag *iface) { struct audio_record *This = impl_from_IPersistPropertyBag(iface); FIXME("(%p/%p)->(): stub\n", iface, This); return E_NOTIMPL; } static HRESULT WINAPI PPB_Load(IPersistPropertyBag *iface, IPropertyBag *pPropBag, IErrorLog *pErrorLog) { struct audio_record *This = impl_from_IPersistPropertyBag(iface); HRESULT hr; VARIANT var; TRACE("(%p/%p)->(%p, %p)\n", iface, This, pPropBag, pErrorLog); V_VT(&var) = VT_I4; hr = IPropertyBag_Read(pPropBag, L"WaveInID", &var, pErrorLog); if (SUCCEEDED(hr)) { FIXME("FIXME: implement opening waveIn device %ld\n", V_I4(&var)); } return hr; } static HRESULT WINAPI PPB_Save(IPersistPropertyBag *iface, IPropertyBag *pPropBag, BOOL fClearDirty, BOOL fSaveAllProperties) { struct audio_record *This = impl_from_IPersistPropertyBag(iface); FIXME("(%p/%p)->(%p, %u, %u): stub\n", iface, This, pPropBag, fClearDirty, fSaveAllProperties); return E_NOTIMPL; } static const IPersistPropertyBagVtbl PersistPropertyBagVtbl = { PPB_QueryInterface, PPB_AddRef, PPB_Release, PPB_GetClassID, PPB_InitNew, PPB_Load, PPB_Save }; HRESULT audio_record_create(IUnknown *outer, IUnknown **out) { struct audio_record *object; if (!(object = calloc(1, sizeof(*object)))) return E_OUTOFMEMORY; object->IPersistPropertyBag_iface.lpVtbl = &PersistPropertyBagVtbl; strmbase_filter_init(&object->filter, outer, &CLSID_AudioRecord, &filter_ops); strmbase_source_init(&object->source, &object->filter, L"Capture", &source_ops); object->IAMStreamConfig_iface.lpVtbl = &stream_config_vtbl; TRACE("Created audio recorder %p.\n", object); *out = &object->filter.IUnknown_inner; return S_OK; }