2025-11-28 00:35:46 +09:00

930 lines
24 KiB
C++

//*****************************************************************************
//
// Microsoft Windows Media
// Copyright (C) Microsoft Corporation. All rights reserved.
//
// FileName: reader.cpp
//
// Abstract: Implementation of the CReader class
//
//*****************************************************************************
#include "Reader.h"
//////////////////////////////////////////////////////////////////////
// The CReader object reads stream data from an input (file or URL)
// specified by the pwszFile parameter of the Configure method and
// sends it the output, which is a CWriter or CWaveOut object,
// depending which one of them has been attached. Data sent to CWriter
// is compressed, while data sent to CWaveOut is decompressed by CReader.
// The GetStats method enables you to get statistics at any time during
// data transmission.
//////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
CReader::CReader()
{
m_pReaderHeaderInfo = NULL ;
m_pReaderAdvanced = NULL ;
m_pReader = NULL ;
m_pReaderStreamClock = NULL ;
m_hEvent = NULL ;
m_fEOF = false ;
m_qwTime = 0 ;
m_hrAsync = S_OK ;
m_fReaderStarted = false;
m_fNetReading = false;
m_pWriter = NULL;
m_dwAudioStreamNum = 0;
m_dwTimerId = 0;
}
///////////////////////////////////////////////////////////////
CReader::~CReader()
{
SAFE_RELEASE( m_pReaderStreamClock );
SAFE_RELEASE( m_pReaderHeaderInfo );
SAFE_RELEASE( m_pReaderAdvanced ) ;
SAFE_RELEASE( m_pReader ) ;
if( NULL != m_hEvent )
{
CloseHandle( m_hEvent ) ;
m_hEvent = NULL ;
}
}
///////////////////////////////////////////////////////////////
// Initialize Reader object:
// create all necessary interfaces and an event
///////////////////////////////////////////////////////////////
HRESULT CReader::Init()
{
HRESULT hr = S_OK;
if ( m_fReaderStarted || m_pReaderHeaderInfo )
return E_FAIL;
do
{
m_hrAsync = S_OK ;
m_hEvent = CreateEvent( NULL, FALSE, FALSE, NULL ) ;
if ( NULL == m_hEvent )
{
_tprintf( _T( "Could not Create Event.\n" ));
break;
}
if ( FAILED( hr = WMCreateReader( NULL, WMT_RIGHT_PLAYBACK, &m_pReader )))
{
_tprintf( _T( "Could not create reader (hr=0x%08x).\n" ), hr );
break;
}
if ( FAILED( hr = m_pReader->QueryInterface( IID_IWMReaderAdvanced, ( void** )&m_pReaderAdvanced )))
{
_tprintf( _T( "Could not QI for IWMReaderAdvanced (hr=0x%08x).\n" ), hr );
break;
}
hr = m_pReader->QueryInterface( IID_IWMHeaderInfo, ( VOID ** )&m_pReaderHeaderInfo );
if ( FAILED( hr ) )
{
_tprintf( _T( "Could not QI for IWMHeaderInfo (hr=0x%08x).\n" ), hr );
break;
}
hr = m_pReader->QueryInterface( IID_IWMReaderStreamClock, ( VOID ** )&m_pReaderStreamClock );
if ( FAILED( hr ) )
{
_tprintf( _T( "Could not QI for IWMReaderStreamClock (hr=0x%08x).\n" ), hr );
break;
}
} while( FALSE );
if (FAILED( hr ) )
{
SAFE_RELEASE( m_pReaderStreamClock );
SAFE_RELEASE( m_pReaderHeaderInfo );
SAFE_RELEASE( m_pReaderAdvanced );
SAFE_RELEASE( m_pReader );
CloseHandle( m_hEvent );
}
return( hr );
}
///////////////////////////////////////////////////////////////
// Configure previously intialized reader object
// pwszFile - URL of input file
//
HRESULT CReader::Configure( const WCHAR *pwszFile )
{
HRESULT hr = S_OK;
const WCHAR * wszAttributes[] = { g_wszWMTitle,
g_wszWMAuthor,
g_wszWMDescription,
g_wszWMRating,
g_wszWMCopyright,
g_wszWMAlbumTitle,
g_wszWMTrack,
g_wszWMPromotionURL,
g_wszWMAlbumCoverURL,
g_wszWMGenre,
g_wszWMYear,
g_wszWMGenreID,
g_wszWMMCDI,
g_wszWMBannerImageType,
g_wszWMBannerImageData,
g_wszWMBannerImageURL,
g_wszWMCopyrightURL };
//
// Check, if it's a net location.
//
if ( NULL == pwszFile || NULL == wcslen(pwszFile))
return E_INVALIDARG;
m_fNetReading = _wcsnicmp( pwszFile, L"http", 4 )? FALSE : TRUE ;
//
// Open the requested file
//
hr = m_pReader->Open( pwszFile, this, NULL );
if ( FAILED( hr ) )
{
_tprintf( _T( "Could not open file %ws (hr=0x%08x).\n" ), pwszFile ,hr );
return( hr );
}
//
// Wait for the open to finish
//
WaitForEvent();
if ( FAILED( m_hrAsync ) )
{
_tprintf( _T( "Open failed (hr=0x%08x).\n" ), m_hrAsync );
return( m_hrAsync );
}
//
// Turn on manual stream selection, so we get all streams.
//
hr = m_pReaderAdvanced->SetManualStreamSelection( TRUE );
if( FAILED( hr ) )
{
_tprintf( _T( "Failed to set manual stream selection (hr=0x%08x).\n" ), hr );
return( hr );
}
//
// Get the profile interface
//
IWMProfile* pProfile = NULL;
hr = m_pReader->QueryInterface( IID_IWMProfile, ( VOID ** )&pProfile );
if ( FAILED( hr ) )
{
_tprintf( _T( "Could not QI for IWMProfile (hr=0x%08x).\n" ), hr );
return( hr );
}
m_dwAudioStreamNum = 0;
do
{
//
// Create the list of data unit extensions defined for all streams
// in this profile
//
hr = m_ExtDataList.Create( pProfile );
if ( FAILED( hr ) )
{
_tprintf( _T( "Creating data unit extension list failed (hr=0x%08x).\n" ), hr );
break;
}
//
// If output is writer, do not decompress samples
//
if ( NULL != m_pWriter )
{
hr = SetCodecOff( pProfile );
if ( FAILED( hr ) )
break;
}
//
// Turn on the user clock if reading from file
//
if ( !m_fNetReading )
{
hr = m_pReaderAdvanced->SetUserProvidedClock( TRUE );
if ( FAILED( hr ) )
{
_tprintf( _T( "SetUserProvidedClock failed (hr=0x%08x).\n" ), hr );
break;
}
}
//
// Configure writer - if attached
//
if ( NULL != m_pWriter )
{
hr = m_pWriter->Configure( pProfile );
if (FAILED ( hr ) )
break;
int attrs = sizeof( wszAttributes ) / sizeof( wszAttributes[ 0 ] );
for ( int i = 0; i < attrs; i++ )
{
hr = CopyAttribToWriter( wszAttributes[ i ] );
if (FAILED ( hr ) )
break;
}
if ( FAILED( hr ) )
break;
hr = CopyScriptsToWriter();
if (FAILED ( hr ) )
break;
}
}while( FALSE );
SAFE_RELEASE( pProfile );
return( hr );
}
///////////////////////////////////////////////////////////////
// Attach writer object used to output stream data
// This function stops an already attached writer object or
// closes an already attached WaveOut object
//
HRESULT CReader::AttachWriter( CWriter *pWriter )
{
HRESULT hr = S_OK;
if ( NULL == pWriter )
return E_INVALIDARG;
if ( NULL != m_pWriter )
m_pWriter->Stop();
m_pWriter = pWriter;
return( hr );
}
///////////////////////////////////////////////////////////////
// Start sending data to attached output. If a writer object has been
// attached, it is initialized. This function is synchronous and waits until all
// data is sent. Since the current writer object implementation has only a
// network sink, all data sent to this object is left compressed; that is, the
// codec is off and data is passed thru OnStreamSample. If a WaveOut object
// is attached, data is decompressed and passed through theOnSample method.
//
HRESULT CReader::Start()
{
HRESULT hr = S_OK;
if ( m_fReaderStarted || NULL == m_pReader )
{
return( E_FAIL );
}
if ( NULL != m_pWriter )
{
hr = m_pWriter->Start();
if ( FAILED( hr ) )
{
return( hr );
}
}
hr = m_pReader->Start( 0, 0, 1.0, 0 );
if ( FAILED( hr ) )
{
_tprintf( _T( "Could not start IWMReader (hr=0x%08x).\n" ), hr );
return( hr );
}
m_fReaderStarted = true;
//
// wait until all data sent to output
//
WaitForEvent();
if ( FAILED( m_hrAsync ) )
{
_tprintf( _T( "IWMReader failed (hr=0x%08x).\n" ), m_hrAsync );
return( m_hrAsync );
}
return( hr );
}
///////////////////////////////////////////////////////////////
// Stop both reader and writer object (if attached). Synchronous method
//
HRESULT CReader::Stop( HANDLE *hThread, int cHandles )
{
HRESULT hr = S_OK;
DWORD dwStatus = 0;
if ( !m_fReaderStarted || NULL == m_pReader )
{
return( E_FAIL );
}
if( NULL != hThread && cHandles > 0 )
{
dwStatus = WaitForMultipleObjects( cHandles, hThread, TRUE, 20000 );
if( WAIT_TIMEOUT == dwStatus )
{
return E_FAIL ;
}
}
if ( NULL != m_pWriter )
{
hr = m_pWriter->Stop();
if ( FAILED( hr ) )
{
_tprintf( _T( "Could not Stop IWMWriter (hr=0x%08x).\n" ), hr );
return( hr );
}
}
hr = m_pReader->Stop();
if ( FAILED( hr ) )
{
_tprintf( _T( "Could not Stop IWMReader (hr=0x%08x).\n" ), hr );
return( hr );
}
WaitForEvent();
m_fReaderStarted = false;
return hr ;
}
///////////////////////////////////////////////////////////////
// Stop and close reader object. If WaveOut is attached, wait
// until it finishes with all samples and close it before closing
// the reader. Asynchronous method
//
HRESULT CReader::Close( HANDLE *hThread, int cHandles )
{
HRESULT hr = S_OK ;
if ( m_fReaderStarted )
{
hr = Stop( hThread, cHandles );
if ( FAILED( hr ) )
{
_tprintf( _T( "Error while stopping reader (hr=0x%08x).\n" ), hr);
}
}
if( NULL != m_pReader )
{
hr = m_pReader->Close();
}
if( FAILED ( hr ) )
{
return hr;
}
WaitForEvent();
return hr;
}
///////////////////////////////////////////////////////////////
// Get current reader statistics
//
HRESULT CReader::GetStats( WM_READER_STATISTICS *pStats )
{
HRESULT hr = S_OK;
if ( NULL == pStats )
{
return E_INVALIDARG;
}
if ( NULL != m_pReaderAdvanced )
{
ZeroMemory( pStats, sizeof( WM_READER_STATISTICS ));
pStats->cbSize = sizeof( WM_READER_STATISTICS );
hr = m_pReaderAdvanced->GetStatistics( pStats );
}
return( hr );
}
///////////////////////////////////////////////////////////////
// Used for synchronization
//
void CReader::WaitForEvent()
{
WaitForSingleObject( m_hEvent, INFINITE );
return;
}
///////////////////////////////////////////////////////////////
// Callback used to send decompressed samples to attached
// WaveOut object
//
HRESULT CReader::OnSample( DWORD dwOutputNum,
QWORD cnsSampleTime,
QWORD cnsSampleDuration,
DWORD dwFlags,
INSSBuffer __RPC_FAR *pSample, void __RPC_FAR *pvContext )
{
HRESULT hr = S_OK;
if ( NULL == pSample )
{
return E_INVALIDARG;
}
if ( NULL != m_hEvent )
{
//
// TODO: Insert per-sample code here
//
}
return S_OK;
}
///////////////////////////////////////////////////////////////
// Callback function used to send compressed samples to attached writer object
//
HRESULT CReader::OnStreamSample( WORD wStreamNum,
QWORD cnsSampleTime,
QWORD cnsSampleDuration,
DWORD dwFlags,
INSSBuffer __RPC_FAR *pSample,
void __RPC_FAR *pvContext )
{
HRESULT hr = S_OK;
if ( NULL == m_pWriter || NULL == pSample)
{
return E_INVALIDARG;
}
if ( cnsSampleTime % cnsSampleDuration )
{
_tprintf( _T( "*" ) );
}
//
// Getting extension data:
// find what extensions are defined for this stream and then
// iterate thru them, get their values and display them
//
CExtensionData *pExtData = NULL;
INSSBuffer3 *pMS3 = NULL;
DWORD pvBuffer = 0;
bool ret = m_ExtDataList.Find( ( WORD )wStreamNum, &pExtData );
if( ret )
{
hr = pSample->QueryInterface( IID_INSSBuffer3, (void**)&pMS3 );
}
while( ret )
{
DWORD dwTemp = pExtData->m_cbExtensionDataSize;
hr = pMS3->GetProperty( pExtData->m_guidDUExt, pExtData->m_pValue, &dwTemp );
pExtData->DisplayData();
ret = m_ExtDataList.Find( 0, &pExtData );
}
SAFE_RELEASE( pMS3 );
//
// Pass uncompressed samples to writer
//
hr = m_pWriter->WriteStreamSample( wStreamNum,
cnsSampleTime,
0,
cnsSampleDuration,
dwFlags,
pSample );
return( hr );
}
///////////////////////////////////////////////////////////////
// Callback used for setting synchronization event and getting
// current status of data transfer
//
HRESULT CReader::OnStatus( WMT_STATUS Status,
HRESULT hr,
WMT_ATTR_DATATYPE dwType,
BYTE __RPC_FAR *pValue,
void __RPC_FAR *pvContext )
{
switch( Status )
{
case WMT_OPENED:
m_hrAsync = hr;
SetEvent( m_hEvent );
break;
case WMT_EOF:
m_fEOF = true;
_tprintf( _T( "EndOfStream detected in reader.\n" ) );
SetEvent( m_hEvent );
break;
case WMT_CLOSED:
m_hrAsync = hr;
SetEvent( m_hEvent );
break ;
case WMT_STOPPED:
m_hrAsync = hr;
SetEvent( m_hEvent );
break;
case WMT_STARTED:
//
//Ask for the specific duration of the stream to be delivered
//
if (!m_fNetReading)
{
m_qwTime = 0;
m_qwTime += 1000 * 10000;
hr = m_pReaderAdvanced->DeliverTime( m_qwTime );
}
break;
case WMT_LOCATING:
//
// The stream is being read from a network; set the variable.
//
m_fNetReading = TRUE;
break;
case WMT_TIMER :
_tprintf( _T( "Timer ...........................\n" ) );
break;
}
return S_OK;
}
///////////////////////////////////////////////////////////////
// Callback : set user defined clock if stream data is read from file
HRESULT CReader::OnTime( QWORD cnsCurrentTime, void __RPC_FAR *pvContext)
{
//
// Keep asking for the specific duration of the stream till EOF
//
HRESULT hr = S_OK;
if( NULL == m_fEOF )
{
m_qwTime += 10000000 ; // 1 second
hr = m_pReaderAdvanced->DeliverTime( m_qwTime );
}
return( hr );
}
///////////////////////////////////////////////////////////////
// Switch codec off so compressed samples are passed through OnStreamSample
//
HRESULT CReader::SetCodecOff(IWMProfile* pProfile)
{
HRESULT hr = S_OK;
IWMStreamConfig* pStream = NULL;
DWORD dwStreams = 0;
GUID pguidStreamType;
if (!pProfile)
return E_INVALIDARG;
hr = pProfile->GetStreamCount( &dwStreams );
if ( FAILED( hr ) )
{
_tprintf( _T( "GetStreamCount on IWMProfile failed (hr=0x%08x).\n" ), hr );
return hr;
}
for ( DWORD i = 0; i < dwStreams; i++ )
{
hr = pProfile->GetStream( i, &pStream );
if ( FAILED( hr ) )
{
_tprintf( _T( "Could not get Stream %d of %d from IWMProfile (hr=0x%08x).\n" ),
i, dwStreams, hr );
break;
}
WORD wStreamNumber = 0 ;
//
// Get the stream number of the current stream
//
hr = pStream->GetStreamNumber( &wStreamNumber );
if ( FAILED( hr ) )
{
_tprintf( _T( "Could not get stream number from IWMStreamConfig %d of %d (hr=0x%08x).\n" ),
i, dwStreams, hr );
break;
}
hr = pStream->GetStreamType( &pguidStreamType );
if ( FAILED( hr ) )
{
_tprintf( _T("Could not get stream type of stream %d of %d from IWMStreamConfig (hr=0x%08x).\n" ),
i, dwStreams, hr ) ;
break ;
}
//
// Set the stream to be received in compressed mode
//
hr = m_pReaderAdvanced->SetReceiveStreamSamples( wStreamNumber, TRUE );
if ( FAILED( hr ) )
{
_tprintf( _T( "Could not SetReceivedStreamSamples for stream number %d (hr=0x%08x).\n" ),
wStreamNumber, hr );
break;
}
SAFE_RELEASE( pStream );
}
return( hr );
}
///////////////////////////////////////////////////////////////
// Copy attribute value from reader to attached writer.
// pwszName - attribute name
//
HRESULT CReader::CopyAttribToWriter( const WCHAR *pwszName )
{
WORD nstreamNum = 0;
WORD cbLength = 0;
WMT_ATTR_DATATYPE type;
HRESULT hr = S_OK;
BYTE* pValue = NULL;
if ( NULL == pwszName || 0 == wcslen(pwszName))
{
return E_INVALIDARG;
}
if ( NULL == m_pWriter)
{
return E_FAIL;
}
//
// Get the number of bytes to be allocated for pValue
//
hr = m_pReaderHeaderInfo->GetAttributeByName( &nstreamNum,
pwszName,
&type,
NULL,
&cbLength );
if ( FAILED( hr ) && hr != ASF_E_NOTFOUND )
{
_tprintf( _T( "GetAttributeByName failed for Attribute name %ws (hr=0x%08x).\n" ), pwszName, hr);
return hr;
}
if( 0 == cbLength && hr == ASF_E_NOTFOUND )
{
return S_OK;
}
pValue = new BYTE[ cbLength ];
if ( NULL == pValue )
{
_tprintf( _T( "Unable to allocate memory for the Attribute name %ws" ), pwszName);
return E_OUTOFMEMORY;
}
//
// Dummy do-while loop
//
do
{
//
// Get the value
//
hr = m_pReaderHeaderInfo->GetAttributeByName( &nstreamNum,
pwszName,
&type,
pValue,
&cbLength );
if ( FAILED( hr ) )
{
_tprintf( _T( "GetAttributeByName failed for Attribute name %ws (hr=0x%08x).\n" ),
pwszName, hr);
break;
}
//
// Set the attribute
//
hr = m_pWriter->SetAttribute( nstreamNum,
pwszName,
type,
pValue,
cbLength );
if ( FAILED( hr ) )
{
_tprintf( _T( "SetAttribute failed for Attribute name %ws (hr=0x%08x).\n" ),
pwszName, hr);
break;
}
}
while( FALSE );
if (NULL != pValue)
{
delete[] pValue;
pValue = NULL;
}
return( hr );
}
///////////////////////////////////////////////////////////////
// Copy all scripts from reader to attached writer
//
HRESULT CReader::CopyScriptsToWriter()
{
HRESULT hr = S_OK;
WCHAR* pwszCommand = NULL;
WCHAR* pwszType = NULL;
QWORD cnsScriptTime = 0;
WORD cScript = 0;
WORD cchTypeLen = 0;
WORD cchCommandLen = 0;
if ( NULL == m_pWriter || NULL == m_pReaderHeaderInfo)
return E_FAIL;
hr = m_pReaderHeaderInfo->GetScriptCount( &cScript );
if ( FAILED( hr ) )
{
_tprintf( _T( "GetScriptCount failed (hr=0x%08x).\n" ), hr);
return hr;
}
for( WORD i = 0 ; i < cScript ; i++)
{
//
// Get the memory required for this script
//
hr = m_pReaderHeaderInfo->GetScript( i ,
NULL ,
&cchTypeLen ,
NULL ,
&cchCommandLen ,
&cnsScriptTime );
if ( FAILED( hr ) )
{
_tprintf( _T( "GetScript failed for Script no %d (hr=0x%08x).\n" ), i, hr ) ;
break ;
}
pwszType = new WCHAR[ cchTypeLen ];
pwszCommand = new WCHAR[ cchCommandLen ];
if( NULL == pwszType || NULL == pwszCommand )
{
_tprintf( _T( "Insufficient Memory" ) );
hr = E_OUTOFMEMORY;
break;
}
//
// Get the script
//
hr = m_pReaderHeaderInfo->GetScript( i ,
pwszType ,
&cchTypeLen ,
pwszCommand ,
&cchCommandLen ,
&cnsScriptTime );
if ( FAILED( hr ) )
{
_tprintf( _T( "GetScript failed for Script no %d (hr=0x%08x).\n" ), i, hr );
break;
}
//
// Add the script to the writer
//
hr = m_pWriter->AddScript( pwszType ,
pwszCommand ,
cnsScriptTime );
if ( FAILED( hr ) )
{
_tprintf( _T( "AddScript failed for Script no %d (hr=0x%08x).\n" ), i, hr);
break;
}
delete[] pwszType;
delete[] pwszCommand;
pwszType = pwszCommand = NULL;
cchTypeLen = 0;
cchCommandLen = 0;
}
if(NULL != pwszType)
{
delete[] pwszType;
pwszType = NULL;
}
if(NULL != pwszCommand)
{
delete[] pwszCommand;
pwszCommand = NULL;
}
pwszType = pwszCommand = NULL;
return( hr );
}
///////////////////////////////////////////////////////////////
////////
HRESULT STDMETHODCALLTYPE CReader::QueryInterface( REFIID riid, void __RPC_FAR *__RPC_FAR *ppvObject)
{
if ( riid == IID_IWMReaderCallback )
{
*ppvObject = ( IWMReaderCallback* )this;
}
else if( riid == IID_IWMReaderCallbackAdvanced )
{
*ppvObject = ( IWMReaderCallbackAdvanced* )this;
}
else if( riid == IID_IUnknown )
{
*ppvObject = this;
}
else
{
*ppvObject = NULL;
return E_NOINTERFACE;
}
return S_OK;
}