////////////////////////////////////////////////////////////////////////// // // ASFManager.cpp : CASFManager class implementation. // // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF // ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A // PARTICULAR PURPOSE. // // Copyright (c) Microsoft Corporation. All rights reserved. // ////////////////////////////////////////////////////////////////////////// #include #include "ASFManager.h" // ----- Static Methods ----------------------------------------------- ////////////////////////////////////////////////////////////////////////// // Name: CreateInstance // Description: Instantiates the class statically // ///////////////////////////////////////////////////////////////////////// HRESULT CASFManager::CreateInstance(CASFManager **ppASFManager) { // Note: CASFManager constructor sets the ref count to zero. // Create method calls AddRef. HRESULT hr = S_OK; CASFManager *pASFManager = new (std::nothrow) CASFManager(&hr); if (!pASFManager) { return E_OUTOFMEMORY; } if (SUCCEEDED(hr)) { *ppASFManager = pASFManager; (*ppASFManager)->AddRef(); TRACE((L"CASFManager created.\n")); } LOG_MSG_IF_FAILED(L"CASFManager creation failed.\n", hr); SAFE_RELEASE (pASFManager); return hr; } // ----- Public Methods ----------------------------------------------- ////////////////////////////////////////////////////////////////////////// // Name: CASFManager // Description: Constructor // ///////////////////////////////////////////////////////////////////////// CASFManager::CASFManager(HRESULT* hr) : m_nRefCount(1), m_CurrentStreamID (0), m_guidCurrentMediaType (GUID_NULL), m_fileinfo(NULL), m_pDecoder (NULL), m_pContentInfo (NULL), m_pIndexer (NULL), m_pSplitter (NULL), m_pDataBuffer (NULL), m_pByteStream(NULL), m_cbDataOffset(0), m_cbDataLength(0) { //Initialize Media Foundation *hr = MFStartup(MF_VERSION); } ////////////////////////////////////////////////////////////////////////// // Name: ~ASFManager // Description: Destructor // // -Calls Shutdown ///////////////////////////////////////////////////////////////////////// CASFManager::~CASFManager() { //Release memory Reset(); // Shutdown the Media Foundation platform (void)MFShutdown(); } ///////////////////////////////////////////////////////////////////// // Name: OpenASFFile // // Opens a file and returns a byte stream. // // sFileName: Path name of the file // ppStream: Receives a pointer to the byte stream. ///////////////////////////////////////////////////////////////////// HRESULT CASFManager::OpenASFFile(const WCHAR *sFileName) { HRESULT hr = S_OK; IMFByteStream* pStream = NULL; // Open a byte stream for the file. CHECK_HR(hr = MFCreateFile( MF_ACCESSMODE_READ, MF_OPENMODE_FAIL_IF_NOT_EXIST, MF_FILEFLAGS_NONE, sFileName, &pStream )); TRACE((L"Opened ASF File\n")); //Reset the ASF components. Reset(); // Create the Media Foundation ASF objects. CHECK_HR(hr = CreateASFContentInfo(pStream, &m_pContentInfo)); CHECK_HR(hr = CreateASFSplitter(pStream, &m_pSplitter)); CHECK_HR(hr = CreateASFIndexer(pStream, &m_pIndexer)); done: LOG_MSG_IF_FAILED(L"CASFManager::OpenASFFile failed.\n", hr); SAFE_RELEASE(pStream); return hr; } // ----- Private Methods ----------------------------------------------- ///////////////////////////////////////////////////////////////////// // Name: CreateASFContentInfo // // Reads the ASF Header Object from a byte stream and returns a // pointer to the ASF content information object. // // pStream: Pointer to the byte stream. The byte stream's // current read position must be 0 that indicates the start of the // ASF Header Object. // ppContentInfo: Receives a pointer to the ASF content information // object. ///////////////////////////////////////////////////////////////////// HRESULT CASFManager::CreateASFContentInfo (IMFByteStream *pContentByteStream, IMFASFContentInfo **ppContentInfo) { if (!pContentByteStream || !ppContentInfo) { return E_INVALIDARG; } HRESULT hr = S_OK; QWORD cbHeader = 0; IMFASFContentInfo *pContentInfo = NULL; IMFMediaBuffer *pBuffer = NULL; // Create the ASF content information object. CHECK_HR(hr = MFCreateASFContentInfo(&pContentInfo)); // Read the first 30 bytes to find the total header size. CHECK_HR(hr = ReadDataIntoBuffer( pContentByteStream, 0, MIN_ASF_HEADER_SIZE, &pBuffer)); CHECK_HR(hr = pContentInfo->GetHeaderSize(pBuffer, &cbHeader)); SAFE_RELEASE(pBuffer); //Read the header into a buffer CHECK_HR(hr = ReadDataIntoBuffer( pContentByteStream, 0, (DWORD)cbHeader, &pBuffer)); // Pass the buffer for the header object. CHECK_HR(hr = pContentInfo->ParseHeader(pBuffer, 0)); // Return the pointer to the caller. *ppContentInfo = pContentInfo; (*ppContentInfo)->AddRef(); TRACE((L"Created ContentInfo object.\n")); done: LOG_MSG_IF_FAILED(L"CASFManager::CreateASFContentInfo failed.\n", hr); SAFE_RELEASE(pBuffer); SAFE_RELEASE(pContentInfo); return hr; } ///////////////////////////////////////////////////////////////////// // Name: CreateASFSplitter // // Creates the ASF splitter. // // pContentByteStream: Pointer to the byte stream that contains the ASF Data Object. // ppSplitter: Receives a pointer to the ASF splitter. ///////////////////////////////////////////////////////////////////// HRESULT CASFManager::CreateASFSplitter (IMFByteStream *pContentByteStream, IMFASFSplitter **ppSplitter) { if (!pContentByteStream || !ppSplitter) { return E_INVALIDARG; } if (!m_pContentInfo) { return MF_E_NOT_INITIALIZED; } HRESULT hr = S_OK; IMFASFSplitter *pSplitter = NULL; IMFPresentationDescriptor* pPD = NULL; UINT64 cbDataOffset = 0, cbDataLength = 0; CHECK_HR(hr = MFCreateASFSplitter(&pSplitter)); CHECK_HR(hr = pSplitter->Initialize(m_pContentInfo)); //Generate the presentation descriptor CHECK_HR(hr = m_pContentInfo->GeneratePresentationDescriptor(&pPD)); //Get the offset to the start of the Data Object CHECK_HR(hr = pPD->GetUINT64(MF_PD_ASF_DATA_START_OFFSET, &cbDataOffset)); //Get the length of the Data Object CHECK_HR(hr = pPD->GetUINT64(MF_PD_ASF_DATA_LENGTH, &cbDataLength)); m_pByteStream = pContentByteStream; m_pByteStream->AddRef(); m_cbDataOffset = cbDataOffset; m_cbDataLength = cbDataLength; // Return the pointer to the caller. *ppSplitter = pSplitter; (*ppSplitter)->AddRef(); TRACE((L"Created Splitter object.\n")); done: LOG_MSG_IF_FAILED(L"CASFManager::CreateASFSplitter failed.\n", hr); SAFE_RELEASE(pSplitter); SAFE_RELEASE(pPD); return hr; } ///////////////////////////////////////////////////////////////////// // Name: CreateASFIndexer // // Creates the ASF Indexer. // // pContentByteStream: Pointer to the content byte stream // // ppIndexer: Receives a pointer to the ASF Indexer. ///////////////////////////////////////////////////////////////////// HRESULT CASFManager::CreateASFIndexer (IMFByteStream *pContentByteStream, IMFASFIndexer **ppIndexer) { if (!pContentByteStream || !ppIndexer) { return E_INVALIDARG; } if (!m_pContentInfo) { return MF_E_NOT_INITIALIZED; } HRESULT hr = S_OK; IMFASFIndexer *pIndexer = NULL; IMFByteStream *pIndexerByteStream = NULL; QWORD qwLength = 0, qwIndexOffset = 0, qwBytestreamLength = 0; DWORD dwByteStreamsNeeded = 0; // Create the indexer. CHECK_HR(hr = MFCreateASFIndexer(&pIndexer)); //Initialize the indexer to work with this ASF library CHECK_HR(hr = pIndexer->Initialize(m_pContentInfo)); //Check if the index exists. //you can only do this after creating the indexer //Get byte stream length CHECK_HR(hr = pContentByteStream->GetLength(&qwLength)); //Get index offset CHECK_HR(hr = pIndexer->GetIndexPosition( m_pContentInfo, &qwIndexOffset )); if ( qwIndexOffset >= qwLength) { //index object does not exist, release the indexer goto done; } else { // initialize the indexer // Create a byte stream that the Indexer will use to read in // and parse the indexers. CHECK_HR(hr = MFCreateASFIndexerByteStream( pContentByteStream, qwIndexOffset, &pIndexerByteStream )); } CHECK_HR(hr = pIndexer->GetIndexByteStreamCount( &dwByteStreamsNeeded )); if (dwByteStreamsNeeded == 1) { CHECK_HR(hr = pIndexer->SetIndexByteStreams( &pIndexerByteStream, dwByteStreamsNeeded )); } // Return the pointer to the caller. *ppIndexer = pIndexer; (*ppIndexer)->AddRef(); TRACE((L"Created Indexer object.\n")); done: LOG_MSG_IF_FAILED(L"CASFManager::CreateASFIndexer failed.\n", hr); SAFE_RELEASE(pIndexer); SAFE_RELEASE(pIndexerByteStream); return hr; } ///////////////////////////////////////////////////////////////////// // Name: EnumerateStreams // // Enumerates the streams in the ASF file. // // ppwStreamNumbers: Receives the stream identifiers in an array. // The caller must release the allocated memory. // // ppguidMajorType: Receives the major media type GUIDs in an array. // The caller must release the allocated memory. // // cbTotalStreams: Receives total number of elements in the array. ///////////////////////////////////////////////////////////////////// HRESULT CASFManager::EnumerateStreams (WORD** ppwStreamNumbers, GUID** ppguidMajorType, DWORD* pcbTotalStreams) { if (!ppwStreamNumbers || !ppguidMajorType || !pcbTotalStreams) { return E_INVALIDARG; } if (!m_pContentInfo) { return MF_E_NOT_INITIALIZED; } HRESULT hr = S_OK; IMFASFStreamConfig* pStream = NULL; IMFASFProfile* pProfile = NULL; *pcbTotalStreams =0; WORD* pwStreamNumbers; GUID* pguidMajorType; CHECK_HR(hr = m_pContentInfo->GetProfile(&pProfile)); CHECK_HR(hr = pProfile->GetStreamCount(pcbTotalStreams)); if (*pcbTotalStreams == 0) { SAFE_RELEASE(pProfile); return E_FAIL; } //create an array of stream numbers and initialize elements swith 0 pwStreamNumbers = new WORD[*pcbTotalStreams*sizeof(WORD)]; if (!pwStreamNumbers) { hr = E_OUTOFMEMORY; goto done; } ZeroMemory (pwStreamNumbers, *pcbTotalStreams*sizeof(WORD)); //create an array of guids and initialize elements with GUID_NULL. pguidMajorType = new GUID[*pcbTotalStreams*sizeof(GUID)]; if (!pguidMajorType) { hr = E_OUTOFMEMORY; goto done; } ZeroMemory (pguidMajorType, *pcbTotalStreams*sizeof(GUID)); //populate the arrays with stream numbers and guids from the profile object for (unsigned index = 0; index < *pcbTotalStreams; index++) { CHECK_HR(hr = pProfile->GetStream(index, &pwStreamNumbers[index], &pStream)); CHECK_HR(hr = pStream->GetStreamType(&pguidMajorType[index])); SAFE_RELEASE(pStream); } *ppwStreamNumbers = pwStreamNumbers; *ppguidMajorType = pguidMajorType; TRACE((L"Enumerated streams.\n")); done: LOG_MSG_IF_FAILED(L"CASFManager::EnumerateStreams failed.\n", hr); SAFE_RELEASE(pProfile); SAFE_RELEASE(pStream); if (FAILED (hr)) { SAFE_ARRAY_DELETE(pwStreamNumbers); SAFE_ARRAY_DELETE(pguidMajorType); } return hr; } ///////////////////////////////////////////////////////////////////// // Name: SelectStream // // Selects the streams in the ASF file. // // pwStreamNumber: Specifies the identifier of the stream to be selected // on the splitter. // // pguidCurrentMediaType: Receives the major media type GUID of the // currently selected stream. // ///////////////////////////////////////////////////////////////////// HRESULT CASFManager::SelectStream (WORD wStreamNumber, GUID* pguidCurrentMediaType) { HRESULT hr = S_OK; if (wStreamNumber == 0 || !pguidCurrentMediaType) { return E_INVALIDARG; } if (! m_pSplitter || ! m_pContentInfo) { return MF_E_NOT_INITIALIZED; } //Select the stream you want to parse. This sample allows you to select only one stream at a time CHECK_HR(hr = m_pSplitter->SelectStreams(&wStreamNumber, 1)); //Load the appropriate stream decoder CHECK_HR(hr = SetupStreamDecoder(wStreamNumber, pguidCurrentMediaType)); m_CurrentStreamID = wStreamNumber; m_guidCurrentMediaType = *pguidCurrentMediaType; TRACE((L"Stream selected.\n")); done: LOG_MSG_IF_FAILED(L"CASFManager::SelectStreams failed.\n", hr); return hr; } ///////////////////////////////////////////////////////////////////// // Name: SetupStreamDecoder // // Loads the appropriate decoder for stream. The decoder is implemented as // a Media Foundation Transform (MFT). The class CDecoder provides a wrapper for // the MFT. The CASFManager::GenerateSamples feeds compressed samples to // the decoder. The MFT decodes the samples and sends them to the CMediaController // object, which plays 10 seconds of uncompressed audio samples or displays the // key frame for the video stream // // wStreamNumber: Specifies the identifier of the stream. // // pguidCurrentMediaType: Receives the major media type GUID of the // currently selected stream. // ///////////////////////////////////////////////////////////////////// HRESULT CASFManager::SetupStreamDecoder (WORD wStreamNumber, GUID* pguidCurrentMediaType) { if (! m_pContentInfo) { return MF_E_NOT_INITIALIZED; } if (wStreamNumber == 0) { return E_INVALIDARG; } IMFASFProfile* pProfile = NULL; IMFMediaType* pMediaType = NULL; IMFASFStreamConfig *pStream = NULL; GUID guidMajorType = GUID_NULL; GUID guidSubType = GUID_NULL; GUID guidDecoderCategory = GUID_NULL; BOOL fIsCompressed = TRUE; CLSID *pDecoderCLSIDs = NULL; // Pointer to an array of CLISDs. UINT32 cDecoderCLSIDs = 0; // Size of the array. HRESULT hr = S_OK; //Get the profile object that stores stream information CHECK_HR(hr = m_pContentInfo->GetProfile(&pProfile)); //Get stream configuration object from the profile CHECK_HR(hr = pProfile->GetStreamByNumber(wStreamNumber, &pStream)); //Get the media type CHECK_HR(hr = pStream->GetMediaType(&pMediaType)); //Get the major media type CHECK_HR(hr = pMediaType->GetMajorType(&guidMajorType)); //Get the sub media type CHECK_HR(hr = pMediaType->GetGUID(MF_MT_SUBTYPE, &guidSubType)); //find out if the media type is compressed CHECK_HR(hr = pMediaType->IsCompressedFormat(&fIsCompressed)); if (fIsCompressed) { //get decoder category if (guidMajorType == MFMediaType_Video) { guidDecoderCategory = MFT_CATEGORY_VIDEO_DECODER; } else if (guidMajorType == MFMediaType_Audio) { guidDecoderCategory = MFT_CATEGORY_AUDIO_DECODER; } else { CHECK_HR(hr = MF_E_INVALIDMEDIATYPE); } // Look for a decoder. MFT_REGISTER_TYPE_INFO tinfo; tinfo.guidMajorType = guidMajorType; tinfo.guidSubtype = guidSubType; CHECK_HR(hr = MFTEnum( guidDecoderCategory, 0, // Reserved &tinfo, // Input type to match. (Encoded type.) NULL, // Output type to match. (Don't care.) NULL, // Attributes to match. (None.) &pDecoderCLSIDs, // Receives a pointer to an array of CLSIDs. &cDecoderCLSIDs // Receives the size of the array. )); // MFTEnum can return zero matches. if (cDecoderCLSIDs == 0) { hr = MF_E_TOPO_CODEC_NOT_FOUND; } else { //if the CDecoder instance does not exist, create one. if (!m_pDecoder) { CHECK_HR(hr = CDecoder::CreateInstance(&m_pDecoder)); } //Load the first MFT in the array for the current media type CHECK_HR(hr = m_pDecoder->Initialize(pDecoderCLSIDs[0], pMediaType)); } *pguidCurrentMediaType = guidMajorType; } else { // Not compressed. Don't need a decoder. CHECK_HR(hr = MF_E_INVALIDREQUEST); } TRACE((L"Stream decoder loaded.\n")); done: LOG_MSG_IF_FAILED(L"CASFManager::SetupStreamDecoder failed.\n", hr); SAFE_RELEASE(pProfile); SAFE_RELEASE(pMediaType); SAFE_RELEASE(pStream); CoTaskMemFree(pDecoderCLSIDs); return hr; } ///////////////////////////////////////////////////////////////////// // Name: GetSeekPosition // // Gets the offset from the start of the ASF Data Object corresponding // to the specified time that is seeked by the caller. // // hnsSeekTime: [In/out]Seek time in hns. This includes the preroll time. The received // value is the actual seek time wth preroll adjustment. // // cbDataOffset: Receives the offset in bytes. // ///////////////////////////////////////////////////////////////////// HRESULT CASFManager::GetSeekPosition (MFTIME* hnsSeekTime, QWORD *pcbDataOffset, MFTIME* phnsApproxSeekTime) { HRESULT hr = S_OK; //if the media type is audio, or doesn't have an indexed data //calculate the offset manually if (( m_guidCurrentMediaType == MFMediaType_Audio) || (!m_pIndexer)) { CHECK_HR(hr = GetSeekPositionManually(*hnsSeekTime, pcbDataOffset)); } //if the type is video, get the position with the indexer if (( m_guidCurrentMediaType == MFMediaType_Video)) { CHECK_HR(hr = GetSeekPositionWithIndexer(*hnsSeekTime, pcbDataOffset, phnsApproxSeekTime)); } TRACE((L"Offset calculated.\n")); done: LOG_MSG_IF_FAILED(L"CASFManager::GetSeekPosition failed.\n", hr); return hr; } ///////////////////////////////////////////////////////////////////// // Name: GetSeekPositionManually // // Gets the offset for audio media types or ones that do not have ASF Index Objects defined. //Offset is calculated as fraction with respect to time // // hnsSeekTime: Presentation time in hns. // // cbDataOffset: Receives the offset in bytes. // ///////////////////////////////////////////////////////////////////// HRESULT CASFManager::GetSeekPositionManually(MFTIME hnsSeekTime, QWORD *cbDataOffset) { //Get average packet size UINT32 averagepacketsize = ( m_fileinfo->cbMaxPacketSize+ m_fileinfo->cbMinPacketSize)/2; DWORD dwFlags = 0; double fraction = 0; HRESULT hr = S_OK; //Check if the reverse flag is set, if so, offset is calculated from the end of the presentation CHECK_HR(hr = this->m_pSplitter->GetFlags(&dwFlags)); if (dwFlags & MFASF_SPLITTER_REVERSE) { fraction = ((double) (m_fileinfo->cbPresentationDuration) - (double) (hnsSeekTime))/(double) (m_fileinfo->cbPresentationDuration); } else { fraction = (double)(hnsSeekTime)/(double) (m_fileinfo->cbPresentationDuration); } //calculate the number of packets passed int seeked_packets = (int)( m_fileinfo->cbPackets * fraction); //get the offset *cbDataOffset = (QWORD)averagepacketsize * seeked_packets; if (*cbDataOffset >= 0) { return S_OK; } TRACE((L"Offset calculated through fraction.\n")); done: LOG_MSG_IF_FAILED(L"CASFManager::GetSeekPositionManually failed.\n", hr); return hr; } ///////////////////////////////////////////////////////////////////// // Name: GetSeekPositionWithIndexer // // Gets the offset for video media types that have ASF Index Objects defined. // // hnsSeekTime: Presentation time in hns. // // cbDataOffset: Receives the offset in bytes. // ///////////////////////////////////////////////////////////////////// HRESULT CASFManager::GetSeekPositionWithIndexer ( MFTIME hnsSeekTime, QWORD *cbDataOffset, MFTIME* hnsApproxSeekTime) { if (! m_pIndexer) { return MF_E_ASF_NOINDEX; } HRESULT hr = S_OK; PROPVARIANT var; PropVariantInit(&var); var.vt = VT_I8; var.hVal.QuadPart = hnsSeekTime; ASF_INDEX_IDENTIFIER IndexIdentifier; BOOL fIsIndexed = FALSE; DWORD cbIndexDescriptor = 0; DWORD dwFlags = 0; //currently only time index is supported, set to GUID_NULL IndexIdentifier.guidIndexType = GUID_NULL; IndexIdentifier.wStreamNumber = m_CurrentStreamID; //Is the stream indexed? Get the value of cbIndexDescriptor hr = m_pIndexer->GetIndexStatus( &IndexIdentifier, &fIsIndexed, NULL, &cbIndexDescriptor ); if (hr == MF_E_BUFFERTOOSMALL) { hr = S_OK; } CHECK_HR(hr); //check if the reverse flag needs to be set on the indexer CHECK_HR (hr = m_pSplitter->GetFlags(&dwFlags)); if (dwFlags & MFASF_SPLITTER_REVERSE) { CHECK_HR (hr = m_pIndexer->SetFlags(MFASF_INDEXER_READ_FOR_REVERSEPLAYBACK)); } //Get the offset from the indexer if (fIsIndexed) { CHECK_HR(hr = m_pIndexer->GetSeekPositionForValue( &var, &IndexIdentifier, cbDataOffset, hnsApproxSeekTime, 0 )); } else { hr = MF_E_ASF_NOINDEX; } TRACE((L"Offset calculated through the Indexer.\n")); done: LOG_MSG_IF_FAILED(L"CASFManager::GetSeekPositionWithIndexer failed.\n", hr); //release memory PropVariantClear(&var); return hr; } ///////////////////////////////////////////////////////////////////// // Name: GetTestDuration // //For audio stream, this method retrieves the duration of the test sample. // // hnsSeekTime: Presentation time in hns. // hnsTestDuration: Presentation time in hns that represents the end time. // dwFlags: Specifies splitter configuration, generate samples in reverse // or generate samples for protected content. ///////////////////////////////////////////////////////////////////// void CASFManager::GetTestDuration(const MFTIME& hnsSeekTime, BOOL bReverse, MFTIME* phnsTestDuration) { MFTIME hnsMaxSeekableTime = m_fileinfo->cbPlayDuration - m_fileinfo->cbPreroll; if (bReverse) { // Reverse playback: The stop time should not go beyond the start of the // ASF Data Object (reading backwards in the file). if (hnsSeekTime - TEST_AUDIO_DURATION < 0) { *phnsTestDuration = 0 ; } else { *phnsTestDuration = hnsSeekTime - TEST_AUDIO_DURATION; } } else { // Forward playback: The stop time should not exceed the end of the presentation. if (hnsSeekTime + TEST_AUDIO_DURATION > hnsMaxSeekableTime) { *phnsTestDuration = hnsMaxSeekableTime ; } else { *phnsTestDuration = hnsSeekTime + TEST_AUDIO_DURATION; } } } ///////////////////////////////////////////////////////////////////// // Name: GenerateSamples // //Gets data offset for the seektime and prepares buffer for parsing. // // hnsSeekTime: Presentation time in hns. // dwFlags: Specifies splitter configuration, generate samples in // reverse or generate samples for protected content. // pSampleInfo: Pointer to SAMPLE_INFO structure that stores sample // information. // FuncPtrToDisplaySampleInfo: Callback defined by the caller that // will display the sample information ///////////////////////////////////////////////////////////////////// HRESULT CASFManager::GenerateSamples( MFTIME hnsSeekTime, DWORD dwFlags, SAMPLE_INFO* pSampleInfo, void (*FuncPtrToDisplaySampleInfo)(SAMPLE_INFO*) ) { if (! m_pSplitter) { return MF_E_NOT_INITIALIZED; } HRESULT hr = S_OK; QWORD cbStartOffset = 0; DWORD cbReadLen = 0; MFTIME hnsApproxTime =0; MFTIME hnsTestSampleDuration =0; BOOL bReverse = FALSE; // Flush the splitter to remove any samples that were delivered // to the ASF splitter during a previous call to this method. CHECK_HR(hr = m_pSplitter->Flush()); //set the reverse flag if applicable hr = m_pSplitter->SetFlags(dwFlags); if (FAILED (hr)) { dwFlags = 0; hr = S_OK; } bReverse = ((dwFlags & MFASF_SPLITTER_REVERSE) == MFASF_SPLITTER_REVERSE); // Get the offset from the start of the ASF Data Object to the desired seek time. CHECK_HR(hr = GetSeekPosition(&hnsSeekTime, &cbStartOffset, &hnsApproxTime)); // Get the audio playback duration. (The duration is TEST_AUDIO_DURATION or up to // the end of the file, whichever is shorter.) if (m_guidCurrentMediaType == MFMediaType_Audio) { GetTestDuration(hnsSeekTime, bReverse, &hnsTestSampleDuration); } // Notify the MFT we are about to start. if (m_pDecoder) { if ( m_pDecoder->GetDecoderStatus() != STREAMING) { hr = m_pDecoder->StartDecoding(); } if (FAILED(hr)) { SAFE_RELEASE(m_pDecoder); } } cbReadLen = (DWORD)(m_cbDataLength - cbStartOffset); if (bReverse) { // Reverse playback: Read from the offset back to zero. CHECK_HR(hr = GenerateSamplesLoop( hnsSeekTime, hnsTestSampleDuration, bReverse, (DWORD)(m_cbDataLength + m_cbDataOffset - cbStartOffset), //DWORD cbDataOffset cbReadLen, //DWORD cbDataLen pSampleInfo, FuncPtrToDisplaySampleInfo )); } else { // Forward playback: Read from the offset to the end. CHECK_HR(hr = GenerateSamplesLoop( hnsSeekTime, hnsTestSampleDuration, bReverse, (DWORD)(m_cbDataOffset + cbStartOffset), //DWORD cbDataOffset, cbReadLen, //DWORD cbDataLen pSampleInfo, FuncPtrToDisplaySampleInfo )); } // Note: cbStartOffset is relative to the start of the data object. // GenerateSamplesLoop expects the offset relative to the start of the file. done: LOG_MSG_IF_FAILED(L"CASFManager::GenerateSamples failed.\n", hr); return hr; } ///////////////////////////////////////////////////////////////////// // Name: GenerateSamplesLoop // //Reads 1024 * 4 byte chunks of media data from a byte stream and //parses the ASF Data Object starting at the specified offset. //Collects 5seconds audio samples and sends to the MFT to decode. //Gets the first key frame for the video stream and sends to the MFT // // hnsSeekTime: Presentation time in hns. // hnsTestSampleDuration: Presentation time at which the parsing should end. // bReverse: Specifies if the splitter configured to parse in reverse. // cbDataOffset: Offset relative to the start of the data object. // cbDataLen: Length of data to parse // pSampleInfo: Pointer to SAMPLE_INFO structure that stores sample // information. // FuncPtrToDisplaySampleInfo: Callback defined by the caller that // will display the sample information ///////////////////////////////////////////////////////////////////// HRESULT CASFManager::GenerateSamplesLoop( const MFTIME& hnsSeekTime, const MFTIME& hnsTestSampleDuration, BOOL bReverse, DWORD cbDataOffset, DWORD cbDataLen, SAMPLE_INFO* pSampleInfo, void (*FuncPtrToDisplaySampleInfo)(SAMPLE_INFO*) ) { const DWORD READ_SIZE = 1024 * 4; HRESULT hr = S_OK; DWORD cbRead = 0; DWORD dwStatusFlags = 0; WORD wStreamNumber = 0; BOOL fComplete = FALSE; IMFSample *pSample = NULL; IMFMediaBuffer *pBuffer = NULL; MFTIME hnsCurrentSampleTime = 0; while (!fComplete && (cbDataLen > 0)) { cbRead = min(READ_SIZE, cbDataLen); if (bReverse) { // Reverse playback: Read data chunks going backward from cbDataOffset. CHECK_HR(hr = ReadDataIntoBuffer(m_pByteStream, cbDataOffset - cbRead, cbRead, &pBuffer)); cbDataOffset -= cbRead; cbDataLen -= cbRead; } else { // Forward playback: Read data chunks going forward from cbDataOffset. CHECK_HR(hr = ReadDataIntoBuffer(m_pByteStream, cbDataOffset, cbRead, &pBuffer)); cbDataOffset += cbRead; cbDataLen -= cbRead; } // Push data on the splitter CHECK_HR(hr = m_pSplitter->ParseData(pBuffer, 0, 0)); // Start getting samples from the splitter as long as it returns ASF_STATUSFLAGS_INCOMPLETE do { CHECK_HR(hr = m_pSplitter->GetNextSample(&dwStatusFlags, &wStreamNumber, &pSample)); if (pSample) { // Get sample information pSampleInfo->wStreamNumber = wStreamNumber; //if decoder is initialized, collect test data if (m_pDecoder) { if (m_guidCurrentMediaType == MFMediaType_Audio) { // Send audio data to the decoder. (void)SendAudioSampleToDecoder(pSample, hnsTestSampleDuration, bReverse, &fComplete, pSampleInfo, FuncPtrToDisplaySampleInfo); } else if (m_guidCurrentMediaType == MFMediaType_Video) { // Send video data to the decoder. (void)SendKeyFrameToDecoder(pSample, hnsSeekTime, bReverse, &fComplete, pSampleInfo, FuncPtrToDisplaySampleInfo); } if (fComplete) { break; } } } SAFE_RELEASE(pSample); } while (dwStatusFlags & ASF_STATUSFLAGS_INCOMPLETE); SAFE_RELEASE(pBuffer); } done: SAFE_RELEASE(pBuffer); SAFE_RELEASE(pSample); return hr; } ///////////////////////////////////////////////////////////////////// // Name: SendAudioSampleToDecoder // // For audio, collect test samples and send it to the decoder // // pSample: Uncompressed sample that needs to be decoded // hnsTestSampleEndTime: Presenation time at which to stop decoding. ///////////////////////////////////////////////////////////////////// HRESULT CASFManager::SendAudioSampleToDecoder( IMFSample* pSample, const MFTIME& hnsTestSampleEndTime, BOOL bReverse, BOOL *pbComplete, SAMPLE_INFO* pSampleInfo, void (*FuncPtrToDisplaySampleInfo)(SAMPLE_INFO*)) { if (!pSample) { return E_INVALIDARG; } HRESULT hr = S_OK; MFTIME hnsCurrentSampleTime = 0; BOOL bShouldDecode = FALSE; // Get the time stamp on the sample. CHECK_HR(hr = pSample->GetSampleTime(&hnsCurrentSampleTime)); if (bReverse) { bShouldDecode = (hnsCurrentSampleTime > hnsTestSampleEndTime); } else { bShouldDecode = (hnsCurrentSampleTime < hnsTestSampleEndTime); } if (bShouldDecode) { // If the decoder is not streaming, start it. if ( m_pDecoder->GetDecoderStatus() != STREAMING) { CHECK_HR (hr = m_pDecoder->StartDecoding()); } CHECK_HR (hr = m_pDecoder->ProcessAudio (pSample)); //Get sample information (void)GetSampleInfo(pSample, pSampleInfo); //Send it to callback to display FuncPtrToDisplaySampleInfo(pSampleInfo); } else { //all samples have been decoded. Inform the decoder. CHECK_HR (hr = m_pDecoder->StopDecoding()); } *pbComplete = !bShouldDecode; done: return hr; } ///////////////////////////////////////////////////////////////////// // Name: SendKeyFrameToDecoder // //For Video, get the key frame closest to the time seeked by the caller // // pSample: Uncompressed sample that needs to be decoded // hnsTestSampleEndTime: Presenation time at which to stop decoding. ///////////////////////////////////////////////////////////////////// HRESULT CASFManager::SendKeyFrameToDecoder( IMFSample* pSample, const MFTIME& hnsSeekTime, BOOL bReverse, BOOL* fDecodedKeyFrame, SAMPLE_INFO* pSampleInfo, void (*FuncPtrToDisplaySampleInfo)(SAMPLE_INFO*)) { if (!pSample) { return E_INVALIDARG; } HRESULT hr = S_OK; MFTIME hnsCurrentSampleTime =0; BOOL fShouldDecode = FALSE; UINT32 fIsKeyFrame = 0; IMFSample* pKeyFrameSample = NULL; // Get the time stamp on the sample CHECK_HR (hr = pSample->GetSampleTime (&hnsCurrentSampleTime)); if ((UINT64)hnsCurrentSampleTime > m_fileinfo->cbPreroll) { hnsCurrentSampleTime -= m_fileinfo->cbPreroll; } // Check if the key-frame attribute is set on the sample fIsKeyFrame = MFGetAttributeUINT32(pSample, MFSampleExtension_CleanPoint, FALSE); if (!fIsKeyFrame) { return hr; } // Should we decode this sample? if (bReverse) { // Reverse playback: // Is the sample *prior* to the seek time, and a key frame? fShouldDecode = (hnsCurrentSampleTime <= hnsSeekTime) ; } else { // Forward playback: // Is the sample *after* the seek time, and a key frame? fShouldDecode = (hnsCurrentSampleTime >= hnsSeekTime); } if (fShouldDecode) { // We found the key frame closest to the seek time. // Start the decoder if not already started. if ( m_pDecoder->GetDecoderStatus() != STREAMING) { CHECK_HR (hr = m_pDecoder->StartDecoding()); } // Set the discontinity attribute. CHECK_HR (hr = pSample->SetUINT32(MFSampleExtension_Discontinuity, TRUE)); //Send the sample to the decoder. CHECK_HR (hr = m_pDecoder->ProcessVideo(pSample)); *fDecodedKeyFrame = TRUE; //Get sample information (void)GetSampleInfo(pSample, pSampleInfo); pSampleInfo->fSeekedKeyFrame = *fDecodedKeyFrame; //Send it to callback to display FuncPtrToDisplaySampleInfo(pSampleInfo); CHECK_HR (hr = m_pDecoder->StopDecoding()); } done: return hr; } ///////////////////////////////////////////////////////////////////// // Name: ReadDataIntoBuffer // // Reads data from a byte stream and returns a media buffer that // contains the data. // // pStream: Pointer to the byte stream // cbOffset: Offset at which to start reading // cbToRead: Number of bytes to read // ppBuffer: Receives a pointer to the buffer. ///////////////////////////////////////////////////////////////////// HRESULT CASFManager::ReadDataIntoBuffer( IMFByteStream *pStream, // Pointer to the byte stream. DWORD cbOffset, // Offset at which to start reading DWORD cbToRead, // Number of bytes to read IMFMediaBuffer **ppBuffer // Receives a pointer to the buffer. ) { HRESULT hr = S_OK; BYTE *pData = NULL; DWORD cbRead = 0; // Actual amount of data read IMFMediaBuffer *pBuffer = NULL; // Create the media buffer. This function allocates the memory. CHECK_HR(hr = MFCreateMemoryBuffer(cbToRead, &pBuffer)); // Access the buffer. CHECK_HR(hr = pBuffer->Lock(&pData, NULL, NULL)); //Set the offset CHECK_HR(hr = pStream->SetCurrentPosition(cbOffset)); // Read the data from the byte stream. CHECK_HR(hr = pStream->Read(pData, cbToRead, &cbRead)); CHECK_HR(hr = pBuffer->Unlock()); pData = NULL; // Update the size of the valid data. CHECK_HR(hr = pBuffer->SetCurrentLength(cbRead)); // Return the pointer to the caller. *ppBuffer = pBuffer; (*ppBuffer)->AddRef(); TRACE((L"Read data from the ASF file into a media buffer.\n")); done: LOG_MSG_IF_FAILED(L"CASFManager::ReadDataIntoBuffer failed.\n", hr); if (pData) { pBuffer->Unlock(); } SAFE_RELEASE(pBuffer); return hr; } ////////////////////////////////////////////////////////////////////////// // Name: SetFilePropertiesObject // Description: Retrieves ASF File Object information through attributes on // the presentation descriptor that is generated from Content Info // ///////////////////////////////////////////////////////////////////////// HRESULT CASFManager::SetFilePropertiesObject(FILE_PROPERTIES_OBJECT* fileinfo) { if (! m_pContentInfo) { return MF_E_NOT_INITIALIZED; } HRESULT hr = S_OK; IMFPresentationDescriptor *pPD = NULL; UINT32 cbBlobSize = 0; CHECK_HR( hr = m_pContentInfo->GeneratePresentationDescriptor(&pPD)); //get File ID hr = pPD->GetGUID(MF_PD_ASF_FILEPROPERTIES_FILE_ID, &fileinfo->guidFileID); // get Creation Time hr = pPD->GetBlob(MF_PD_ASF_FILEPROPERTIES_CREATION_TIME, (BYTE *)&fileinfo->ftCreationTime, sizeof(FILETIME), &cbBlobSize); //get Data Packets Count hr = pPD->GetUINT32(MF_PD_ASF_FILEPROPERTIES_PACKETS, &fileinfo->cbPackets); //get Play Duration hr = pPD->GetUINT64(MF_PD_ASF_FILEPROPERTIES_PLAY_DURATION, &fileinfo->cbPlayDuration); //get presentation duration hr = pPD->GetUINT64(MF_PD_DURATION, &fileinfo->cbPresentationDuration); //get Send Duration hr = pPD->GetUINT64(MF_PD_ASF_FILEPROPERTIES_SEND_DURATION, &fileinfo->cbSendDuration); //get preroll UINT64 msecspreroll = 0, hnspreroll =0; hr = pPD->GetUINT64(MF_PD_ASF_FILEPROPERTIES_PREROLL, &msecspreroll); hnspreroll = msecspreroll*10000; fileinfo->cbPreroll = hnspreroll; //get Flags hr = pPD->GetUINT32(MF_PD_ASF_FILEPROPERTIES_FLAGS, &fileinfo->cbFlags); //get Maximum Data Packet Size hr = pPD->GetUINT32(MF_PD_ASF_FILEPROPERTIES_MAX_PACKET_SIZE, &fileinfo->cbMaxPacketSize); //get Minimum Data Packet Size hr = pPD->GetUINT32(MF_PD_ASF_FILEPROPERTIES_MIN_PACKET_SIZE, &fileinfo->cbMinPacketSize); // get Maximum Bit rate hr = pPD->GetUINT32(MF_PD_ASF_FILEPROPERTIES_MAX_BITRATE, &fileinfo->cbMaxBitRate); this->m_fileinfo = fileinfo; TRACE((L"Read File Properties Object from the ASF Header Object.\n")); done: LOG_MSG_IF_FAILED(L"CASFManager::SetFilePropertiesObject failed.\n", hr); SAFE_RELEASE(pPD); return hr; } ////////////////////////////////////////////////////////////////////////// // Name: GetSampleInfo // Description: Retrieves sample information from the sample generated by the splitter // // pSample: Pointer to the sample object // pSampleInfo: Pointer to the SAMPLE_INFO structure tha stores the sample information. // ///////////////////////////////////////////////////////////////////////// HRESULT CASFManager::GetSampleInfo(IMFSample *pSample, SAMPLE_INFO* pSampleInfo) { if (!pSampleInfo || !pSample) { return E_INVALIDARG; } HRESULT hr = S_OK; //Number of buffers in the sample CHECK_HR(hr = pSample->GetBufferCount(&pSampleInfo->cBufferCount)); //Total buffer length CHECK_HR(hr = pSample->GetTotalLength(&pSampleInfo->cbTotalLength)); //Sample time hr = pSample->GetSampleTime(&pSampleInfo->hnsSampleTime); if (hr == MF_E_NO_SAMPLE_TIMESTAMP) { hr = S_OK; } done: return hr; } ////////////////////////////////////////////////////////////////////////// // Name: Reset // Description: Releases the existing ASF objects for the current file // ///////////////////////////////////////////////////////////////////////// void CASFManager::Reset() { SAFE_RELEASE( m_pContentInfo); SAFE_RELEASE( m_pDataBuffer); SAFE_RELEASE( m_pIndexer); SAFE_RELEASE( m_pSplitter); // TEST SAFE_RELEASE(m_pByteStream); m_cbDataOffset = 0; m_cbDataLength = 0; if (m_pDecoder) { m_pDecoder->Reset(); SAFE_RELEASE (m_pDecoder); } if( m_fileinfo) { m_fileinfo = NULL; } TRACE((L"CASFManager reset.\n")); }