4 * Copyright 2005 Christian Costa
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21 #include "quartz_private.h"
34 #include "wine/debug.h"
36 WINE_DEFAULT_DEBUG_CHANNEL(quartz
);
40 struct strmbase_filter filter
;
42 struct strmbase_source source
;
43 IQualityControl source_IQualityControl_iface
;
44 IQualityControl
*source_qc_sink
;
45 struct strmbase_passthrough passthrough
;
47 struct strmbase_sink sink
;
51 LPWAVEFORMATEX pWfOut
;
53 LONGLONG lasttime_real
;
54 LONGLONG lasttime_sent
;
57 static struct acm_wrapper
*impl_from_strmbase_filter(struct strmbase_filter
*iface
)
59 return CONTAINING_RECORD(iface
, struct acm_wrapper
, filter
);
62 static HRESULT
acm_wrapper_sink_query_interface(struct strmbase_pin
*iface
, REFIID iid
, void **out
)
64 struct acm_wrapper
*filter
= impl_from_strmbase_filter(iface
->filter
);
66 if (IsEqualGUID(iid
, &IID_IMemInputPin
))
67 *out
= &filter
->sink
.IMemInputPin_iface
;
71 IUnknown_AddRef((IUnknown
*)*out
);
75 static HRESULT
acm_wrapper_sink_query_accept(struct strmbase_pin
*iface
, const AM_MEDIA_TYPE
*mt
)
80 static HRESULT WINAPI
acm_wrapper_sink_Receive(struct strmbase_sink
*iface
, IMediaSample
*pSample
)
82 struct acm_wrapper
*This
= impl_from_strmbase_filter(iface
->pin
.filter
);
83 IMediaSample
* pOutSample
= NULL
;
84 DWORD cbDstStream
, cbSrcStream
;
86 LPBYTE pbSrcStream
= NULL
;
88 BOOL unprepare_header
= FALSE
, preroll
;
91 LONGLONG tStart
= -1, tStop
= -1, tMed
;
92 LONGLONG mtStart
= -1, mtStop
= -1, mtMed
;
94 /* We do not expect pin connection state to change while the filter is
95 * running. This guarantee is necessary, since otherwise we would have to
96 * take the filter lock, and we can't take the filter lock from a streaming
98 if (!This
->source
.pMemInputPin
)
100 WARN("Source is not connected, returning VFW_E_NOT_CONNECTED.\n");
101 return VFW_E_NOT_CONNECTED
;
104 if (This
->filter
.state
== State_Stopped
)
105 return VFW_E_WRONG_STATE
;
107 if (This
->sink
.flushing
)
110 hr
= IMediaSample_GetPointer(pSample
, &pbSrcStream
);
113 ERR("Failed to get input buffer pointer, hr %#lx.\n", hr
);
117 preroll
= (IMediaSample_IsPreroll(pSample
) == S_OK
);
119 IMediaSample_GetTime(pSample
, &tStart
, &tStop
);
120 if (IMediaSample_GetMediaTime(pSample
, &mtStart
, &mtStop
) != S_OK
)
121 mtStart
= mtStop
= -1;
122 cbSrcStream
= IMediaSample_GetActualDataLength(pSample
);
124 /* Prevent discontinuities when codecs 'absorb' data but not give anything back in return */
125 if (IMediaSample_IsDiscontinuity(pSample
) == S_OK
)
127 This
->lasttime_real
= tStart
;
128 This
->lasttime_sent
= tStart
;
130 else if (This
->lasttime_real
== tStart
)
131 tStart
= This
->lasttime_sent
;
133 WARN("Discontinuity\n");
138 ash
.pbSrc
= pbSrcStream
;
139 ash
.cbSrcLength
= cbSrcStream
;
141 while(hr
== S_OK
&& ash
.cbSrcLength
)
143 if (FAILED(hr
= IMemAllocator_GetBuffer(This
->source
.pAllocator
, &pOutSample
, NULL
, NULL
, 0)))
145 ERR("Failed to get sample, hr %#lx.\n", hr
);
148 IMediaSample_SetPreroll(pOutSample
, preroll
);
150 hr
= IMediaSample_SetActualDataLength(pOutSample
, 0);
153 hr
= IMediaSample_GetPointer(pOutSample
, &pbDstStream
);
155 ERR("Failed to get output buffer pointer, hr %#lx.\n", hr
);
158 cbDstStream
= IMediaSample_GetSize(pOutSample
);
160 ash
.cbStruct
= sizeof(ash
);
163 ash
.pbDst
= pbDstStream
;
164 ash
.cbDstLength
= cbDstStream
;
166 if ((res
= acmStreamPrepareHeader(This
->has
, &ash
, 0))) {
167 ERR("Cannot prepare header %d\n", res
);
170 unprepare_header
= TRUE
;
172 if (IMediaSample_IsDiscontinuity(pSample
) == S_OK
)
174 res
= acmStreamConvert(This
->has
, &ash
, ACM_STREAMCONVERTF_START
);
175 IMediaSample_SetDiscontinuity(pOutSample
, TRUE
);
176 /* One sample could be converted to multiple packets */
177 IMediaSample_SetDiscontinuity(pSample
, FALSE
);
181 res
= acmStreamConvert(This
->has
, &ash
, 0);
182 IMediaSample_SetDiscontinuity(pOutSample
, FALSE
);
187 if(res
!= MMSYSERR_MOREDATA
)
188 ERR("Cannot convert data header %d\n", res
);
192 TRACE("used in %lu/%lu, used out %lu/%lu\n", ash
.cbSrcLengthUsed
, ash
.cbSrcLength
, ash
.cbDstLengthUsed
, ash
.cbDstLength
);
194 hr
= IMediaSample_SetActualDataLength(pOutSample
, ash
.cbDstLengthUsed
);
197 /* Bug in acm codecs? It apparently uses the input, but doesn't necessarily output immediately */
198 if (!ash
.cbSrcLengthUsed
)
200 WARN("Sample was skipped? Outputted: %lu\n", ash
.cbDstLengthUsed
);
205 TRACE("Sample start time: %s.\n", debugstr_time(tStart
));
206 if (ash
.cbSrcLengthUsed
== cbSrcStream
)
208 IMediaSample_SetTime(pOutSample
, &tStart
, &tStop
);
209 tStart
= tMed
= tStop
;
211 else if (tStop
!= tStart
)
213 tMed
= tStop
- tStart
;
214 tMed
= tStart
+ tMed
* ash
.cbSrcLengthUsed
/ cbSrcStream
;
215 IMediaSample_SetTime(pOutSample
, &tStart
, &tMed
);
220 ERR("No valid timestamp found\n");
221 IMediaSample_SetTime(pOutSample
, NULL
, NULL
);
225 IMediaSample_SetMediaTime(pOutSample
, NULL
, NULL
);
226 } else if (ash
.cbSrcLengthUsed
== cbSrcStream
) {
227 IMediaSample_SetMediaTime(pOutSample
, &mtStart
, &mtStop
);
228 mtStart
= mtMed
= mtStop
;
229 } else if (mtStop
>= mtStart
) {
230 mtMed
= mtStop
- mtStart
;
231 mtMed
= mtStart
+ mtMed
* ash
.cbSrcLengthUsed
/ cbSrcStream
;
232 IMediaSample_SetMediaTime(pOutSample
, &mtStart
, &mtMed
);
235 IMediaSample_SetMediaTime(pOutSample
, NULL
, NULL
);
238 TRACE("Sample stop time: %s\n", debugstr_time(tStart
));
240 hr
= IMemInputPin_Receive(This
->source
.pMemInputPin
, pOutSample
);
241 if (hr
!= S_OK
&& hr
!= VFW_E_NOT_CONNECTED
) {
243 ERR("Failed to send sample, hr %#lx.\n", hr
);
248 if (unprepare_header
&& (res
= acmStreamUnprepareHeader(This
->has
, &ash
, 0)))
249 ERR("Cannot unprepare header %d\n", res
);
250 unprepare_header
= FALSE
;
251 ash
.pbSrc
+= ash
.cbSrcLengthUsed
;
252 ash
.cbSrcLength
-= ash
.cbSrcLengthUsed
;
254 IMediaSample_Release(pOutSample
);
259 This
->lasttime_real
= tStop
;
260 This
->lasttime_sent
= tMed
;
265 static BOOL
is_audio_subtype(const GUID
*guid
)
267 return !memcmp(&guid
->Data2
, &MEDIATYPE_Audio
.Data2
, sizeof(GUID
) - sizeof(int));
270 static HRESULT
acm_wrapper_sink_connect(struct strmbase_sink
*iface
, IPin
*peer
, const AM_MEDIA_TYPE
*mt
)
272 struct acm_wrapper
*filter
= impl_from_strmbase_filter(iface
->pin
.filter
);
273 const WAVEFORMATEX
*wfx
= (WAVEFORMATEX
*)mt
->pbFormat
;
277 if (!IsEqualGUID(&mt
->majortype
, &MEDIATYPE_Audio
) || !is_audio_subtype(&mt
->subtype
)
278 || !IsEqualGUID(&mt
->formattype
, &FORMAT_WaveFormatEx
) || !wfx
279 || wfx
->wFormatTag
== WAVE_FORMAT_PCM
|| wfx
->wFormatTag
== WAVE_FORMAT_EXTENSIBLE
)
280 return VFW_E_TYPE_NOT_ACCEPTED
;
282 CopyMediaType(&filter
->mt
, mt
);
283 filter
->mt
.subtype
.Data1
= WAVE_FORMAT_PCM
;
284 filter
->pWfOut
= (WAVEFORMATEX
*)filter
->mt
.pbFormat
;
285 filter
->pWfOut
->wFormatTag
= WAVE_FORMAT_PCM
;
286 filter
->pWfOut
->wBitsPerSample
= 16;
287 filter
->pWfOut
->nBlockAlign
= filter
->pWfOut
->wBitsPerSample
* filter
->pWfOut
->nChannels
/ 8;
288 filter
->pWfOut
->cbSize
= 0;
289 filter
->pWfOut
->nAvgBytesPerSec
= filter
->pWfOut
->nChannels
* filter
->pWfOut
->nSamplesPerSec
290 * (filter
->pWfOut
->wBitsPerSample
/ 8);
292 if ((res
= acmStreamOpen(&drv
, NULL
, (WAVEFORMATEX
*)wfx
, filter
->pWfOut
, NULL
, 0, 0, 0)))
294 ERR("Failed to open stream, error %u.\n", res
);
295 FreeMediaType(&filter
->mt
);
296 return VFW_E_TYPE_NOT_ACCEPTED
;
304 static void acm_wrapper_sink_disconnect(struct strmbase_sink
*iface
)
306 struct acm_wrapper
*filter
= impl_from_strmbase_filter(iface
->pin
.filter
);
309 acmStreamClose(filter
->has
, 0);
311 filter
->lasttime_real
= filter
->lasttime_sent
= -1;
314 static const struct strmbase_sink_ops sink_ops
=
316 .base
.pin_query_interface
= acm_wrapper_sink_query_interface
,
317 .base
.pin_query_accept
= acm_wrapper_sink_query_accept
,
318 .pfnReceive
= acm_wrapper_sink_Receive
,
319 .sink_connect
= acm_wrapper_sink_connect
,
320 .sink_disconnect
= acm_wrapper_sink_disconnect
,
323 static HRESULT
acm_wrapper_source_query_interface(struct strmbase_pin
*iface
, REFIID iid
, void **out
)
325 struct acm_wrapper
*filter
= impl_from_strmbase_filter(iface
->filter
);
327 if (IsEqualGUID(iid
, &IID_IQualityControl
))
328 *out
= &filter
->source_IQualityControl_iface
;
329 else if (IsEqualGUID(iid
, &IID_IMediaSeeking
))
330 *out
= &filter
->passthrough
.IMediaSeeking_iface
;
332 return E_NOINTERFACE
;
334 IUnknown_AddRef((IUnknown
*)*out
);
338 static HRESULT
acm_wrapper_source_query_accept(struct strmbase_pin
*iface
, const AM_MEDIA_TYPE
*mt
)
340 struct acm_wrapper
*filter
= impl_from_strmbase_filter(iface
->filter
);
342 if (IsEqualGUID(&mt
->majortype
, &filter
->mt
.majortype
)
343 && (IsEqualGUID(&mt
->subtype
, &filter
->mt
.subtype
)
344 || IsEqualGUID(&filter
->mt
.subtype
, &GUID_NULL
)))
349 static HRESULT
acm_wrapper_source_get_media_type(struct strmbase_pin
*iface
,
350 unsigned int index
, AM_MEDIA_TYPE
*mt
)
352 struct acm_wrapper
*filter
= impl_from_strmbase_filter(iface
->filter
);
355 return VFW_S_NO_MORE_ITEMS
;
356 CopyMediaType(mt
, &filter
->mt
);
360 static HRESULT WINAPI
acm_wrapper_source_DecideBufferSize(struct strmbase_source
*iface
,
361 IMemAllocator
*pAlloc
, ALLOCATOR_PROPERTIES
*ppropInputRequest
)
363 struct acm_wrapper
*filter
= impl_from_strmbase_filter(iface
->pin
.filter
);
364 ALLOCATOR_PROPERTIES actual
;
366 if (!ppropInputRequest
->cbAlign
)
367 ppropInputRequest
->cbAlign
= 1;
369 if (ppropInputRequest
->cbBuffer
< filter
->pWfOut
->nAvgBytesPerSec
/ 2)
370 ppropInputRequest
->cbBuffer
= filter
->pWfOut
->nAvgBytesPerSec
/ 2;
372 if (!ppropInputRequest
->cBuffers
)
373 ppropInputRequest
->cBuffers
= 1;
375 return IMemAllocator_SetProperties(pAlloc
, ppropInputRequest
, &actual
);
378 static const struct strmbase_source_ops source_ops
=
380 .base
.pin_query_interface
= acm_wrapper_source_query_interface
,
381 .base
.pin_query_accept
= acm_wrapper_source_query_accept
,
382 .base
.pin_get_media_type
= acm_wrapper_source_get_media_type
,
383 .pfnAttemptConnection
= BaseOutputPinImpl_AttemptConnection
,
384 .pfnDecideAllocator
= BaseOutputPinImpl_DecideAllocator
,
385 .pfnDecideBufferSize
= acm_wrapper_source_DecideBufferSize
,
388 static struct acm_wrapper
*impl_from_source_IQualityControl(IQualityControl
*iface
)
390 return CONTAINING_RECORD(iface
, struct acm_wrapper
, source_IQualityControl_iface
);
393 static HRESULT WINAPI
acm_wrapper_source_qc_QueryInterface(IQualityControl
*iface
,
394 REFIID iid
, void **out
)
396 struct acm_wrapper
*filter
= impl_from_source_IQualityControl(iface
);
397 return IPin_QueryInterface(&filter
->source
.pin
.IPin_iface
, iid
, out
);
400 static ULONG WINAPI
acm_wrapper_source_qc_AddRef(IQualityControl
*iface
)
402 struct acm_wrapper
*filter
= impl_from_source_IQualityControl(iface
);
403 return IPin_AddRef(&filter
->source
.pin
.IPin_iface
);
406 static ULONG WINAPI
acm_wrapper_source_qc_Release(IQualityControl
*iface
)
408 struct acm_wrapper
*filter
= impl_from_source_IQualityControl(iface
);
409 return IPin_Release(&filter
->source
.pin
.IPin_iface
);
412 static HRESULT WINAPI
acm_wrapper_source_qc_Notify(IQualityControl
*iface
,
413 IBaseFilter
*sender
, Quality q
)
415 struct acm_wrapper
*filter
= impl_from_source_IQualityControl(iface
);
416 IQualityControl
*peer
;
419 TRACE("filter %p, sender %p, type %#x, proportion %ld, late %s, timestamp %s.\n",
420 filter
, sender
, q
.Type
, q
.Proportion
, debugstr_time(q
.Late
), debugstr_time(q
.TimeStamp
));
422 if (filter
->source_qc_sink
)
423 return IQualityControl_Notify(filter
->source_qc_sink
, &filter
->filter
.IBaseFilter_iface
, q
);
425 if (filter
->sink
.pin
.peer
426 && SUCCEEDED(IPin_QueryInterface(filter
->sink
.pin
.peer
, &IID_IQualityControl
, (void **)&peer
)))
428 hr
= IQualityControl_Notify(peer
, &filter
->filter
.IBaseFilter_iface
, q
);
429 IQualityControl_Release(peer
);
434 static HRESULT WINAPI
acm_wrapper_source_qc_SetSink(IQualityControl
*iface
, IQualityControl
*sink
)
436 struct acm_wrapper
*filter
= impl_from_source_IQualityControl(iface
);
438 TRACE("filter %p, sink %p.\n", filter
, sink
);
440 filter
->source_qc_sink
= sink
;
445 static const IQualityControlVtbl source_qc_vtbl
=
447 acm_wrapper_source_qc_QueryInterface
,
448 acm_wrapper_source_qc_AddRef
,
449 acm_wrapper_source_qc_Release
,
450 acm_wrapper_source_qc_Notify
,
451 acm_wrapper_source_qc_SetSink
,
454 static struct strmbase_pin
*acm_wrapper_get_pin(struct strmbase_filter
*iface
, unsigned int index
)
456 struct acm_wrapper
*filter
= impl_from_strmbase_filter(iface
);
459 return &filter
->sink
.pin
;
461 return &filter
->source
.pin
;
465 static void acm_wrapper_destroy(struct strmbase_filter
*iface
)
467 struct acm_wrapper
*filter
= impl_from_strmbase_filter(iface
);
469 if (filter
->sink
.pin
.peer
)
470 IPin_Disconnect(filter
->sink
.pin
.peer
);
471 IPin_Disconnect(&filter
->sink
.pin
.IPin_iface
);
473 if (filter
->source
.pin
.peer
)
474 IPin_Disconnect(filter
->source
.pin
.peer
);
475 IPin_Disconnect(&filter
->source
.pin
.IPin_iface
);
477 strmbase_sink_cleanup(&filter
->sink
);
478 strmbase_source_cleanup(&filter
->source
);
479 strmbase_passthrough_cleanup(&filter
->passthrough
);
481 FreeMediaType(&filter
->mt
);
482 strmbase_filter_cleanup(&filter
->filter
);
486 static HRESULT
acm_wrapper_init_stream(struct strmbase_filter
*iface
)
488 struct acm_wrapper
*filter
= impl_from_strmbase_filter(iface
);
491 if (filter
->source
.pin
.peer
&& FAILED(hr
= IMemAllocator_Commit(filter
->source
.pAllocator
)))
492 ERR("Failed to commit allocator, hr %#lx.\n", hr
);
496 static HRESULT
acm_wrapper_cleanup_stream(struct strmbase_filter
*iface
)
498 struct acm_wrapper
*filter
= impl_from_strmbase_filter(iface
);
500 if (filter
->source
.pin
.peer
)
501 IMemAllocator_Decommit(filter
->source
.pAllocator
);
505 static const struct strmbase_filter_ops filter_ops
=
507 .filter_get_pin
= acm_wrapper_get_pin
,
508 .filter_destroy
= acm_wrapper_destroy
,
509 .filter_init_stream
= acm_wrapper_init_stream
,
510 .filter_cleanup_stream
= acm_wrapper_cleanup_stream
,
513 HRESULT
acm_wrapper_create(IUnknown
*outer
, IUnknown
**out
)
515 struct acm_wrapper
*object
;
517 if (!(object
= calloc(1, sizeof(*object
))))
518 return E_OUTOFMEMORY
;
520 strmbase_filter_init(&object
->filter
, outer
, &CLSID_ACMWrapper
, &filter_ops
);
522 strmbase_sink_init(&object
->sink
, &object
->filter
, L
"In", &sink_ops
, NULL
);
523 wcscpy(object
->sink
.pin
.name
, L
"Input");
525 strmbase_source_init(&object
->source
, &object
->filter
, L
"Out", &source_ops
);
526 wcscpy(object
->source
.pin
.name
, L
"Output");
528 object
->source_IQualityControl_iface
.lpVtbl
= &source_qc_vtbl
;
529 strmbase_passthrough_init(&object
->passthrough
, (IUnknown
*)&object
->source
.pin
.IPin_iface
);
530 ISeekingPassThru_Init(&object
->passthrough
.ISeekingPassThru_iface
, FALSE
,
531 &object
->sink
.pin
.IPin_iface
);
533 object
->lasttime_real
= object
->lasttime_sent
= -1;
535 TRACE("Created ACM wrapper %p.\n", object
);
536 *out
= &object
->filter
.IUnknown_inner
;