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("Cannot get pointer to sample data (%x)\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 TRACE("Sample data ptr = %p, size = %d\n", pbSrcStream
, cbSrcStream
);
140 ash
.pbSrc
= pbSrcStream
;
141 ash
.cbSrcLength
= cbSrcStream
;
143 while(hr
== S_OK
&& ash
.cbSrcLength
)
145 hr
= BaseOutputPinImpl_GetDeliveryBuffer(&This
->source
, &pOutSample
, NULL
, NULL
, 0);
148 ERR("Unable to get delivery buffer (%x)\n", hr
);
151 IMediaSample_SetPreroll(pOutSample
, preroll
);
153 hr
= IMediaSample_SetActualDataLength(pOutSample
, 0);
156 hr
= IMediaSample_GetPointer(pOutSample
, &pbDstStream
);
158 ERR("Unable to get pointer to buffer (%x)\n", hr
);
161 cbDstStream
= IMediaSample_GetSize(pOutSample
);
163 ash
.cbStruct
= sizeof(ash
);
166 ash
.pbDst
= pbDstStream
;
167 ash
.cbDstLength
= cbDstStream
;
169 if ((res
= acmStreamPrepareHeader(This
->has
, &ash
, 0))) {
170 ERR("Cannot prepare header %d\n", res
);
173 unprepare_header
= TRUE
;
175 if (IMediaSample_IsDiscontinuity(pSample
) == S_OK
)
177 res
= acmStreamConvert(This
->has
, &ash
, ACM_STREAMCONVERTF_START
);
178 IMediaSample_SetDiscontinuity(pOutSample
, TRUE
);
179 /* One sample could be converted to multiple packets */
180 IMediaSample_SetDiscontinuity(pSample
, FALSE
);
184 res
= acmStreamConvert(This
->has
, &ash
, 0);
185 IMediaSample_SetDiscontinuity(pOutSample
, FALSE
);
190 if(res
!= MMSYSERR_MOREDATA
)
191 ERR("Cannot convert data header %d\n", res
);
195 TRACE("used in %u/%u, used out %u/%u\n", ash
.cbSrcLengthUsed
, ash
.cbSrcLength
, ash
.cbDstLengthUsed
, ash
.cbDstLength
);
197 hr
= IMediaSample_SetActualDataLength(pOutSample
, ash
.cbDstLengthUsed
);
200 /* Bug in acm codecs? It apparently uses the input, but doesn't necessarily output immediately */
201 if (!ash
.cbSrcLengthUsed
)
203 WARN("Sample was skipped? Outputted: %u\n", ash
.cbDstLengthUsed
);
208 TRACE("Sample start time: %s.\n", debugstr_time(tStart
));
209 if (ash
.cbSrcLengthUsed
== cbSrcStream
)
211 IMediaSample_SetTime(pOutSample
, &tStart
, &tStop
);
212 tStart
= tMed
= tStop
;
214 else if (tStop
!= tStart
)
216 tMed
= tStop
- tStart
;
217 tMed
= tStart
+ tMed
* ash
.cbSrcLengthUsed
/ cbSrcStream
;
218 IMediaSample_SetTime(pOutSample
, &tStart
, &tMed
);
223 ERR("No valid timestamp found\n");
224 IMediaSample_SetTime(pOutSample
, NULL
, NULL
);
228 IMediaSample_SetMediaTime(pOutSample
, NULL
, NULL
);
229 } else if (ash
.cbSrcLengthUsed
== cbSrcStream
) {
230 IMediaSample_SetMediaTime(pOutSample
, &mtStart
, &mtStop
);
231 mtStart
= mtMed
= mtStop
;
232 } else if (mtStop
>= mtStart
) {
233 mtMed
= mtStop
- mtStart
;
234 mtMed
= mtStart
+ mtMed
* ash
.cbSrcLengthUsed
/ cbSrcStream
;
235 IMediaSample_SetMediaTime(pOutSample
, &mtStart
, &mtMed
);
238 IMediaSample_SetMediaTime(pOutSample
, NULL
, NULL
);
241 TRACE("Sample stop time: %s\n", debugstr_time(tStart
));
243 hr
= IMemInputPin_Receive(This
->source
.pMemInputPin
, pOutSample
);
244 if (hr
!= S_OK
&& hr
!= VFW_E_NOT_CONNECTED
) {
246 ERR("Error sending sample (%x)\n", hr
);
251 if (unprepare_header
&& (res
= acmStreamUnprepareHeader(This
->has
, &ash
, 0)))
252 ERR("Cannot unprepare header %d\n", res
);
253 unprepare_header
= FALSE
;
254 ash
.pbSrc
+= ash
.cbSrcLengthUsed
;
255 ash
.cbSrcLength
-= ash
.cbSrcLengthUsed
;
257 IMediaSample_Release(pOutSample
);
262 This
->lasttime_real
= tStop
;
263 This
->lasttime_sent
= tMed
;
268 static BOOL
is_audio_subtype(const GUID
*guid
)
270 return !memcmp(&guid
->Data2
, &MEDIATYPE_Audio
.Data2
, sizeof(GUID
) - sizeof(int));
273 static HRESULT
acm_wrapper_sink_connect(struct strmbase_sink
*iface
, IPin
*peer
, const AM_MEDIA_TYPE
*mt
)
275 struct acm_wrapper
*filter
= impl_from_strmbase_filter(iface
->pin
.filter
);
276 const WAVEFORMATEX
*wfx
= (WAVEFORMATEX
*)mt
->pbFormat
;
280 if (!IsEqualGUID(&mt
->majortype
, &MEDIATYPE_Audio
) || !is_audio_subtype(&mt
->subtype
)
281 || !IsEqualGUID(&mt
->formattype
, &FORMAT_WaveFormatEx
) || !wfx
282 || wfx
->wFormatTag
== WAVE_FORMAT_PCM
|| wfx
->wFormatTag
== WAVE_FORMAT_EXTENSIBLE
)
283 return VFW_E_TYPE_NOT_ACCEPTED
;
285 CopyMediaType(&filter
->mt
, mt
);
286 filter
->mt
.subtype
.Data1
= WAVE_FORMAT_PCM
;
287 filter
->pWfOut
= (WAVEFORMATEX
*)filter
->mt
.pbFormat
;
288 filter
->pWfOut
->wFormatTag
= WAVE_FORMAT_PCM
;
289 filter
->pWfOut
->wBitsPerSample
= 16;
290 filter
->pWfOut
->nBlockAlign
= filter
->pWfOut
->wBitsPerSample
* filter
->pWfOut
->nChannels
/ 8;
291 filter
->pWfOut
->cbSize
= 0;
292 filter
->pWfOut
->nAvgBytesPerSec
= filter
->pWfOut
->nChannels
* filter
->pWfOut
->nSamplesPerSec
293 * (filter
->pWfOut
->wBitsPerSample
/ 8);
295 if ((res
= acmStreamOpen(&drv
, NULL
, (WAVEFORMATEX
*)wfx
, filter
->pWfOut
, NULL
, 0, 0, 0)))
297 ERR("Failed to open stream, error %u.\n", res
);
298 FreeMediaType(&filter
->mt
);
299 return VFW_E_TYPE_NOT_ACCEPTED
;
307 static void acm_wrapper_sink_disconnect(struct strmbase_sink
*iface
)
309 struct acm_wrapper
*filter
= impl_from_strmbase_filter(iface
->pin
.filter
);
312 acmStreamClose(filter
->has
, 0);
314 filter
->lasttime_real
= filter
->lasttime_sent
= -1;
317 static const struct strmbase_sink_ops sink_ops
=
319 .base
.pin_query_interface
= acm_wrapper_sink_query_interface
,
320 .base
.pin_query_accept
= acm_wrapper_sink_query_accept
,
321 .pfnReceive
= acm_wrapper_sink_Receive
,
322 .sink_connect
= acm_wrapper_sink_connect
,
323 .sink_disconnect
= acm_wrapper_sink_disconnect
,
326 static HRESULT
acm_wrapper_source_query_interface(struct strmbase_pin
*iface
, REFIID iid
, void **out
)
328 struct acm_wrapper
*filter
= impl_from_strmbase_filter(iface
->filter
);
330 if (IsEqualGUID(iid
, &IID_IQualityControl
))
331 *out
= &filter
->source_IQualityControl_iface
;
332 else if (IsEqualGUID(iid
, &IID_IMediaSeeking
))
333 *out
= &filter
->passthrough
.IMediaSeeking_iface
;
335 return E_NOINTERFACE
;
337 IUnknown_AddRef((IUnknown
*)*out
);
341 static HRESULT
acm_wrapper_source_query_accept(struct strmbase_pin
*iface
, const AM_MEDIA_TYPE
*mt
)
343 struct acm_wrapper
*filter
= impl_from_strmbase_filter(iface
->filter
);
345 if (IsEqualGUID(&mt
->majortype
, &filter
->mt
.majortype
)
346 && (IsEqualGUID(&mt
->subtype
, &filter
->mt
.subtype
)
347 || IsEqualGUID(&filter
->mt
.subtype
, &GUID_NULL
)))
352 static HRESULT
acm_wrapper_source_get_media_type(struct strmbase_pin
*iface
,
353 unsigned int index
, AM_MEDIA_TYPE
*mt
)
355 struct acm_wrapper
*filter
= impl_from_strmbase_filter(iface
->filter
);
358 return VFW_S_NO_MORE_ITEMS
;
359 CopyMediaType(mt
, &filter
->mt
);
363 static HRESULT WINAPI
acm_wrapper_source_DecideBufferSize(struct strmbase_source
*iface
,
364 IMemAllocator
*pAlloc
, ALLOCATOR_PROPERTIES
*ppropInputRequest
)
366 struct acm_wrapper
*filter
= impl_from_strmbase_filter(iface
->pin
.filter
);
367 ALLOCATOR_PROPERTIES actual
;
369 if (!ppropInputRequest
->cbAlign
)
370 ppropInputRequest
->cbAlign
= 1;
372 if (ppropInputRequest
->cbBuffer
< filter
->pWfOut
->nAvgBytesPerSec
/ 2)
373 ppropInputRequest
->cbBuffer
= filter
->pWfOut
->nAvgBytesPerSec
/ 2;
375 if (!ppropInputRequest
->cBuffers
)
376 ppropInputRequest
->cBuffers
= 1;
378 return IMemAllocator_SetProperties(pAlloc
, ppropInputRequest
, &actual
);
381 static const struct strmbase_source_ops source_ops
=
383 .base
.pin_query_interface
= acm_wrapper_source_query_interface
,
384 .base
.pin_query_accept
= acm_wrapper_source_query_accept
,
385 .base
.pin_get_media_type
= acm_wrapper_source_get_media_type
,
386 .pfnAttemptConnection
= BaseOutputPinImpl_AttemptConnection
,
387 .pfnDecideAllocator
= BaseOutputPinImpl_DecideAllocator
,
388 .pfnDecideBufferSize
= acm_wrapper_source_DecideBufferSize
,
391 static struct acm_wrapper
*impl_from_source_IQualityControl(IQualityControl
*iface
)
393 return CONTAINING_RECORD(iface
, struct acm_wrapper
, source_IQualityControl_iface
);
396 static HRESULT WINAPI
acm_wrapper_source_qc_QueryInterface(IQualityControl
*iface
,
397 REFIID iid
, void **out
)
399 struct acm_wrapper
*filter
= impl_from_source_IQualityControl(iface
);
400 return IPin_QueryInterface(&filter
->source
.pin
.IPin_iface
, iid
, out
);
403 static ULONG WINAPI
acm_wrapper_source_qc_AddRef(IQualityControl
*iface
)
405 struct acm_wrapper
*filter
= impl_from_source_IQualityControl(iface
);
406 return IPin_AddRef(&filter
->source
.pin
.IPin_iface
);
409 static ULONG WINAPI
acm_wrapper_source_qc_Release(IQualityControl
*iface
)
411 struct acm_wrapper
*filter
= impl_from_source_IQualityControl(iface
);
412 return IPin_Release(&filter
->source
.pin
.IPin_iface
);
415 static HRESULT WINAPI
acm_wrapper_source_qc_Notify(IQualityControl
*iface
,
416 IBaseFilter
*sender
, Quality q
)
418 struct acm_wrapper
*filter
= impl_from_source_IQualityControl(iface
);
419 IQualityControl
*peer
;
422 TRACE("filter %p, sender %p, type %#x, proportion %u, late %s, timestamp %s.\n",
423 filter
, sender
, q
.Type
, q
.Proportion
, debugstr_time(q
.Late
), debugstr_time(q
.TimeStamp
));
425 if (filter
->source_qc_sink
)
426 return IQualityControl_Notify(filter
->source_qc_sink
, &filter
->filter
.IBaseFilter_iface
, q
);
428 if (filter
->sink
.pin
.peer
429 && SUCCEEDED(IPin_QueryInterface(filter
->sink
.pin
.peer
, &IID_IQualityControl
, (void **)&peer
)))
431 hr
= IQualityControl_Notify(peer
, &filter
->filter
.IBaseFilter_iface
, q
);
432 IQualityControl_Release(peer
);
437 static HRESULT WINAPI
acm_wrapper_source_qc_SetSink(IQualityControl
*iface
, IQualityControl
*sink
)
439 struct acm_wrapper
*filter
= impl_from_source_IQualityControl(iface
);
441 TRACE("filter %p, sink %p.\n", filter
, sink
);
443 filter
->source_qc_sink
= sink
;
448 static const IQualityControlVtbl source_qc_vtbl
=
450 acm_wrapper_source_qc_QueryInterface
,
451 acm_wrapper_source_qc_AddRef
,
452 acm_wrapper_source_qc_Release
,
453 acm_wrapper_source_qc_Notify
,
454 acm_wrapper_source_qc_SetSink
,
457 static struct strmbase_pin
*acm_wrapper_get_pin(struct strmbase_filter
*iface
, unsigned int index
)
459 struct acm_wrapper
*filter
= impl_from_strmbase_filter(iface
);
462 return &filter
->sink
.pin
;
464 return &filter
->source
.pin
;
468 static void acm_wrapper_destroy(struct strmbase_filter
*iface
)
470 struct acm_wrapper
*filter
= impl_from_strmbase_filter(iface
);
472 if (filter
->sink
.pin
.peer
)
473 IPin_Disconnect(filter
->sink
.pin
.peer
);
474 IPin_Disconnect(&filter
->sink
.pin
.IPin_iface
);
476 if (filter
->source
.pin
.peer
)
477 IPin_Disconnect(filter
->source
.pin
.peer
);
478 IPin_Disconnect(&filter
->source
.pin
.IPin_iface
);
480 strmbase_sink_cleanup(&filter
->sink
);
481 strmbase_source_cleanup(&filter
->source
);
482 strmbase_passthrough_cleanup(&filter
->passthrough
);
484 FreeMediaType(&filter
->mt
);
485 strmbase_filter_cleanup(&filter
->filter
);
489 static HRESULT
acm_wrapper_init_stream(struct strmbase_filter
*iface
)
491 struct acm_wrapper
*filter
= impl_from_strmbase_filter(iface
);
494 if (filter
->source
.pin
.peer
&& FAILED(hr
= IMemAllocator_Commit(filter
->source
.pAllocator
)))
495 ERR("Failed to commit allocator, hr %#x.\n", hr
);
499 static HRESULT
acm_wrapper_cleanup_stream(struct strmbase_filter
*iface
)
501 struct acm_wrapper
*filter
= impl_from_strmbase_filter(iface
);
503 if (filter
->source
.pin
.peer
)
504 IMemAllocator_Decommit(filter
->source
.pAllocator
);
508 static const struct strmbase_filter_ops filter_ops
=
510 .filter_get_pin
= acm_wrapper_get_pin
,
511 .filter_destroy
= acm_wrapper_destroy
,
512 .filter_init_stream
= acm_wrapper_init_stream
,
513 .filter_cleanup_stream
= acm_wrapper_cleanup_stream
,
516 HRESULT
acm_wrapper_create(IUnknown
*outer
, IUnknown
**out
)
518 struct acm_wrapper
*object
;
520 if (!(object
= calloc(1, sizeof(*object
))))
521 return E_OUTOFMEMORY
;
523 strmbase_filter_init(&object
->filter
, outer
, &CLSID_ACMWrapper
, &filter_ops
);
525 strmbase_sink_init(&object
->sink
, &object
->filter
, L
"In", &sink_ops
, NULL
);
527 strmbase_source_init(&object
->source
, &object
->filter
, L
"Out", &source_ops
);
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
;