1 /* 2 * ACM Wrapper 3 * 4 * Copyright 2005 Christian Costa 5 * 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. 10 * 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. 15 * 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 19 */ 20 21 #include "config.h" 22 23 #include "quartz_private.h" 24 #include "pin.h" 25 26 #include "uuids.h" 27 #include "mmreg.h" 28 #include "windef.h" 29 #include "winbase.h" 30 #include "dshow.h" 31 #include "strmif.h" 32 #include "vfwmsgs.h" 33 #include "msacm.h" 34 35 #include <assert.h> 36 37 #include "wine/unicode.h" 38 #include "wine/debug.h" 39 40 WINE_DEFAULT_DEBUG_CHANNEL(quartz); 41 42 typedef struct ACMWrapperImpl 43 { 44 TransformFilter tf; 45 46 HACMSTREAM has; 47 LPWAVEFORMATEX pWfIn; 48 LPWAVEFORMATEX pWfOut; 49 50 LONGLONG lasttime_real; 51 LONGLONG lasttime_sent; 52 } ACMWrapperImpl; 53 54 static const IBaseFilterVtbl ACMWrapper_Vtbl; 55 56 static inline ACMWrapperImpl *impl_from_TransformFilter( TransformFilter *iface ) 57 { 58 return CONTAINING_RECORD(iface, ACMWrapperImpl, tf); 59 } 60 61 static HRESULT WINAPI ACMWrapper_Receive(TransformFilter *tf, IMediaSample *pSample) 62 { 63 ACMWrapperImpl* This = impl_from_TransformFilter(tf); 64 AM_MEDIA_TYPE amt; 65 IMediaSample* pOutSample = NULL; 66 DWORD cbDstStream, cbSrcStream; 67 LPBYTE pbDstStream; 68 LPBYTE pbSrcStream = NULL; 69 ACMSTREAMHEADER ash; 70 BOOL unprepare_header = FALSE, preroll; 71 MMRESULT res; 72 HRESULT hr; 73 LONGLONG tStart = -1, tStop = -1, tMed; 74 LONGLONG mtStart = -1, mtStop = -1, mtMed; 75 76 EnterCriticalSection(&This->tf.csReceive); 77 hr = IMediaSample_GetPointer(pSample, &pbSrcStream); 78 if (FAILED(hr)) 79 { 80 ERR("Cannot get pointer to sample data (%x)\n", hr); 81 LeaveCriticalSection(&This->tf.csReceive); 82 return hr; 83 } 84 85 preroll = (IMediaSample_IsPreroll(pSample) == S_OK); 86 87 IMediaSample_GetTime(pSample, &tStart, &tStop); 88 if (IMediaSample_GetMediaTime(pSample, &mtStart, &mtStop) != S_OK) 89 mtStart = mtStop = -1; 90 cbSrcStream = IMediaSample_GetActualDataLength(pSample); 91 92 /* Prevent discontinuities when codecs 'absorb' data but not give anything back in return */ 93 if (IMediaSample_IsDiscontinuity(pSample) == S_OK) 94 { 95 This->lasttime_real = tStart; 96 This->lasttime_sent = tStart; 97 } 98 else if (This->lasttime_real == tStart) 99 tStart = This->lasttime_sent; 100 else 101 WARN("Discontinuity\n"); 102 103 tMed = tStart; 104 mtMed = mtStart; 105 106 TRACE("Sample data ptr = %p, size = %d\n", pbSrcStream, cbSrcStream); 107 108 hr = IPin_ConnectionMediaType(This->tf.ppPins[0], &amt); 109 if (FAILED(hr)) 110 { 111 ERR("Unable to retrieve media type\n"); 112 LeaveCriticalSection(&This->tf.csReceive); 113 return hr; 114 } 115 116 ash.pbSrc = pbSrcStream; 117 ash.cbSrcLength = cbSrcStream; 118 119 while(hr == S_OK && ash.cbSrcLength) 120 { 121 hr = BaseOutputPinImpl_GetDeliveryBuffer((BaseOutputPin*)This->tf.ppPins[1], &pOutSample, NULL, NULL, 0); 122 if (FAILED(hr)) 123 { 124 ERR("Unable to get delivery buffer (%x)\n", hr); 125 LeaveCriticalSection(&This->tf.csReceive); 126 return hr; 127 } 128 IMediaSample_SetPreroll(pOutSample, preroll); 129 130 hr = IMediaSample_SetActualDataLength(pOutSample, 0); 131 assert(hr == S_OK); 132 133 hr = IMediaSample_GetPointer(pOutSample, &pbDstStream); 134 if (FAILED(hr)) { 135 ERR("Unable to get pointer to buffer (%x)\n", hr); 136 goto error; 137 } 138 cbDstStream = IMediaSample_GetSize(pOutSample); 139 140 ash.cbStruct = sizeof(ash); 141 ash.fdwStatus = 0; 142 ash.dwUser = 0; 143 ash.pbDst = pbDstStream; 144 ash.cbDstLength = cbDstStream; 145 146 if ((res = acmStreamPrepareHeader(This->has, &ash, 0))) { 147 ERR("Cannot prepare header %d\n", res); 148 goto error; 149 } 150 unprepare_header = TRUE; 151 152 if (IMediaSample_IsDiscontinuity(pSample) == S_OK) 153 { 154 res = acmStreamConvert(This->has, &ash, ACM_STREAMCONVERTF_START); 155 IMediaSample_SetDiscontinuity(pOutSample, TRUE); 156 /* One sample could be converted to multiple packets */ 157 IMediaSample_SetDiscontinuity(pSample, FALSE); 158 } 159 else 160 { 161 res = acmStreamConvert(This->has, &ash, 0); 162 IMediaSample_SetDiscontinuity(pOutSample, FALSE); 163 } 164 165 if (res) 166 { 167 if(res != MMSYSERR_MOREDATA) 168 ERR("Cannot convert data header %d\n", res); 169 goto error; 170 } 171 172 TRACE("used in %u/%u, used out %u/%u\n", ash.cbSrcLengthUsed, ash.cbSrcLength, ash.cbDstLengthUsed, ash.cbDstLength); 173 174 hr = IMediaSample_SetActualDataLength(pOutSample, ash.cbDstLengthUsed); 175 assert(hr == S_OK); 176 177 /* Bug in acm codecs? It apparently uses the input, but doesn't necessarily output immediately */ 178 if (!ash.cbSrcLengthUsed) 179 { 180 WARN("Sample was skipped? Outputted: %u\n", ash.cbDstLengthUsed); 181 ash.cbSrcLength = 0; 182 goto error; 183 } 184 185 TRACE("Sample start time: %u.%03u\n", (DWORD)(tStart/10000000), (DWORD)((tStart/10000)%1000)); 186 if (ash.cbSrcLengthUsed == cbSrcStream) 187 { 188 IMediaSample_SetTime(pOutSample, &tStart, &tStop); 189 tStart = tMed = tStop; 190 } 191 else if (tStop != tStart) 192 { 193 tMed = tStop - tStart; 194 tMed = tStart + tMed * ash.cbSrcLengthUsed / cbSrcStream; 195 IMediaSample_SetTime(pOutSample, &tStart, &tMed); 196 tStart = tMed; 197 } 198 else 199 { 200 ERR("No valid timestamp found\n"); 201 IMediaSample_SetTime(pOutSample, NULL, NULL); 202 } 203 204 if (mtStart < 0) { 205 IMediaSample_SetMediaTime(pOutSample, NULL, NULL); 206 } else if (ash.cbSrcLengthUsed == cbSrcStream) { 207 IMediaSample_SetMediaTime(pOutSample, &mtStart, &mtStop); 208 mtStart = mtMed = mtStop; 209 } else if (mtStop >= mtStart) { 210 mtMed = mtStop - mtStart; 211 mtMed = mtStart + mtMed * ash.cbSrcLengthUsed / cbSrcStream; 212 IMediaSample_SetMediaTime(pOutSample, &mtStart, &mtMed); 213 mtStart = mtMed; 214 } else { 215 IMediaSample_SetMediaTime(pOutSample, NULL, NULL); 216 } 217 218 TRACE("Sample stop time: %u.%03u\n", (DWORD)(tStart/10000000), (DWORD)((tStart/10000)%1000)); 219 220 LeaveCriticalSection(&This->tf.csReceive); 221 hr = BaseOutputPinImpl_Deliver((BaseOutputPin*)This->tf.ppPins[1], pOutSample); 222 EnterCriticalSection(&This->tf.csReceive); 223 224 if (hr != S_OK && hr != VFW_E_NOT_CONNECTED) { 225 if (FAILED(hr)) 226 ERR("Error sending sample (%x)\n", hr); 227 goto error; 228 } 229 230 error: 231 if (unprepare_header && (res = acmStreamUnprepareHeader(This->has, &ash, 0))) 232 ERR("Cannot unprepare header %d\n", res); 233 unprepare_header = FALSE; 234 ash.pbSrc += ash.cbSrcLengthUsed; 235 ash.cbSrcLength -= ash.cbSrcLengthUsed; 236 237 IMediaSample_Release(pOutSample); 238 pOutSample = NULL; 239 240 } 241 242 This->lasttime_real = tStop; 243 This->lasttime_sent = tMed; 244 245 LeaveCriticalSection(&This->tf.csReceive); 246 return hr; 247 } 248 249 static HRESULT WINAPI ACMWrapper_SetMediaType(TransformFilter *tf, PIN_DIRECTION dir, const AM_MEDIA_TYPE * pmt) 250 { 251 ACMWrapperImpl* This = impl_from_TransformFilter(tf); 252 MMRESULT res; 253 254 TRACE("(%p)->(%i %p)\n", This, dir, pmt); 255 256 if (dir != PINDIR_INPUT) 257 return S_OK; 258 259 /* Check root (GUID w/o FOURCC) */ 260 if ((IsEqualIID(&pmt->majortype, &MEDIATYPE_Audio)) && 261 (!memcmp(((const char *)&pmt->subtype)+4, ((const char *)&MEDIATYPE_Audio)+4, sizeof(GUID)-4)) && 262 (IsEqualIID(&pmt->formattype, &FORMAT_WaveFormatEx))) 263 { 264 HACMSTREAM drv; 265 WAVEFORMATEX *wfx = (WAVEFORMATEX*)pmt->pbFormat; 266 AM_MEDIA_TYPE* outpmt = &This->tf.pmt; 267 268 if (!wfx || wfx->wFormatTag == WAVE_FORMAT_PCM || wfx->wFormatTag == WAVE_FORMAT_EXTENSIBLE) 269 return VFW_E_TYPE_NOT_ACCEPTED; 270 FreeMediaType(outpmt); 271 272 This->pWfIn = (LPWAVEFORMATEX)pmt->pbFormat; 273 274 /* HACK */ 275 /* TRACE("ALIGN = %d\n", pACMWrapper->pWfIn->nBlockAlign); */ 276 /* pACMWrapper->pWfIn->nBlockAlign = 1; */ 277 278 /* Set output audio data to PCM */ 279 CopyMediaType(outpmt, pmt); 280 outpmt->subtype.Data1 = WAVE_FORMAT_PCM; 281 This->pWfOut = (WAVEFORMATEX*)outpmt->pbFormat; 282 This->pWfOut->wFormatTag = WAVE_FORMAT_PCM; 283 This->pWfOut->wBitsPerSample = 16; 284 This->pWfOut->nBlockAlign = This->pWfOut->wBitsPerSample * This->pWfOut->nChannels / 8; 285 This->pWfOut->cbSize = 0; 286 This->pWfOut->nAvgBytesPerSec = This->pWfOut->nChannels * This->pWfOut->nSamplesPerSec 287 * (This->pWfOut->wBitsPerSample/8); 288 289 if (!(res = acmStreamOpen(&drv, NULL, This->pWfIn, This->pWfOut, NULL, 0, 0, 0))) 290 { 291 This->has = drv; 292 293 TRACE("Connection accepted\n"); 294 return S_OK; 295 } 296 else 297 FIXME("acmStreamOpen returned %d\n", res); 298 FreeMediaType(outpmt); 299 TRACE("Unable to find a suitable ACM decompressor\n"); 300 } 301 302 TRACE("Connection refused\n"); 303 return VFW_E_TYPE_NOT_ACCEPTED; 304 } 305 306 static HRESULT WINAPI ACMWrapper_CompleteConnect(TransformFilter *tf, PIN_DIRECTION dir, IPin *pin) 307 { 308 ACMWrapperImpl* This = impl_from_TransformFilter(tf); 309 MMRESULT res; 310 HACMSTREAM drv; 311 312 TRACE("(%p)\n", This); 313 314 if (dir != PINDIR_INPUT) 315 return S_OK; 316 317 if (!(res = acmStreamOpen(&drv, NULL, This->pWfIn, This->pWfOut, NULL, 0, 0, 0))) 318 { 319 This->has = drv; 320 321 TRACE("Connection accepted\n"); 322 return S_OK; 323 } 324 325 FIXME("acmStreamOpen returned %d\n", res); 326 TRACE("Unable to find a suitable ACM decompressor\n"); 327 return VFW_E_TYPE_NOT_ACCEPTED; 328 } 329 330 static HRESULT WINAPI ACMWrapper_BreakConnect(TransformFilter *tf, PIN_DIRECTION dir) 331 { 332 ACMWrapperImpl *This = impl_from_TransformFilter(tf); 333 334 TRACE("(%p)->(%i)\n", This,dir); 335 336 if (dir == PINDIR_INPUT) 337 { 338 if (This->has) 339 acmStreamClose(This->has, 0); 340 341 This->has = 0; 342 This->lasttime_real = This->lasttime_sent = -1; 343 } 344 345 return S_OK; 346 } 347 348 static HRESULT WINAPI ACMWrapper_DecideBufferSize(TransformFilter *tf, IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *ppropInputRequest) 349 { 350 ACMWrapperImpl *pACM = impl_from_TransformFilter(tf); 351 ALLOCATOR_PROPERTIES actual; 352 353 if (!ppropInputRequest->cbAlign) 354 ppropInputRequest->cbAlign = 1; 355 356 if (ppropInputRequest->cbBuffer < pACM->pWfOut->nAvgBytesPerSec / 2) 357 ppropInputRequest->cbBuffer = pACM->pWfOut->nAvgBytesPerSec / 2; 358 359 if (!ppropInputRequest->cBuffers) 360 ppropInputRequest->cBuffers = 1; 361 362 return IMemAllocator_SetProperties(pAlloc, ppropInputRequest, &actual); 363 } 364 365 static const TransformFilterFuncTable ACMWrapper_FuncsTable = { 366 ACMWrapper_DecideBufferSize, 367 NULL, 368 ACMWrapper_Receive, 369 NULL, 370 NULL, 371 ACMWrapper_SetMediaType, 372 ACMWrapper_CompleteConnect, 373 ACMWrapper_BreakConnect, 374 NULL, 375 NULL, 376 NULL, 377 NULL 378 }; 379 380 HRESULT ACMWrapper_create(IUnknown * pUnkOuter, LPVOID * ppv) 381 { 382 HRESULT hr; 383 ACMWrapperImpl* This; 384 385 TRACE("(%p, %p)\n", pUnkOuter, ppv); 386 387 *ppv = NULL; 388 389 if (pUnkOuter) 390 return CLASS_E_NOAGGREGATION; 391 392 hr = TransformFilter_Construct(&ACMWrapper_Vtbl, sizeof(ACMWrapperImpl), &CLSID_ACMWrapper, &ACMWrapper_FuncsTable, (IBaseFilter**)&This); 393 394 if (FAILED(hr)) 395 return hr; 396 397 *ppv = &This->tf.filter.IBaseFilter_iface; 398 This->lasttime_real = This->lasttime_sent = -1; 399 400 return hr; 401 } 402 403 static const IBaseFilterVtbl ACMWrapper_Vtbl = 404 { 405 TransformFilterImpl_QueryInterface, 406 BaseFilterImpl_AddRef, 407 TransformFilterImpl_Release, 408 BaseFilterImpl_GetClassID, 409 TransformFilterImpl_Stop, 410 TransformFilterImpl_Pause, 411 TransformFilterImpl_Run, 412 BaseFilterImpl_GetState, 413 BaseFilterImpl_SetSyncSource, 414 BaseFilterImpl_GetSyncSource, 415 BaseFilterImpl_EnumPins, 416 TransformFilterImpl_FindPin, 417 BaseFilterImpl_QueryFilterInfo, 418 BaseFilterImpl_JoinFilterGraph, 419 BaseFilterImpl_QueryVendorInfo 420 }; 421