Commit | Line | Data |
---|---|---|
80a2e6e9 CC |
1 | /* |
2 | * WAVE Parser Filter | |
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 | |
360a3f91 | 18 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA |
80a2e6e9 CC |
19 | */ |
20 | ||
21 | #include "quartz_private.h" | |
22 | #include "control_private.h" | |
23 | #include "pin.h" | |
24 | ||
25 | #include "uuids.h" | |
26 | #include "aviriff.h" | |
80a2e6e9 CC |
27 | #include "vfwmsgs.h" |
28 | #include "mmsystem.h" | |
29 | ||
80a2e6e9 CC |
30 | #include "wine/unicode.h" |
31 | #include "wine/debug.h" | |
32 | ||
33 | #include <math.h> | |
34 | #include <assert.h> | |
35 | ||
36 | #include "parser.h" | |
37 | ||
38 | WINE_DEFAULT_DEBUG_CHANNEL(quartz); | |
39 | ||
40 | static const WCHAR wcsOutputPinName[] = {'o','u','t','p','u','t',' ','p','i','n',0}; | |
41 | ||
42 | typedef struct WAVEParserImpl | |
43 | { | |
44 | ParserImpl Parser; | |
80a2e6e9 CC |
45 | LONGLONG StartOfFile; /* in media time */ |
46 | LONGLONG EndOfFile; | |
796bb923 | 47 | DWORD dwSampleSize; |
ebe438c8 | 48 | DWORD nSamplesPerSec; |
796bb923 | 49 | DWORD dwLength; |
80a2e6e9 CC |
50 | } WAVEParserImpl; |
51 | ||
602d44a1 AS |
52 | static inline WAVEParserImpl *impl_from_IMediaSeeking( IMediaSeeking *iface ) |
53 | { | |
54 | return (WAVEParserImpl*)((char*)iface - FIELD_OFFSET(WAVEParserImpl, Parser.sourceSeeking.lpVtbl)); | |
55 | } | |
56 | ||
ebe438c8 ML |
57 | static LONGLONG bytepos_to_duration(WAVEParserImpl *This, LONGLONG bytepos) |
58 | { | |
59 | LONGLONG duration = BYTES_FROM_MEDIATIME(bytepos - This->StartOfFile); | |
60 | duration *= 10000000; | |
61 | duration /= (This->dwSampleSize * This->nSamplesPerSec); | |
62 | ||
63 | return duration; | |
64 | } | |
65 | ||
66 | static LONGLONG duration_to_bytepos(WAVEParserImpl *This, LONGLONG duration) | |
67 | { | |
68 | LONGLONG bytepos; | |
69 | ||
70 | bytepos = (This->dwSampleSize * This->nSamplesPerSec); | |
71 | bytepos *= duration; | |
72 | bytepos /= 10000000; | |
73 | bytepos += BYTES_FROM_MEDIATIME(This->StartOfFile); | |
74 | bytepos -= bytepos % This->dwSampleSize; | |
75 | ||
76 | return MEDIATIME_FROM_BYTES(bytepos); | |
77 | } | |
78 | ||
a0224676 | 79 | static HRESULT WAVEParser_Sample(LPVOID iface, IMediaSample * pSample, DWORD_PTR cookie) |
80a2e6e9 | 80 | { |
cc7fc4ab | 81 | WAVEParserImpl *This = iface; |
80a2e6e9 | 82 | LPBYTE pbSrcStream = NULL; |
3a39805e | 83 | ULONG cbSrcStream = 0; |
80a2e6e9 CC |
84 | REFERENCE_TIME tStart, tStop; |
85 | HRESULT hr; | |
3a39805e ML |
86 | IMediaSample *newsample = NULL; |
87 | Parser_OutputPin *pOutputPin; | |
88 | PullPin *pin = This->Parser.pInputPin; | |
80a2e6e9 | 89 | |
3a39805e | 90 | IMediaSample_GetPointer(pSample, &pbSrcStream); |
80a2e6e9 CC |
91 | hr = IMediaSample_GetTime(pSample, &tStart, &tStop); |
92 | ||
93 | cbSrcStream = IMediaSample_GetActualDataLength(pSample); | |
94 | ||
633ee950 | 95 | /* Flush occurring */ |
3a39805e ML |
96 | if (cbSrcStream == 0) |
97 | { | |
98 | TRACE(".. Why do I need you?\n"); | |
99 | return S_OK; | |
100 | } | |
80a2e6e9 CC |
101 | |
102 | pOutputPin = (Parser_OutputPin *)This->Parser.ppPins[1]; | |
103 | ||
3a39805e ML |
104 | if (SUCCEEDED(hr)) |
105 | hr = IMemAllocator_GetBuffer(pin->pAlloc, &newsample, NULL, NULL, 0); | |
80a2e6e9 | 106 | |
3a39805e | 107 | if (SUCCEEDED(hr)) |
80a2e6e9 | 108 | { |
3a39805e ML |
109 | LONGLONG rtSampleStart = pin->rtNext; |
110 | /* Add 4 for the next header, which should hopefully work */ | |
111 | LONGLONG rtSampleStop = rtSampleStart + MEDIATIME_FROM_BYTES(IMediaSample_GetSize(newsample)); | |
80a2e6e9 | 112 | |
3a39805e ML |
113 | if (rtSampleStop > pin->rtStop) |
114 | rtSampleStop = MEDIATIME_FROM_BYTES(ALIGNUP(BYTES_FROM_MEDIATIME(pin->rtStop), pin->cbAlign)); | |
80a2e6e9 | 115 | |
3a39805e | 116 | hr = IMediaSample_SetTime(newsample, &rtSampleStart, &rtSampleStop); |
80a2e6e9 | 117 | |
3a39805e ML |
118 | pin->rtCurrent = pin->rtNext; |
119 | pin->rtNext = rtSampleStop; | |
80a2e6e9 | 120 | |
3a39805e ML |
121 | IMediaSample_SetPreroll(newsample, 0); |
122 | IMediaSample_SetDiscontinuity(newsample, 0); | |
123 | IMediaSample_SetSyncPoint(newsample, 1); | |
80a2e6e9 | 124 | |
3a39805e ML |
125 | hr = IAsyncReader_Request(pin->pReader, newsample, 0); |
126 | } | |
cbb0ff37 | 127 | |
3a39805e ML |
128 | if (SUCCEEDED(hr)) |
129 | { | |
130 | REFERENCE_TIME tAviStart, tAviStop; | |
80a2e6e9 | 131 | |
3a39805e ML |
132 | IMediaSample_SetSyncPoint(pSample, TRUE); |
133 | pOutputPin->dwSamplesProcessed++; | |
ebe438c8 | 134 | |
3a39805e ML |
135 | tAviStart = bytepos_to_duration(This, tStart); |
136 | tAviStop = bytepos_to_duration(This, tStart + MEDIATIME_FROM_BYTES(IMediaSample_GetActualDataLength(pSample))); | |
80a2e6e9 | 137 | |
3a39805e | 138 | IMediaSample_SetTime(pSample, &tAviStart, &tAviStop); |
80a2e6e9 | 139 | |
5c1409b5 | 140 | hr = BaseOutputPinImpl_Deliver(&pOutputPin->pin, pSample); |
8099a624 | 141 | if (hr != S_OK && hr != S_FALSE && hr != VFW_E_WRONG_STATE) |
3a39805e | 142 | ERR("Error sending sample (%x)\n", hr); |
8099a624 ML |
143 | else if (hr != S_OK) |
144 | /* Unset progression if denied! */ | |
145 | This->Parser.pInputPin->rtCurrent = tStart; | |
80a2e6e9 CC |
146 | } |
147 | ||
602d44a1 | 148 | if (tStop >= This->EndOfFile || (bytepos_to_duration(This, tStop) >= This->Parser.sourceSeeking.llStop) || hr == VFW_E_NOT_CONNECTED) |
7dea79c4 | 149 | { |
a19ff5f0 | 150 | unsigned int i; |
7dea79c4 CC |
151 | |
152 | TRACE("End of file reached\n"); | |
153 | ||
154 | for (i = 0; i < This->Parser.cStreams; i++) | |
155 | { | |
156 | IPin* ppin; | |
157 | HRESULT hr; | |
158 | ||
a19ff5f0 | 159 | TRACE("Send End Of Stream to output pin %u\n", i); |
7dea79c4 CC |
160 | |
161 | hr = IPin_ConnectedTo(This->Parser.ppPins[i+1], &ppin); | |
162 | if (SUCCEEDED(hr)) | |
163 | { | |
164 | hr = IPin_EndOfStream(ppin); | |
165 | IPin_Release(ppin); | |
166 | } | |
167 | if (FAILED(hr)) | |
168 | { | |
cfbb859f | 169 | ERR("%x\n", hr); |
7dea79c4 CC |
170 | break; |
171 | } | |
172 | } | |
173 | ||
174 | /* Force the pullpin thread to stop */ | |
175 | hr = S_FALSE; | |
176 | } | |
177 | ||
80a2e6e9 CC |
178 | return hr; |
179 | } | |
180 | ||
181 | static HRESULT WAVEParser_QueryAccept(LPVOID iface, const AM_MEDIA_TYPE * pmt) | |
182 | { | |
183 | if (!IsEqualIID(&pmt->majortype, &MEDIATYPE_Stream)) | |
184 | return S_FALSE; | |
185 | if (IsEqualIID(&pmt->subtype, &MEDIASUBTYPE_WAVE)) | |
186 | return S_OK; | |
187 | if (IsEqualIID(&pmt->subtype, &MEDIASUBTYPE_AU) || IsEqualIID(&pmt->subtype, &MEDIASUBTYPE_AIFF)) | |
188 | FIXME("AU and AIFF files not supported yet!\n"); | |
189 | return S_FALSE; | |
190 | } | |
191 | ||
602d44a1 | 192 | static HRESULT WINAPI WAVEParserImpl_seek(IMediaSeeking *iface) |
ebe438c8 | 193 | { |
602d44a1 | 194 | WAVEParserImpl *This = impl_from_IMediaSeeking(iface); |
ebe438c8 ML |
195 | PullPin *pPin = This->Parser.pInputPin; |
196 | IPin *victim = NULL; | |
197 | LONGLONG newpos, curpos, endpos, bytepos; | |
198 | ||
602d44a1 | 199 | newpos = This->Parser.sourceSeeking.llCurrent; |
ebe438c8 ML |
200 | curpos = bytepos_to_duration(This, pPin->rtCurrent); |
201 | endpos = bytepos_to_duration(This, This->EndOfFile); | |
202 | bytepos = duration_to_bytepos(This, newpos); | |
203 | ||
204 | if (newpos > endpos) | |
205 | { | |
206 | WARN("Requesting position %x%08x beyond end of stream %x%08x\n", (DWORD)(newpos>>32), (DWORD)newpos, (DWORD)(endpos>>32), (DWORD)endpos); | |
207 | return E_INVALIDARG; | |
208 | } | |
209 | ||
210 | if (curpos/1000000 == newpos/1000000) | |
211 | { | |
212 | TRACE("Requesting position %x%08x same as current position %x%08x\n", (DWORD)(newpos>>32), (DWORD)newpos, (DWORD)(curpos>>32), (DWORD)curpos); | |
213 | return S_OK; | |
214 | } | |
215 | ||
216 | TRACE("Moving sound to %08u bytes!\n", (DWORD)BYTES_FROM_MEDIATIME(bytepos)); | |
217 | ||
218 | EnterCriticalSection(&pPin->thread_lock); | |
219 | IPin_BeginFlush((IPin *)pPin); | |
220 | ||
221 | /* Make sure this is done while stopped, BeginFlush takes care of this */ | |
1d42659c | 222 | EnterCriticalSection(&This->Parser.filter.csFilter); |
ebe438c8 ML |
223 | IPin_ConnectedTo(This->Parser.ppPins[1], &victim); |
224 | if (victim) | |
225 | { | |
226 | IPin_NewSegment(victim, newpos, endpos, pPin->dRate); | |
227 | IPin_Release(victim); | |
228 | } | |
229 | ||
230 | pPin->rtStart = pPin->rtCurrent = bytepos; | |
cbb0ff37 | 231 | ((Parser_OutputPin *)This->Parser.ppPins[1])->dwSamplesProcessed = 0; |
1d42659c | 232 | LeaveCriticalSection(&This->Parser.filter.csFilter); |
ebe438c8 ML |
233 | |
234 | TRACE("Done flushing\n"); | |
235 | IPin_EndFlush((IPin *)pPin); | |
236 | LeaveCriticalSection(&pPin->thread_lock); | |
237 | ||
238 | return S_OK; | |
239 | } | |
240 | ||
3a39805e | 241 | static HRESULT WAVEParser_InputPin_PreConnect(IPin * iface, IPin * pConnectPin, ALLOCATOR_PROPERTIES *props) |
80a2e6e9 CC |
242 | { |
243 | PullPin *This = (PullPin *)iface; | |
244 | HRESULT hr; | |
245 | RIFFLIST list; | |
246 | RIFFCHUNK chunk; | |
247 | LONGLONG pos = 0; /* in bytes */ | |
248 | PIN_INFO piOutput; | |
80a2e6e9 | 249 | AM_MEDIA_TYPE amt; |
80a2e6e9 | 250 | WAVEParserImpl * pWAVEParser = (WAVEParserImpl *)This->pin.pinInfo.pFilter; |
796bb923 | 251 | LONGLONG length, avail; |
80a2e6e9 CC |
252 | |
253 | piOutput.dir = PINDIR_OUTPUT; | |
254 | piOutput.pFilter = (IBaseFilter *)This; | |
e732fc02 | 255 | lstrcpynW(piOutput.achName, wcsOutputPinName, sizeof(piOutput.achName) / sizeof(piOutput.achName[0])); |
80a2e6e9 CC |
256 | |
257 | hr = IAsyncReader_SyncRead(This->pReader, pos, sizeof(list), (BYTE *)&list); | |
258 | pos += sizeof(list); | |
259 | ||
0b326924 | 260 | if (list.fcc != FOURCC_RIFF) |
80a2e6e9 CC |
261 | { |
262 | ERR("Input stream not a RIFF file\n"); | |
263 | return E_FAIL; | |
264 | } | |
265 | if (list.cb > 1 * 1024 * 1024 * 1024) /* cannot be more than 1Gb in size */ | |
266 | { | |
267 | ERR("Input stream violates RIFF spec\n"); | |
268 | return E_FAIL; | |
269 | } | |
270 | if (list.fccListType != mmioFOURCC('W','A','V','E')) | |
271 | { | |
272 | ERR("Input stream not an WAVE RIFF file\n"); | |
273 | return E_FAIL; | |
274 | } | |
275 | ||
276 | hr = IAsyncReader_SyncRead(This->pReader, pos, sizeof(chunk), (BYTE *)&chunk); | |
277 | pos += sizeof(chunk); | |
278 | if (chunk.fcc != mmioFOURCC('f','m','t',' ')) | |
279 | { | |
280 | ERR("Expected 'fmt ' chunk, but got %.04s\n", (LPSTR)&chunk.fcc); | |
281 | return E_FAIL; | |
282 | } | |
283 | ||
442f29ab AT |
284 | amt.majortype = MEDIATYPE_Audio; |
285 | amt.formattype = FORMAT_WaveFormatEx; | |
80a2e6e9 CC |
286 | amt.cbFormat = chunk.cb; |
287 | amt.pbFormat = CoTaskMemAlloc(amt.cbFormat); | |
288 | amt.pUnk = NULL; | |
289 | hr = IAsyncReader_SyncRead(This->pReader, pos, amt.cbFormat, amt.pbFormat); | |
442f29ab | 290 | amt.subtype = MEDIATYPE_Audio; |
80a2e6e9 | 291 | amt.subtype.Data1 = ((WAVEFORMATEX*)amt.pbFormat)->wFormatTag; |
80a2e6e9 CC |
292 | |
293 | pos += chunk.cb; | |
294 | hr = IAsyncReader_SyncRead(This->pReader, pos, sizeof(chunk), (BYTE *)&chunk); | |
295 | if (chunk.fcc == mmioFOURCC('f','a','c','t')) | |
296 | { | |
297 | FIXME("'fact' chunk not supported yet\n"); | |
298 | pos += sizeof(chunk) + chunk.cb; | |
299 | hr = IAsyncReader_SyncRead(This->pReader, pos, sizeof(chunk), (BYTE *)&chunk); | |
300 | } | |
301 | if (chunk.fcc != mmioFOURCC('d','a','t','a')) | |
302 | { | |
303 | ERR("Expected 'data' chunk, but got %.04s\n", (LPSTR)&chunk.fcc); | |
304 | return E_FAIL; | |
305 | } | |
306 | ||
307 | if (hr == S_OK) | |
308 | { | |
309 | pWAVEParser->StartOfFile = MEDIATIME_FROM_BYTES(pos + sizeof(RIFFCHUNK)); | |
310 | pWAVEParser->EndOfFile = MEDIATIME_FROM_BYTES(pos + chunk.cb + sizeof(RIFFCHUNK)); | |
311 | } | |
312 | ||
313 | if (hr != S_OK) | |
314 | return E_FAIL; | |
315 | ||
3a39805e ML |
316 | props->cbAlign = ((WAVEFORMATEX*)amt.pbFormat)->nBlockAlign; |
317 | props->cbPrefix = 0; | |
318 | props->cbBuffer = 4096; | |
8099a624 | 319 | props->cBuffers = 3; |
796bb923 ML |
320 | pWAVEParser->dwSampleSize = ((WAVEFORMATEX*)amt.pbFormat)->nBlockAlign; |
321 | IAsyncReader_Length(This->pReader, &length, &avail); | |
322 | pWAVEParser->dwLength = length / (ULONGLONG)pWAVEParser->dwSampleSize; | |
ebe438c8 | 323 | pWAVEParser->nSamplesPerSec = ((WAVEFORMATEX*)amt.pbFormat)->nSamplesPerSec; |
3a39805e | 324 | hr = Parser_AddPin(&(pWAVEParser->Parser), &piOutput, props, &amt); |
ebe438c8 ML |
325 | CoTaskMemFree(amt.pbFormat); |
326 | ||
602d44a1 AS |
327 | pWAVEParser->Parser.sourceSeeking.llCurrent = 0; |
328 | pWAVEParser->Parser.sourceSeeking.llStop = pWAVEParser->Parser.sourceSeeking.llDuration = bytepos_to_duration(pWAVEParser, pWAVEParser->EndOfFile); | |
329 | TRACE("Duration: %u seconds\n", (DWORD)(pWAVEParser->Parser.sourceSeeking.llDuration / (LONGLONG)10000000)); | |
ebe438c8 ML |
330 | |
331 | This->rtStop = pWAVEParser->EndOfFile; | |
332 | This->rtStart = pWAVEParser->StartOfFile; | |
333 | ||
80a2e6e9 CC |
334 | TRACE("WAVE File ok\n"); |
335 | ||
336 | return hr; | |
337 | } | |
338 | ||
20d87e76 CR |
339 | static HRESULT WAVEParser_Cleanup(LPVOID iface) |
340 | { | |
cc7fc4ab | 341 | WAVEParserImpl *This = iface; |
20d87e76 CR |
342 | |
343 | TRACE("(%p)->()\n", This); | |
344 | ||
20d87e76 CR |
345 | return S_OK; |
346 | } | |
347 | ||
3a39805e ML |
348 | static HRESULT WAVEParser_first_request(LPVOID iface) |
349 | { | |
cc7fc4ab | 350 | WAVEParserImpl *This = iface; |
3a39805e ML |
351 | PullPin *pin = This->Parser.pInputPin; |
352 | HRESULT hr; | |
353 | IMediaSample *sample; | |
354 | ||
355 | if (pin->rtCurrent >= pin->rtStop) | |
356 | { | |
357 | /* Last sample has already been queued, request nothing more */ | |
358 | TRACE("Done!\n"); | |
359 | return S_OK; | |
360 | } | |
361 | ||
362 | hr = IMemAllocator_GetBuffer(pin->pAlloc, &sample, NULL, NULL, 0); | |
363 | ||
364 | pin->rtNext = pin->rtCurrent; | |
365 | if (SUCCEEDED(hr)) | |
366 | { | |
367 | LONGLONG rtSampleStart = pin->rtNext; | |
368 | /* Add 4 for the next header, which should hopefully work */ | |
369 | LONGLONG rtSampleStop = rtSampleStart + MEDIATIME_FROM_BYTES(IMediaSample_GetSize(sample)); | |
370 | Parser_OutputPin *outpin = (Parser_OutputPin *)This->Parser.ppPins[1]; | |
371 | ||
372 | if (rtSampleStop > pin->rtStop) | |
373 | rtSampleStop = MEDIATIME_FROM_BYTES(ALIGNUP(BYTES_FROM_MEDIATIME(pin->rtStop), pin->cbAlign)); | |
374 | ||
375 | hr = IMediaSample_SetTime(sample, &rtSampleStart, &rtSampleStop); | |
376 | ||
377 | pin->rtCurrent = pin->rtNext; | |
378 | pin->rtNext = rtSampleStop; | |
379 | ||
380 | IMediaSample_SetPreroll(sample, FALSE); | |
381 | if (!outpin->dwSamplesProcessed++) | |
382 | IMediaSample_SetDiscontinuity(sample, TRUE); | |
383 | else | |
384 | IMediaSample_SetDiscontinuity(sample, FALSE); | |
385 | ||
386 | hr = IAsyncReader_Request(pin->pReader, sample, 0); | |
387 | } | |
388 | if (FAILED(hr)) | |
6aabf5d3 | 389 | ERR("Horsemen of the apocalypse came to bring error 0x%08x %p\n", hr, sample); |
3a39805e ML |
390 | |
391 | return hr; | |
392 | } | |
393 | ||
6165d87f ML |
394 | static HRESULT WAVEParser_disconnect(LPVOID iface) |
395 | { | |
396 | /* TODO: Find and plug memory leaks */ | |
397 | return S_OK; | |
398 | } | |
399 | ||
1f136a57 ML |
400 | static const IBaseFilterVtbl WAVEParser_Vtbl = |
401 | { | |
402 | Parser_QueryInterface, | |
403 | Parser_AddRef, | |
404 | Parser_Release, | |
405 | Parser_GetClassID, | |
406 | Parser_Stop, | |
407 | Parser_Pause, | |
408 | Parser_Run, | |
409 | Parser_GetState, | |
410 | Parser_SetSyncSource, | |
411 | Parser_GetSyncSource, | |
412 | Parser_EnumPins, | |
413 | Parser_FindPin, | |
414 | Parser_QueryFilterInfo, | |
415 | Parser_JoinFilterGraph, | |
416 | Parser_QueryVendorInfo | |
417 | }; | |
418 | ||
80a2e6e9 CC |
419 | HRESULT WAVEParser_create(IUnknown * pUnkOuter, LPVOID * ppv) |
420 | { | |
421 | HRESULT hr; | |
422 | WAVEParserImpl * This; | |
423 | ||
424 | TRACE("(%p, %p)\n", pUnkOuter, ppv); | |
425 | ||
426 | *ppv = NULL; | |
427 | ||
428 | if (pUnkOuter) | |
429 | return CLASS_E_NOAGGREGATION; | |
430 | ||
431 | /* Note: This memory is managed by the transform filter once created */ | |
432 | This = CoTaskMemAlloc(sizeof(WAVEParserImpl)); | |
433 | ||
512ee927 | 434 | hr = Parser_Create(&(This->Parser), &WAVEParser_Vtbl, &CLSID_WAVEParser, WAVEParser_Sample, WAVEParser_QueryAccept, WAVEParser_InputPin_PreConnect, WAVEParser_Cleanup, WAVEParser_disconnect, WAVEParser_first_request, NULL, NULL, WAVEParserImpl_seek, NULL); |
80a2e6e9 CC |
435 | |
436 | if (FAILED(hr)) | |
437 | return hr; | |
438 | ||
cc7fc4ab | 439 | *ppv = This; |
80a2e6e9 CC |
440 | |
441 | return hr; | |
442 | } |