1 /*******************************************************************************
3 * This file is part of the Ogg Vorbis DirectShow filter collection *
5 * Copyright (c) 2001, Tobias Waldvogel *
6 * All rights reserved. *
8 * Redistribution and use in source and binary forms, with or without *
9 * modification, are permitted provided that the following conditions are met: *
11 * - Redistributions of source code must retain the above copyright notice, *
12 * this list of conditions and the following disclaimer. *
14 * - Redistributions in binary form must reproduce the above copyright notice, *
15 * this list of conditions and the following disclaimer in the documentation *
16 * and/or other materials provided with the distribution. *
18 * - The names of the contributors may not be used to endorse or promote *
19 * products derived from this software without specific prior written *
22 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" *
23 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE *
24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE *
25 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE *
26 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR *
27 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF *
28 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS *
29 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN *
30 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) *
31 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE *
32 * POSSIBILITY OF SUCH DAMAGE. *
34 *******************************************************************************/
36 #include "OggSplitterDS.h"
38 COggStream::COggStream(int iStreamID
, COggSplitter
*pOggSplitter
, bool bDummyStream
) :
39 m_evWaitForData(FALSE
)
41 m_pOggSplitter
= pOggSplitter
;
42 m_iStreamID
= iStreamID
;
43 m_bIdentified
= false;
44 m_iStreamType
= cnts_UNKNOWN
;
47 stream_state_init(&m_ss
, iStreamID
);
49 vorbis_info_init(&m_vi
);
50 vorbis_comment_init(&m_vc
);
51 memset(&m_VorbisHeader
, 0, sizeof(ogg_packet
) * 3);
52 m_bIsDummyStream
= bDummyStream
;
57 m_mt
.SetType(&MEDIATYPE_Text
);
59 vorbis_comment_add_tag(&m_vc
, "LANGUAGE", "off");
63 COggStream::~COggStream()
65 stream_state_clear(&m_ss
);
66 vorbis_info_clear(&m_vi
);
67 vorbis_comment_clear(&m_vc
);
71 HRESULT
COggStream::IdentifyType(ogg_page
*og
)
73 if (m_bIdentified
) return S_OK
; // Everything is already done
77 ogg_stream_pagein(&m_ss
.os
, og
);
78 while (ogg_stream_packetout(&m_ss
.os
, &op
) > 0)
80 if ((*op
.packet
& PACKET_TYPE_HEADER
) == 0)
82 m_bIdentified
= true; // This is already a data packet
83 return S_OK
; // We are done
86 switch (*op
.packet
& PACKET_TYPE_BITS
)
88 case PACKET_TYPE_HEADER
:
89 if (stream_header_in(&m_sh
, &op
) == E_SUCCESS
) // => it is our stream format
91 SetMTFromSH(m_sh
, &m_mt
, &m_iLowWatermark
, &m_iHighWatermark
);
92 m_iBufferSize
= m_sh
->buffersize
;
94 m_vi
.rate
= 1; // otherwhise vorbis_synthesis_headerin will fail
95 m_iStreamType
= cnts_OGGSTREAM
;
97 else if (vorbis_synthesis_headerin(&m_vi
, &m_vc
, &op
) >= 0) // => it is Ogg/Vorbis
99 CopyOggPacket(&m_VorbisHeader
[0], &op
);
101 SetMTFromVI(&m_vi
, &m_mt
);
102 m_iBufferSize
= 8192; // Vorbis packets shoudn't contain more than
104 m_iHighWatermark
= 32;
105 m_iLowWatermark
= 16;
107 m_iStreamType
= cnts_VORBIS
;
109 else // No idea what's that
111 m_mt
.SetType(&MEDIATYPE_NULL
);
112 m_bIdentified
= true; // Unknown type
113 m_iStreamType
= cnts_UNKNOWN
;
118 case PACKET_TYPE_COMMENT
:
119 vorbis_synthesis_headerin(&m_vi
, &m_vc
, &op
);
120 CopyOggPacket(&m_VorbisHeader
[1], &op
);
123 case PACKET_TYPE_CODEBOOK
:
124 if (m_iStreamType
== cnts_VORBIS
)
126 vorbis_synthesis_headerin(&m_vi
, &m_vc
, &op
);
127 CopyOggPacket(&m_VorbisHeader
[2], &op
);
128 m_bIdentified
= true;
136 bool COggStream::PageInFromQueue()
140 pPage
= OggPageDequeue();
141 if (!pPage
) return false;
143 ogg_stream_pagein(&m_ss
.os
, &pPage
->og
);
144 OggPageRelease(pPage
);
146 if (OggPageQueuePages() < m_iLowWatermark
)
147 m_pOggSplitter
->m_pInput
->NotifyLowFilllevel();
152 // Add a page in the list and return if HighWatermark was reached
153 bool COggStream::DeliverPage(ogg_page
*pog
)
155 // If the group is not connected we are not interested in the page
156 if (!(m_pPin
->IsConnected())) return false;
160 if (ogg_page_packets(pog
) == 0) return false; // This would cause a problem in the Ogg lib
161 m_bAfterReset
= false;
164 OggPageEnqueue(pog
, NULL
);
165 m_evWaitForData
.Set(); // Signal the stream thread that we have received a page
167 return OggPageQueuePages() >= m_iHighWatermark
;
170 HRESULT
COggStream::Active()
172 if (m_pOggSplitter
->IsActive())
174 if (!m_pPin
->IsConnected())
183 HRESULT
COggStream::Inactive()
189 m_evWaitForData
.Set(); // signal the events to be sure that
190 m_evWaitForGroup
.Set(); // the thread is not blocked anymore
191 CallWorker(CMD_EXIT
);
192 Close(); // Wait for the thread to exit, then tidy up.
198 HRESULT
COggStream::BeginFlush()
204 m_evWaitForData
.Set();
205 m_evWaitForGroup
.Set();
206 CallWorker(CMD_STOP
);
210 HRESULT
COggStream::EndFlush()
220 DWORD
COggStream::ThreadProc(void)
225 com
= (Command
)GetRequest();
238 if (!m_bIsDummyStream
)
241 SendDummySampleLoop();
245 Reply((DWORD
) E_NOTIMPL
);
248 } while (com
!= CMD_EXIT
);
253 bool COggStream::GetSample(REFERENCE_TIME
* prtStart
, REFERENCE_TIME
* prtStop
,
254 ogg_int64_t
* pmtStart
, ogg_int64_t
* pmtStop
,
255 bool* pbSyncPoint
, bool* pbEOS
,
256 unsigned char** ppbBuffer
, int* pcbBuffer
)
262 if (m_iStreamType
== cnts_VORBIS
)
264 or = stream_sampleout_vorbis(&m_ss
, &m_vi
, pbEOS
,
265 pmtStart
, prtStart
, ppbBuffer
, pcbBuffer
);
266 *prtStop
= *prtStart
;
267 *pmtStop
= *pmtStart
;
272 or = stream_sampleout(&m_ss
, m_sh
, pbEOS
, pbSyncPoint
,
273 pmtStart
, pmtStop
, prtStart
, prtStop
, ppbBuffer
, pcbBuffer
);
274 *pmtStop
+= *pmtStart
;
278 return true; // Got a sample => we are done
281 if (!PageInFromQueue())
282 return false; // If there are no more pages in the queue then
283 // there is no sample available
288 void COggStream::SendDummySample()
290 IMediaSample
* pSample
;
292 REFERENCE_TIME rtStart
= 0;
293 REFERENCE_TIME rtStop
= 1;
295 if FAILED(m_pPin
->GetDeliveryBuffer(&pSample
,NULL
,NULL
, 0)) return;
297 pSample
->GetPointer(&pSampBuffer
);
299 pSample
->SetActualDataLength(1);
301 pSample
->SetTime(&rtStart
,&rtStop
);
302 pSample
->SetPreroll(FALSE
);
303 pSample
->SetDiscontinuity(TRUE
);
304 pSample
->SetSyncPoint(TRUE
);
305 m_pPin
->Deliver(pSample
);
309 HRESULT
COggStream::SendSampleLoop()
314 stream_state_reset(&m_ss
);
315 m_bAfterReset
= true;
318 m_evWaitForData
.Reset();
319 m_evWaitForGroup
.Reset();
320 m_bDiscontinuity
= true;
322 REFERENCE_TIME rtSegLen
= m_pOggSplitter
->m_rtStop
- m_pOggSplitter
->m_rtStart
;
328 m_pPin
->DeliverNewSegment(m_pOggSplitter
->m_rtStart
, m_pOggSplitter
->m_rtStop
,
329 m_pOggSplitter
->m_dRate
);
330 // If there is currently no subtitle then no sample is sent to the
331 // subsequent filter. But DirectShow expect at least one sample to
332 // go into pause state and the graph would block. This is a work around
333 // We send just a 100ns sample with just a 0 Byte.
334 if (m_iStreamType
== cnts_OGGSTREAM
)
335 if (strcmp(m_sh
->streamtype
, "text") == 0)
341 REFERENCE_TIME rtLastPos
;
342 REFERENCE_TIME rtStart
, rtStop
;
343 __int64 mtStart
, mtStop
;
348 unsigned char *pbBuffer
;
351 if (GetSample(&rtStart
, &rtStop
, &mtStart
, &mtStop
, &bSyncPoint
, &bEOS
, &pbBuffer
, &cbBuffer
))
353 if (!m_bDiscontinuity
|| bSyncPoint
) // => we have valid times
355 rtStart
-= m_pOggSplitter
->m_rtStart
;
356 rtStop
-= m_pOggSplitter
->m_rtStart
;
357 m_pPin
->GetLastPos(&rtLastPos
);
359 if ((rtStart
>= 0) && (rtStart
> rtLastPos
))
363 if (m_bDiscontinuity
&& m_iStreamType
== cnts_VORBIS
)
364 m_pPin
->SendVorbisHeaderPackets(&(m_VorbisHeader
[0]));
366 IMediaSample
* pSample
;
369 hr
= m_pPin
->GetDeliveryBuffer(&pSample
,NULL
,NULL
, bSyncPoint
? 0 : AM_GBF_NOTASYNCPOINT
);
370 if FAILED(hr
) return hr
;
372 pSample
->GetPointer(&pSampBuffer
);
373 memcpy(pSampBuffer
, pbBuffer
, cbBuffer
);
374 pSample
->SetActualDataLength(cbBuffer
);
376 pSample
->SetTime(&rtStart
,&rtStop
);
377 pSample
->SetMediaTime(&mtStart
, &mtStop
);
378 pSample
->SetPreroll(FALSE
);
379 pSample
->SetDiscontinuity(m_bDiscontinuity
? TRUE
: FALSE
);
380 pSample
->SetSyncPoint(bSyncPoint
? TRUE
: FALSE
);
381 m_bDiscontinuity
= false;
382 hr
= m_pPin
->Deliver(pSample
);
384 if FAILED(hr
) return hr
;
386 // inform the other streams ...
387 m_pOggSplitter
->NotifyGroup(m_iGroupID
, m_pPin
, rtStart
);
389 if ((rtStop
> rtSegLen
) || bEOS
)
390 return m_pPin
->DeliverEndOfStream();
394 else // Stream is not enabled
396 m_evWaitForGroup
.Wait(INFINITE
);
397 m_bDiscontinuity
= TRUE
;
398 if (rtStop
> rtSegLen
)
407 // There was no sample available
408 if (m_pOggSplitter
->IsEOF())
411 return m_pPin
->DeliverEndOfStream();
414 m_evWaitForData
.Wait(INFINITE
); // wait until we get more data
417 } while (!CheckRequest((DWORD
*)&com
) && !m_bAbort
);
421 // Just sends empty samples
422 // Used for the dummy subtitle stream otherwise
423 // The internal script renderer would block
424 HRESULT
COggStream::SendDummySampleLoop()
428 REFERENCE_TIME rtStreamStart
, rtStreamStop
;
432 m_evWaitForGroup
.Reset();
433 m_bDiscontinuity
= TRUE
;
435 m_pOggSplitter
->GetPositions(&rtStreamStart
, &rtStreamStop
);
436 m_pOggSplitter
->GetRate(&dRate
);
440 m_pPin
->DeliverNewSegment(rtStreamStart
, rtStreamStop
, dRate
);
442 REFERENCE_TIME rtStart
, rtStop
;
448 rtStop
= rtStart
+ SEC_IN_REFTIME
;
450 REFERENCE_TIME rtLastPos
;
451 m_pPin
->GetLastPos(&rtLastPos
);
455 if (rtStart
> rtLastPos
)
457 IMediaSample
*pSample
;
460 hr
= m_pPin
->GetDeliveryBuffer(&pSample
,NULL
,NULL
,0);
461 if FAILED(hr
) return (hr
);
463 pSample
->GetPointer(&pSampBuffer
);
466 pSample
->SetDiscontinuity(m_bDiscontinuity
);
467 pSample
->SetSyncPoint(true);
468 pSample
->SetTime(&rtStart
,&rtStop
);
469 pSample
->SetPreroll(rtStart
< 0);
470 pSample
->SetActualDataLength(1);
471 hr
= m_pPin
->Deliver(pSample
);
473 if FAILED(hr
) return (hr
);
474 m_bDiscontinuity
= FALSE
;
476 // inform the other streams ...
477 m_pOggSplitter
->NotifyGroup(m_iGroupID
, m_pPin
, rtStart
);
480 if ((rtStop
> (rtStreamStop
- rtStreamStart
)) || m_pOggSplitter
->IsEOF())
481 return m_pPin
->DeliverEndOfStream();
485 if (rtStart
> rtLastPos
)
486 m_evWaitForGroup
.Wait(INFINITE
);
487 m_bDiscontinuity
= TRUE
;
488 if ((rtStop
> (rtStreamStop
- rtStreamStart
)) || m_pOggSplitter
->IsEOF())
494 } while (!CheckRequest((DWORD
*)&com
) && !m_bAbort
);
498 void COggStream::Enable(bool bEnabled
)
500 m_bDiscontinuity
= TRUE
;
501 m_bEnabled
= bEnabled
;
503 m_pPin
->m_pStream
= this;
506 __int64
COggStream::MediaTimeToRefTime(__int64 iMediaTime
)
508 if (m_iStreamType
== cnts_UNKNOWN
)
510 else if (m_iStreamType
== cnts_VORBIS
)
511 return mediatime_to_reference_time(SEC_IN_REFTIME
, m_vi
.rate
, iMediaTime
);
512 return mediatime_to_reference_time(m_sh
->time_unit
, m_sh
->samples_per_unit
, iMediaTime
);
515 // This is the fourcc mapper feature
516 void COggStream::TranslateFourCC(char* FOURCC
)
521 hReg
= m_pOggSplitter
->OpenRegistry();
524 if (RegOpenKeyEx(hReg
, idFourCCMapping
, 0, KEY_READ
, &hFourCCReg
) != ERROR_SUCCESS
)
530 // Now the registry is opened
534 DWORD
* pdwFOURCC
= (DWORD
*) FOURCC
;
538 wsprintf(srcFOURCC
, "0x%08x", *pdwFOURCC
);
539 dstFOURCC
= *pdwFOURCC
;
543 dwSize
= sizeof(dstFOURCC
);
545 iReturn
= RegQueryValueEx(hFourCCReg
, srcFOURCC
, NULL
, NULL
, (BYTE
*)&dstFOURCC
, &dwSize
);
546 if (iReturn
== ERROR_SUCCESS
)
548 if (dstFOURCC
== *pdwFOURCC
) // To avoid circular conversions
550 *pdwFOURCC
= strtol(srcFOURCC
, NULL
, 16);
551 RegCloseKey(hFourCCReg
);
557 if (iReturn
!= ERROR_SUCCESS
)
559 // There was no entry => we are done
560 *pdwFOURCC
= dstFOURCC
;
561 RegCloseKey(hFourCCReg
);
566 wsprintf(srcFOURCC
, "0x%08x", dstFOURCC
);
570 void COggStream::SetMTFromSH(stream_header
*sh
, CMediaType
*pmt
, int* pHighWM
, int* pLowWM
)
572 if (strncmp((char*)&sh
->streamtype
, MT_Video
, strlen(MT_Video
)) == 0)
574 TranslateFourCC(&sh
->subtype
[0]);
576 pmt
->InitMediaType();
577 pmt
->SetType(&MEDIATYPE_Video
);
578 pmt
->SetSubtype(&MEDIASUBTYPE_YVYU
);
579 pmt
->subtype
.Data1
= *(ogg_int32_t
*)&sh
->subtype
;
581 if (*(pmt
->Subtype()) != MEDIASUBTYPE_RGB565
&&
582 *(pmt
->Subtype()) != MEDIASUBTYPE_RGB555
&&
583 *(pmt
->Subtype()) != MEDIASUBTYPE_RGB24
&&
584 *(pmt
->Subtype()) != MEDIASUBTYPE_RGB32
&&
585 *(pmt
->Subtype()) != MEDIASUBTYPE_ARGB32
&&
586 *(pmt
->Subtype()) != MEDIASUBTYPE_YUY2
&&
587 *(pmt
->Subtype()) != MEDIASUBTYPE_UYVY
&&
588 *(pmt
->Subtype()) != MEDIASUBTYPE_YVYU
)
590 pmt
->SetTemporalCompression(TRUE
);
591 pmt
->SetVariableSize();
593 pmt
->SetFormatType(&FORMAT_VideoInfo
);
594 VIDEOINFO
* pvi
= (VIDEOINFO
*)pmt
->AllocFormatBuffer(sizeof(VIDEOINFO
));
595 memset(pvi
, 0, sizeof(*pvi
));
596 pvi
->AvgTimePerFrame
= sh
->time_unit
;
597 pvi
->bmiHeader
.biBitCount
= sh
->bits_per_sample
;
598 pvi
->bmiHeader
.biPlanes
= 1;
599 pvi
->bmiHeader
.biSize
= sizeof(pvi
->bmiHeader
);
600 pvi
->bmiHeader
.biWidth
= sh
->video
.width
;
601 pvi
->bmiHeader
.biHeight
= sh
->video
.height
;
602 pvi
->bmiHeader
.biCompression
= *(ogg_int32_t
*)&sh
->subtype
;
604 *pHighWM
= sh
->buffersize
* 10 / 4096;
605 *pLowWM
= *pHighWM
/ 2;
610 if (strncmp((char*)&sh
->streamtype
, MT_Audio
, strlen(MT_Audio
)) == 0)
612 unsigned __int16 dwFormatTag
= 0;
613 unsigned __int16 mult
= 0x1000;
615 for (int i
=0; i
<4; i
++)
617 if (sh
->subtype
[i
] >= '0' && sh
->subtype
[i
] <= '9')
618 dwFormatTag
+= (sh
->subtype
[i
] - '0') * mult
;
619 else if (sh
->subtype
[i
] >= 'A' && sh
->subtype
[i
] <= 'F')
620 dwFormatTag
+= (sh
->subtype
[i
] - 'A' + 10) * mult
;
621 else if (sh
->subtype
[i
] >= 'a' && sh
->subtype
[i
] <= 'f')
622 dwFormatTag
+= (sh
->subtype
[i
] - 'a' + 10) * mult
;
626 pmt
->InitMediaType();
627 pmt
->SetType(&MEDIATYPE_Audio
);
628 pmt
->SetSubtype(&MEDIASUBTYPE_PCM
);
629 pmt
->subtype
.Data1
= dwFormatTag
;
631 pmt
->SetFormatType(&FORMAT_WaveFormatEx
);
633 int extralen
= sh
->size
- sizeof(stream_header
);
634 WAVEFORMATEX
* pwfx
= (WAVEFORMATEX
*)pmt
->AllocFormatBuffer(sizeof(WAVEFORMATEX
) + extralen
);
635 memset(pwfx
, 0, sizeof(WAVEFORMATEX
));
637 pwfx
->cbSize
= extralen
;
638 memcpy(pwfx
+1, sh
+1, extralen
);
640 pwfx
->nAvgBytesPerSec
= sh
->audio
.avgbytespersec
;
641 pwfx
->nBlockAlign
= sh
->audio
.blockalign
;
642 pwfx
->nChannels
= sh
->audio
.channels
;
643 pwfx
->nSamplesPerSec
= (__int32
)sh
->samples_per_unit
;
644 pwfx
->wBitsPerSample
= sh
->bits_per_sample
;
645 pwfx
->wFormatTag
= dwFormatTag
;
647 *pHighWM
= sh
->buffersize
* 10 / 4096;
648 *pLowWM
= *pHighWM
/ 2;
653 if (strncmp((char*)&sh
->streamtype
, MT_Text
, strlen(MT_Text
)) == 0)
655 pmt
->InitMediaType();
656 pmt
->SetType(&MEDIATYPE_Text
);
664 void COggStream::SetMTFromVI(vorbis_info
*vi
, CMediaType
*pmt
)
666 pmt
->InitMediaType();
667 pmt
->SetType(&MEDIATYPE_Audio
);
668 pmt
->SetSubtype(&MEDIASUBTYPE_Vorbis
);
669 pmt
->SetFormatType(&FORMAT_VorbisFormat
);
671 VORBISFORMAT
*pfmt
= (VORBISFORMAT
*) (pmt
->AllocFormatBuffer(sizeof(VORBISFORMAT
)));
673 pfmt
->nChannels
= vi
->channels
;
674 pfmt
->nSamplesPerSec
= vi
->rate
;
675 pfmt
->nMinBitsPerSec
= vi
->bitrate_lower
;
676 pfmt
->nAvgBitsPerSec
= vi
->bitrate_nominal
;
677 pfmt
->nMaxBitsPerSec
= vi
->bitrate_upper
;
680 // used by the seek function
681 bool COggStream::FindKeyFrame(ogg_page
*og
, CRefTime
*rtFrame
)
684 REFERENCE_TIME rtStart
;
686 ogg_stream_pagein(&m_ss
.os
, og
);
688 while (stream_sampleout(&m_ss
, m_sh
, NULL
, &bSyncPoint
, NULL
, NULL
,
689 &rtStart
, NULL
, NULL
, NULL
) > 0)
691 if (bSyncPoint
&& (rtStart
>= *rtFrame
))