/* * File writer filter * * Copyright (C) 2020 Zebediah Figura * * 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 */ #define COBJMACROS #include "dshow.h" #include "qcap_main.h" #include "wine/debug.h" #include "wine/unicode.h" WINE_DEFAULT_DEBUG_CHANNEL(qcap); struct file_writer { struct strmbase_filter filter; IAMFilterMiscFlags IAMFilterMiscFlags_iface; IFileSinkFilter IFileSinkFilter_iface; struct strmbase_sink sink; WCHAR *filename; HANDLE file; BOOL eos; }; static inline struct file_writer *impl_from_strmbase_pin(struct strmbase_pin *iface) { return CONTAINING_RECORD(iface, struct file_writer, sink.pin); } static HRESULT file_writer_sink_query_interface(struct strmbase_pin *iface, REFIID iid, void **out) { struct file_writer *filter = impl_from_strmbase_pin(iface); if (IsEqualGUID(iid, &IID_IMemInputPin)) *out = &filter->sink.IMemInputPin_iface; else return E_NOINTERFACE; IUnknown_AddRef((IUnknown *)*out); return S_OK; } static HRESULT file_writer_sink_query_accept(struct strmbase_pin *iface, const AM_MEDIA_TYPE *mt) { struct file_writer *filter = impl_from_strmbase_pin(iface); if (filter->filename && !IsEqualGUID(&mt->majortype, &MEDIATYPE_Stream)) return S_FALSE; return S_OK; } static HRESULT WINAPI file_writer_sink_receive(struct strmbase_sink *iface, IMediaSample *sample) { struct file_writer *filter = impl_from_strmbase_pin(&iface->pin); REFERENCE_TIME start, stop; LARGE_INTEGER offset; HRESULT hr; DWORD size; BYTE *data; if ((hr = IMediaSample_GetTime(sample, &start, &stop)) != S_OK) ERR("Failed to get sample time, hr %#x.\n", hr); if ((hr = IMediaSample_GetPointer(sample, &data)) != S_OK) ERR("Failed to get sample pointer, hr %#x.\n", hr); offset.QuadPart = start; if (!SetFilePointerEx(filter->file, offset, NULL, FILE_BEGIN) || !WriteFile(filter->file, data, stop - start, &size, NULL)) { ERR("Failed to write file, error %u.\n", GetLastError()); return HRESULT_FROM_WIN32(hr); } if (size != stop - start) ERR("Short write, %u/%u.\n", size, (DWORD)(stop - start)); return S_OK; } static void deliver_ec_complete(struct file_writer *filter) { IMediaEventSink *event_sink; if (SUCCEEDED(IFilterGraph_QueryInterface(filter->filter.graph, &IID_IMediaEventSink, (void **)&event_sink))) { IMediaEventSink_Notify(event_sink, EC_COMPLETE, S_OK, (LONG_PTR)&filter->filter.IBaseFilter_iface); IMediaEventSink_Release(event_sink); } } static HRESULT file_writer_sink_eos(struct strmbase_sink *iface) { struct file_writer *filter = impl_from_strmbase_pin(&iface->pin); EnterCriticalSection(&filter->filter.csFilter); if (filter->filter.state == State_Running) deliver_ec_complete(filter); else filter->eos = TRUE; LeaveCriticalSection(&filter->filter.csFilter); return S_OK; } static const struct strmbase_sink_ops sink_ops = { .base.pin_query_interface = file_writer_sink_query_interface, .base.pin_query_accept = file_writer_sink_query_accept, .pfnReceive = file_writer_sink_receive, .sink_eos = file_writer_sink_eos, }; static inline struct file_writer *impl_from_strmbase_filter(struct strmbase_filter *iface) { return CONTAINING_RECORD(iface, struct file_writer, filter); } static HRESULT file_writer_query_interface(struct strmbase_filter *iface, REFIID iid, void **out) { struct file_writer *filter = impl_from_strmbase_filter(iface); if (IsEqualGUID(iid, &IID_IAMFilterMiscFlags)) *out = &filter->IAMFilterMiscFlags_iface; else if (IsEqualGUID(iid, &IID_IFileSinkFilter)) *out = &filter->IFileSinkFilter_iface; else return E_NOINTERFACE; IUnknown_AddRef((IUnknown *)*out); return S_OK; } static struct strmbase_pin *file_writer_get_pin(struct strmbase_filter *iface, unsigned int index) { struct file_writer *filter = impl_from_strmbase_filter(iface); if (!index) return &filter->sink.pin; return NULL; } static void file_writer_destroy(struct strmbase_filter *iface) { struct file_writer *filter = impl_from_strmbase_filter(iface); heap_free(filter->filename); strmbase_sink_cleanup(&filter->sink); strmbase_filter_cleanup(&filter->filter); heap_free(filter); } static HRESULT file_writer_init_stream(struct strmbase_filter *iface) { struct file_writer *filter = impl_from_strmbase_filter(iface); HANDLE file; if ((file = CreateFileW(filter->filename, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, NULL)) == INVALID_HANDLE_VALUE) { ERR("Failed to create %s, error %u.\n", debugstr_w(filter->filename), GetLastError()); return HRESULT_FROM_WIN32(GetLastError()); } filter->file = file; return S_OK; } static HRESULT file_writer_start_stream(struct strmbase_filter *iface, REFERENCE_TIME start) { struct file_writer *filter = impl_from_strmbase_filter(iface); if (filter->eos) deliver_ec_complete(filter); filter->eos = FALSE; return S_OK; } static HRESULT file_writer_cleanup_stream(struct strmbase_filter *iface) { struct file_writer *filter = impl_from_strmbase_filter(iface); CloseHandle(filter->file); return S_OK; } static struct strmbase_filter_ops filter_ops = { .filter_query_interface = file_writer_query_interface, .filter_get_pin = file_writer_get_pin, .filter_destroy = file_writer_destroy, .filter_init_stream = file_writer_init_stream, .filter_start_stream = file_writer_start_stream, .filter_cleanup_stream = file_writer_cleanup_stream, }; static inline struct file_writer *impl_from_IFileSinkFilter(IFileSinkFilter *iface) { return CONTAINING_RECORD(iface, struct file_writer, IFileSinkFilter_iface); } static HRESULT WINAPI filesinkfilter_QueryInterface(IFileSinkFilter *iface, REFIID iid, void **out) { struct file_writer *filter = impl_from_IFileSinkFilter(iface); return IUnknown_QueryInterface(filter->filter.outer_unk, iid, out); } static ULONG WINAPI filesinkfilter_AddRef(IFileSinkFilter *iface) { struct file_writer *filter = impl_from_IFileSinkFilter(iface); return IUnknown_AddRef(filter->filter.outer_unk); } static ULONG WINAPI filesinkfilter_Release(IFileSinkFilter *iface) { struct file_writer *filter = impl_from_IFileSinkFilter(iface); return IUnknown_Release(filter->filter.outer_unk); } static HRESULT WINAPI filesinkfilter_SetFileName(IFileSinkFilter *iface, LPCOLESTR filename, const AM_MEDIA_TYPE *mt) { struct file_writer *filter = impl_from_IFileSinkFilter(iface); WCHAR *new_filename; TRACE("filter %p, filename %s, mt %p, stub!\n", filter, debugstr_w(filename), mt); strmbase_dump_media_type(mt); if (mt) FIXME("Ignoring media type %p.\n", mt); if (!(new_filename = heap_alloc((strlenW(filename) + 1) * sizeof(WCHAR)))) return E_OUTOFMEMORY; strcpyW(new_filename, filename); heap_free(filter->filename); filter->filename = new_filename; return S_OK; } static HRESULT WINAPI filesinkfilter_GetCurFile(IFileSinkFilter *iface, LPOLESTR *filename, AM_MEDIA_TYPE *mt) { struct file_writer *filter = impl_from_IFileSinkFilter(iface); FIXME("filter %p, filename %p, mt %p, stub!\n", filter, filename, mt); return E_NOTIMPL; } static const IFileSinkFilterVtbl filesinkfilter_vtbl = { filesinkfilter_QueryInterface, filesinkfilter_AddRef, filesinkfilter_Release, filesinkfilter_SetFileName, filesinkfilter_GetCurFile, }; static inline struct file_writer *impl_from_IAMFilterMiscFlags(IAMFilterMiscFlags *iface) { return CONTAINING_RECORD(iface, struct file_writer, IAMFilterMiscFlags_iface); } static HRESULT WINAPI misc_flags_QueryInterface(IAMFilterMiscFlags *iface, REFIID iid, void **out) { struct file_writer *filter = impl_from_IAMFilterMiscFlags(iface); return IUnknown_QueryInterface(filter->filter.outer_unk, iid, out); } static ULONG WINAPI misc_flags_AddRef(IAMFilterMiscFlags *iface) { struct file_writer *filter = impl_from_IAMFilterMiscFlags(iface); return IUnknown_AddRef(filter->filter.outer_unk); } static ULONG WINAPI misc_flags_Release(IAMFilterMiscFlags *iface) { struct file_writer *filter = impl_from_IAMFilterMiscFlags(iface); return IUnknown_Release(filter->filter.outer_unk); } static ULONG WINAPI misc_flags_GetMiscFlags(IAMFilterMiscFlags *iface) { struct file_writer *filter = impl_from_IAMFilterMiscFlags(iface); TRACE("filter %p.\n", filter); return AM_FILTER_MISC_FLAGS_IS_RENDERER; } static const IAMFilterMiscFlagsVtbl misc_flags_vtbl = { misc_flags_QueryInterface, misc_flags_AddRef, misc_flags_Release, misc_flags_GetMiscFlags, }; HRESULT file_writer_create(IUnknown *outer, IUnknown **out) { static const WCHAR sink_name[] = {'i','n',0}; struct file_writer *object; if (!(object = heap_alloc_zero(sizeof(*object)))) return E_OUTOFMEMORY; strmbase_filter_init(&object->filter, outer, &CLSID_FileWriter, &filter_ops); object->IFileSinkFilter_iface.lpVtbl = &filesinkfilter_vtbl; object->IAMFilterMiscFlags_iface.lpVtbl = &misc_flags_vtbl; strmbase_sink_init(&object->sink, &object->filter, sink_name, &sink_ops, NULL); TRACE("Created file writer %p.\n", object); *out = &object->filter.IUnknown_inner; return S_OK; }