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
;
41 CRITICAL_SECTION stream_cs
;
43 struct strmbase_source source
;
44 IQualityControl source_IQualityControl_iface
;
45 IQualityControl
*source_qc_sink
;
46 struct strmbase_passthrough passthrough
;
48 struct strmbase_sink sink
;
52 LPWAVEFORMATEX pWfOut
;
54 LONGLONG lasttime_real
;
55 LONGLONG lasttime_sent
;
58 static struct acm_wrapper
*impl_from_strmbase_filter(struct strmbase_filter
*iface
)
60 return CONTAINING_RECORD(iface
, struct acm_wrapper
, filter
);
63 static HRESULT
acm_wrapper_sink_query_interface(struct strmbase_pin
*iface
, REFIID iid
, void **out
)
65 struct acm_wrapper
*filter
= impl_from_strmbase_filter(iface
->filter
);
67 if (IsEqualGUID(iid
, &IID_IMemInputPin
))
68 *out
= &filter
->sink
.IMemInputPin_iface
;
72 IUnknown_AddRef((IUnknown
*)*out
);
76 static HRESULT
acm_wrapper_sink_query_accept(struct strmbase_pin
*iface
, const AM_MEDIA_TYPE
*mt
)
81 static HRESULT WINAPI
acm_wrapper_sink_Receive(struct strmbase_sink
*iface
, IMediaSample
*pSample
)
83 struct acm_wrapper
*This
= impl_from_strmbase_filter(iface
->pin
.filter
);
84 IMediaSample
* pOutSample
= NULL
;
85 DWORD cbDstStream
, cbSrcStream
;
87 LPBYTE pbSrcStream
= NULL
;
89 BOOL unprepare_header
= FALSE
, preroll
;
92 LONGLONG tStart
= -1, tStop
= -1, tMed
;
93 LONGLONG mtStart
= -1, mtStop
= -1, mtMed
;
95 /* We do not expect pin connection state to change while the filter is
96 * running. This guarantee is necessary, since otherwise we would have to
97 * take the filter lock, and we can't take the filter lock from a streaming
99 if (!This
->source
.pMemInputPin
)
101 WARN("Source is not connected, returning VFW_E_NOT_CONNECTED.\n");
102 return VFW_E_NOT_CONNECTED
;
105 if (This
->filter
.state
== State_Stopped
)
106 return VFW_E_WRONG_STATE
;
108 if (This
->sink
.flushing
)
111 EnterCriticalSection(&This
->stream_cs
);
113 hr
= IMediaSample_GetPointer(pSample
, &pbSrcStream
);
116 ERR("Cannot get pointer to sample data (%x)\n", hr
);
117 LeaveCriticalSection(&This
->stream_cs
);
121 preroll
= (IMediaSample_IsPreroll(pSample
) == S_OK
);
123 IMediaSample_GetTime(pSample
, &tStart
, &tStop
);
124 if (IMediaSample_GetMediaTime(pSample
, &mtStart
, &mtStop
) != S_OK
)
125 mtStart
= mtStop
= -1;
126 cbSrcStream
= IMediaSample_GetActualDataLength(pSample
);
128 /* Prevent discontinuities when codecs 'absorb' data but not give anything back in return */
129 if (IMediaSample_IsDiscontinuity(pSample
) == S_OK
)
131 This
->lasttime_real
= tStart
;
132 This
->lasttime_sent
= tStart
;
134 else if (This
->lasttime_real
== tStart
)
135 tStart
= This
->lasttime_sent
;
137 WARN("Discontinuity\n");
142 TRACE("Sample data ptr = %p, size = %d\n", pbSrcStream
, cbSrcStream
);
144 ash
.pbSrc
= pbSrcStream
;
145 ash
.cbSrcLength
= cbSrcStream
;
147 while(hr
== S_OK
&& ash
.cbSrcLength
)
149 hr
= BaseOutputPinImpl_GetDeliveryBuffer(&This
->source
, &pOutSample
, NULL
, NULL
, 0);
152 ERR("Unable to get delivery buffer (%x)\n", hr
);
153 LeaveCriticalSection(&This
->stream_cs
);
156 IMediaSample_SetPreroll(pOutSample
, preroll
);
158 hr
= IMediaSample_SetActualDataLength(pOutSample
, 0);
161 hr
= IMediaSample_GetPointer(pOutSample
, &pbDstStream
);
163 ERR("Unable to get pointer to buffer (%x)\n", hr
);
166 cbDstStream
= IMediaSample_GetSize(pOutSample
);
168 ash
.cbStruct
= sizeof(ash
);
171 ash
.pbDst
= pbDstStream
;
172 ash
.cbDstLength
= cbDstStream
;
174 if ((res
= acmStreamPrepareHeader(This
->has
, &ash
, 0))) {
175 ERR("Cannot prepare header %d\n", res
);
178 unprepare_header
= TRUE
;
180 if (IMediaSample_IsDiscontinuity(pSample
) == S_OK
)
182 res
= acmStreamConvert(This
->has
, &ash
, ACM_STREAMCONVERTF_START
);
183 IMediaSample_SetDiscontinuity(pOutSample
, TRUE
);
184 /* One sample could be converted to multiple packets */
185 IMediaSample_SetDiscontinuity(pSample
, FALSE
);
189 res
= acmStreamConvert(This
->has
, &ash
, 0);
190 IMediaSample_SetDiscontinuity(pOutSample
, FALSE
);
195 if(res
!= MMSYSERR_MOREDATA
)
196 ERR("Cannot convert data header %d\n", res
);
200 TRACE("used in %u/%u, used out %u/%u\n", ash
.cbSrcLengthUsed
, ash
.cbSrcLength
, ash
.cbDstLengthUsed
, ash
.cbDstLength
);
202 hr
= IMediaSample_SetActualDataLength(pOutSample
, ash
.cbDstLengthUsed
);
205 /* Bug in acm codecs? It apparently uses the input, but doesn't necessarily output immediately */
206 if (!ash
.cbSrcLengthUsed
)
208 WARN("Sample was skipped? Outputted: %u\n", ash
.cbDstLengthUsed
);
213 TRACE("Sample start time: %s.\n", debugstr_time(tStart
));
214 if (ash
.cbSrcLengthUsed
== cbSrcStream
)
216 IMediaSample_SetTime(pOutSample
, &tStart
, &tStop
);
217 tStart
= tMed
= tStop
;
219 else if (tStop
!= tStart
)
221 tMed
= tStop
- tStart
;
222 tMed
= tStart
+ tMed
* ash
.cbSrcLengthUsed
/ cbSrcStream
;
223 IMediaSample_SetTime(pOutSample
, &tStart
, &tMed
);
228 ERR("No valid timestamp found\n");
229 IMediaSample_SetTime(pOutSample
, NULL
, NULL
);
233 IMediaSample_SetMediaTime(pOutSample
, NULL
, NULL
);
234 } else if (ash
.cbSrcLengthUsed
== cbSrcStream
) {
235 IMediaSample_SetMediaTime(pOutSample
, &mtStart
, &mtStop
);
236 mtStart
= mtMed
= mtStop
;
237 } else if (mtStop
>= mtStart
) {
238 mtMed
= mtStop
- mtStart
;
239 mtMed
= mtStart
+ mtMed
* ash
.cbSrcLengthUsed
/ cbSrcStream
;
240 IMediaSample_SetMediaTime(pOutSample
, &mtStart
, &mtMed
);
243 IMediaSample_SetMediaTime(pOutSample
, NULL
, NULL
);
246 TRACE("Sample stop time: %s\n", debugstr_time(tStart
));
248 hr
= IMemInputPin_Receive(This
->source
.pMemInputPin
, pOutSample
);
249 if (hr
!= S_OK
&& hr
!= VFW_E_NOT_CONNECTED
) {
251 ERR("Error sending sample (%x)\n", hr
);
256 if (unprepare_header
&& (res
= acmStreamUnprepareHeader(This
->has
, &ash
, 0)))
257 ERR("Cannot unprepare header %d\n", res
);
258 unprepare_header
= FALSE
;
259 ash
.pbSrc
+= ash
.cbSrcLengthUsed
;
260 ash
.cbSrcLength
-= ash
.cbSrcLengthUsed
;
262 IMediaSample_Release(pOutSample
);
267 This
->lasttime_real
= tStop
;
268 This
->lasttime_sent
= tMed
;
270 LeaveCriticalSection(&This
->stream_cs
);
274 static BOOL
is_audio_subtype(const GUID
*guid
)
276 return !memcmp(&guid
->Data2
, &MEDIATYPE_Audio
.Data2
, sizeof(GUID
) - sizeof(int));
279 static HRESULT
acm_wrapper_sink_connect(struct strmbase_sink
*iface
, IPin
*peer
, const AM_MEDIA_TYPE
*mt
)
281 struct acm_wrapper
*filter
= impl_from_strmbase_filter(iface
->pin
.filter
);
282 const WAVEFORMATEX
*wfx
= (WAVEFORMATEX
*)mt
->pbFormat
;
286 if (!IsEqualGUID(&mt
->majortype
, &MEDIATYPE_Audio
) || !is_audio_subtype(&mt
->subtype
)
287 || !IsEqualGUID(&mt
->formattype
, &FORMAT_WaveFormatEx
) || !wfx
288 || wfx
->wFormatTag
== WAVE_FORMAT_PCM
|| wfx
->wFormatTag
== WAVE_FORMAT_EXTENSIBLE
)
289 return VFW_E_TYPE_NOT_ACCEPTED
;
291 CopyMediaType(&filter
->mt
, mt
);
292 filter
->mt
.subtype
.Data1
= WAVE_FORMAT_PCM
;
293 filter
->pWfOut
= (WAVEFORMATEX
*)filter
->mt
.pbFormat
;
294 filter
->pWfOut
->wFormatTag
= WAVE_FORMAT_PCM
;
295 filter
->pWfOut
->wBitsPerSample
= 16;
296 filter
->pWfOut
->nBlockAlign
= filter
->pWfOut
->wBitsPerSample
* filter
->pWfOut
->nChannels
/ 8;
297 filter
->pWfOut
->cbSize
= 0;
298 filter
->pWfOut
->nAvgBytesPerSec
= filter
->pWfOut
->nChannels
* filter
->pWfOut
->nSamplesPerSec
299 * (filter
->pWfOut
->wBitsPerSample
/ 8);
301 if ((res
= acmStreamOpen(&drv
, NULL
, (WAVEFORMATEX
*)wfx
, filter
->pWfOut
, NULL
, 0, 0, 0)))
303 ERR("Failed to open stream, error %u.\n", res
);
304 FreeMediaType(&filter
->mt
);
305 return VFW_E_TYPE_NOT_ACCEPTED
;
313 static void acm_wrapper_sink_disconnect(struct strmbase_sink
*iface
)
315 struct acm_wrapper
*filter
= impl_from_strmbase_filter(iface
->pin
.filter
);
318 acmStreamClose(filter
->has
, 0);
320 filter
->lasttime_real
= filter
->lasttime_sent
= -1;
323 static const struct strmbase_sink_ops sink_ops
=
325 .base
.pin_query_interface
= acm_wrapper_sink_query_interface
,
326 .base
.pin_query_accept
= acm_wrapper_sink_query_accept
,
327 .base
.pin_get_media_type
= strmbase_pin_get_media_type
,
328 .pfnReceive
= acm_wrapper_sink_Receive
,
329 .sink_connect
= acm_wrapper_sink_connect
,
330 .sink_disconnect
= acm_wrapper_sink_disconnect
,
333 static HRESULT
acm_wrapper_source_query_interface(struct strmbase_pin
*iface
, REFIID iid
, void **out
)
335 struct acm_wrapper
*filter
= impl_from_strmbase_filter(iface
->filter
);
337 if (IsEqualGUID(iid
, &IID_IQualityControl
))
338 *out
= &filter
->source_IQualityControl_iface
;
339 else if (IsEqualGUID(iid
, &IID_IMediaSeeking
))
340 *out
= &filter
->passthrough
.IMediaSeeking_iface
;
342 return E_NOINTERFACE
;
344 IUnknown_AddRef((IUnknown
*)*out
);
348 static HRESULT
acm_wrapper_source_query_accept(struct strmbase_pin
*iface
, const AM_MEDIA_TYPE
*mt
)
350 struct acm_wrapper
*filter
= impl_from_strmbase_filter(iface
->filter
);
352 if (IsEqualGUID(&mt
->majortype
, &filter
->mt
.majortype
)
353 && (IsEqualGUID(&mt
->subtype
, &filter
->mt
.subtype
)
354 || IsEqualGUID(&filter
->mt
.subtype
, &GUID_NULL
)))
359 static HRESULT
acm_wrapper_source_get_media_type(struct strmbase_pin
*iface
,
360 unsigned int index
, AM_MEDIA_TYPE
*mt
)
362 struct acm_wrapper
*filter
= impl_from_strmbase_filter(iface
->filter
);
365 return VFW_S_NO_MORE_ITEMS
;
366 CopyMediaType(mt
, &filter
->mt
);
370 static HRESULT WINAPI
acm_wrapper_source_DecideBufferSize(struct strmbase_source
*iface
,
371 IMemAllocator
*pAlloc
, ALLOCATOR_PROPERTIES
*ppropInputRequest
)
373 struct acm_wrapper
*filter
= impl_from_strmbase_filter(iface
->pin
.filter
);
374 ALLOCATOR_PROPERTIES actual
;
376 if (!ppropInputRequest
->cbAlign
)
377 ppropInputRequest
->cbAlign
= 1;
379 if (ppropInputRequest
->cbBuffer
< filter
->pWfOut
->nAvgBytesPerSec
/ 2)
380 ppropInputRequest
->cbBuffer
= filter
->pWfOut
->nAvgBytesPerSec
/ 2;
382 if (!ppropInputRequest
->cBuffers
)
383 ppropInputRequest
->cBuffers
= 1;
385 return IMemAllocator_SetProperties(pAlloc
, ppropInputRequest
, &actual
);
388 static const struct strmbase_source_ops source_ops
=
390 .base
.pin_query_interface
= acm_wrapper_source_query_interface
,
391 .base
.pin_query_accept
= acm_wrapper_source_query_accept
,
392 .base
.pin_get_media_type
= acm_wrapper_source_get_media_type
,
393 .pfnAttemptConnection
= BaseOutputPinImpl_AttemptConnection
,
394 .pfnDecideAllocator
= BaseOutputPinImpl_DecideAllocator
,
395 .pfnDecideBufferSize
= acm_wrapper_source_DecideBufferSize
,
398 static struct acm_wrapper
*impl_from_source_IQualityControl(IQualityControl
*iface
)
400 return CONTAINING_RECORD(iface
, struct acm_wrapper
, source_IQualityControl_iface
);
403 static HRESULT WINAPI
acm_wrapper_source_qc_QueryInterface(IQualityControl
*iface
,
404 REFIID iid
, void **out
)
406 struct acm_wrapper
*filter
= impl_from_source_IQualityControl(iface
);
407 return IPin_QueryInterface(&filter
->source
.pin
.IPin_iface
, iid
, out
);
410 static ULONG WINAPI
acm_wrapper_source_qc_AddRef(IQualityControl
*iface
)
412 struct acm_wrapper
*filter
= impl_from_source_IQualityControl(iface
);
413 return IPin_AddRef(&filter
->source
.pin
.IPin_iface
);
416 static ULONG WINAPI
acm_wrapper_source_qc_Release(IQualityControl
*iface
)
418 struct acm_wrapper
*filter
= impl_from_source_IQualityControl(iface
);
419 return IPin_Release(&filter
->source
.pin
.IPin_iface
);
422 static HRESULT WINAPI
acm_wrapper_source_qc_Notify(IQualityControl
*iface
,
423 IBaseFilter
*sender
, Quality q
)
425 struct acm_wrapper
*filter
= impl_from_source_IQualityControl(iface
);
426 IQualityControl
*peer
;
429 TRACE("filter %p, sender %p, type %#x, proportion %u, late %s, timestamp %s.\n",
430 filter
, sender
, q
.Type
, q
.Proportion
, debugstr_time(q
.Late
), debugstr_time(q
.TimeStamp
));
432 if (filter
->source_qc_sink
)
433 return IQualityControl_Notify(filter
->source_qc_sink
, &filter
->filter
.IBaseFilter_iface
, q
);
435 if (filter
->sink
.pin
.peer
436 && SUCCEEDED(IPin_QueryInterface(filter
->sink
.pin
.peer
, &IID_IQualityControl
, (void **)&peer
)))
438 hr
= IQualityControl_Notify(peer
, &filter
->filter
.IBaseFilter_iface
, q
);
439 IQualityControl_Release(peer
);
444 static HRESULT WINAPI
acm_wrapper_source_qc_SetSink(IQualityControl
*iface
, IQualityControl
*sink
)
446 struct acm_wrapper
*filter
= impl_from_source_IQualityControl(iface
);
448 TRACE("filter %p, sink %p.\n", filter
, sink
);
450 filter
->source_qc_sink
= sink
;
455 static const IQualityControlVtbl source_qc_vtbl
=
457 acm_wrapper_source_qc_QueryInterface
,
458 acm_wrapper_source_qc_AddRef
,
459 acm_wrapper_source_qc_Release
,
460 acm_wrapper_source_qc_Notify
,
461 acm_wrapper_source_qc_SetSink
,
464 static struct strmbase_pin
*acm_wrapper_get_pin(struct strmbase_filter
*iface
, unsigned int index
)
466 struct acm_wrapper
*filter
= impl_from_strmbase_filter(iface
);
469 return &filter
->sink
.pin
;
471 return &filter
->source
.pin
;
475 static void acm_wrapper_destroy(struct strmbase_filter
*iface
)
477 struct acm_wrapper
*filter
= impl_from_strmbase_filter(iface
);
479 if (filter
->sink
.pin
.peer
)
480 IPin_Disconnect(filter
->sink
.pin
.peer
);
481 IPin_Disconnect(&filter
->sink
.pin
.IPin_iface
);
483 if (filter
->source
.pin
.peer
)
484 IPin_Disconnect(filter
->source
.pin
.peer
);
485 IPin_Disconnect(&filter
->source
.pin
.IPin_iface
);
487 strmbase_sink_cleanup(&filter
->sink
);
488 strmbase_source_cleanup(&filter
->source
);
489 strmbase_passthrough_cleanup(&filter
->passthrough
);
491 filter
->stream_cs
.DebugInfo
->Spare
[0] = 0;
492 DeleteCriticalSection(&filter
->stream_cs
);
493 FreeMediaType(&filter
->mt
);
494 strmbase_filter_cleanup(&filter
->filter
);
497 InterlockedDecrement(&object_locks
);
500 static HRESULT
acm_wrapper_init_stream(struct strmbase_filter
*iface
)
502 struct acm_wrapper
*filter
= impl_from_strmbase_filter(iface
);
505 if (filter
->source
.pin
.peer
&& FAILED(hr
= IMemAllocator_Commit(filter
->source
.pAllocator
)))
506 ERR("Failed to commit allocator, hr %#x.\n", hr
);
510 static HRESULT
acm_wrapper_cleanup_stream(struct strmbase_filter
*iface
)
512 struct acm_wrapper
*filter
= impl_from_strmbase_filter(iface
);
514 if (filter
->source
.pin
.peer
)
515 IMemAllocator_Decommit(filter
->source
.pAllocator
);
519 static const struct strmbase_filter_ops filter_ops
=
521 .filter_get_pin
= acm_wrapper_get_pin
,
522 .filter_destroy
= acm_wrapper_destroy
,
523 .filter_init_stream
= acm_wrapper_init_stream
,
524 .filter_cleanup_stream
= acm_wrapper_cleanup_stream
,
527 HRESULT
acm_wrapper_create(IUnknown
*outer
, IUnknown
**out
)
529 struct acm_wrapper
*object
;
531 if (!(object
= calloc(1, sizeof(*object
))))
532 return E_OUTOFMEMORY
;
534 strmbase_filter_init(&object
->filter
, outer
, &CLSID_ACMWrapper
, &filter_ops
);
536 InitializeCriticalSection(&object
->stream_cs
);
537 object
->stream_cs
.DebugInfo
->Spare
[0] = (DWORD_PTR
)(__FILE__
": acm_wrapper.stream_cs");
539 strmbase_sink_init(&object
->sink
, &object
->filter
, L
"In", &sink_ops
, NULL
);
541 strmbase_source_init(&object
->source
, &object
->filter
, L
"Out", &source_ops
);
542 object
->source_IQualityControl_iface
.lpVtbl
= &source_qc_vtbl
;
543 strmbase_passthrough_init(&object
->passthrough
, (IUnknown
*)&object
->source
.pin
.IPin_iface
);
544 ISeekingPassThru_Init(&object
->passthrough
.ISeekingPassThru_iface
, FALSE
,
545 &object
->sink
.pin
.IPin_iface
);
547 object
->lasttime_real
= object
->lasttime_sent
= -1;
549 TRACE("Created ACM wrapper %p.\n", object
);
550 *out
= &object
->filter
.IUnknown_inner
;