Sequencer 소스 예제 코드
이 항목에서는 Microsoft Media Foundation에서 Sequencer 원본 을 사용하기 위한 예제 코드를 보여 봅니다. 이 항목에는 다음과 같은 섹션이 포함됩니다.
CPlaylist 클래스
클래스는 CPlaylist
Media Foundation을 CPlayer
사용하여 미디어 파일을 재생하는 방법 자습서에 표시된 클래스에서 파생됩니다.
const DWORD MAX_PLAYLIST_SEGMENTS = 128;
// For this example, there is a hard-coded limit to the number of playlist
// segments, to avoid using a dynamically sized list.
class CPlaylist : public CPlayer
{
private:
IMFSequencerSource *m_pSequencerSource;
IMFPresentationClock *m_pPresentationClock;
MFTIME m_PresentationTimeOffset;
DWORD m_ActiveSegment;
LONGLONG m_hnsSegmentDuration;
// Table of segment IDs and topology IDs.
struct
{
MFSequencerElementId SegmentID;
TOPOID TopoID;
} m_segments[MAX_PLAYLIST_SEGMENTS];
DWORD m_count;
private:
MFSequencerElementId LastSegment() const
{
return m_segments[ m_count - 1].SegmentID;
}
HRESULT LookupTopoID(TOPOID id, DWORD *pIndex);
HRESULT OnSessionEvent(IMFMediaEvent *pEvent, MediaEventType meType);
HRESULT OnTopologyStatus(IMFMediaEvent *pEvent);
HRESULT OnNewPresentation(IMFMediaEvent *pEvent);
HRESULT AddSegment(PCWSTR pszURL);
HRESULT QueueNextSegment(IMFPresentationDescriptor *pPD);
CPlaylist(HWND hWnd, HRESULT *phr);
~CPlaylist();
HRESULT Initialize();
public:
static HRESULT CreateInstance(HWND hwnd, CPlaylist **ppPlayer);
HRESULT AddToPlaylist(PCWSTR pszURL);
HRESULT DeleteSegment(DWORD index);
HRESULT SkipTo(DWORD index);
DWORD NumSegments() { return m_count; }
DWORD ActiveSegment() const { return m_ActiveSegment; }
HRESULT GetPlaybackTime(MFTIME *phnsPresentation, MFTIME *phnsSegment);
LONGLONG SegmentDuration() { return m_hnsSegmentDuration; }
};
CPlaylist 인스턴스 만들기
메서드는 CPlaylist::CreateInstance
새 CPlaylist
개체를 만듭니다. 내부적으로 이 메서드는 를 호출 CPlaylist::Initialize
하여 개체를 초기화합니다. 메서드는 Initialize
MFCreateSequencerSource 를 호출하여 시퀀스 원본을 만듭니다. 또한 IMFMediaSession::GetClock 을 호출하여 프레젠테이션 시계에 대한 포인터를 가져옵니다.
/*static*/
HRESULT CPlaylist::CreateInstance(HWND hwnd, CPlaylist **ppPlayer)
{
*ppPlayer = NULL;
HRESULT hr = S_OK;
CPlaylist *pPlayer = new (std::nothrow) CPlaylist(hwnd, &hr);
if (pPlayer == NULL)
{
return E_OUTOFMEMORY;
}
if (SUCCEEDED(hr))
{
hr = pPlayer->Initialize();
}
if (SUCCEEDED(hr))
{
*ppPlayer = pPlayer;
}
else
{
pPlayer->Release();
}
return hr;
}
HRESULT CPlaylist::Initialize()
{
IMFClock *pClock = NULL;
HRESULT hr = CPlayer::CreateSession();
if (FAILED(hr))
{
goto done;
}
// Create an instance of the sequencer Source.
hr = MFCreateSequencerSource(NULL, &m_pSequencerSource);
if (FAILED(hr))
{
goto done;
}
hr = m_pSequencerSource->QueryInterface(IID_PPV_ARGS(&m_pSource));
if (FAILED(hr))
{
goto done;
}
// Get the presentation clock.
hr = m_pSession->GetClock(&pClock);
if (FAILED(hr))
{
goto done;
}
hr = pClock->QueryInterface(IID_PPV_ARGS(&m_pPresentationClock));
done:
SafeRelease(&pClock);
return hr;
}
CPlaylist::CPlaylist(HWND hWnd, HRESULT *phr)
: CPlayer(NULL, hWnd, phr),
m_pSequencerSource (NULL),
m_pPresentationClock (NULL),
m_PresentationTimeOffset (0),
m_ActiveSegment((DWORD)-1),
m_hnsSegmentDuration(0),
m_count(0)
{
}
CPlaylist::~CPlaylist()
{
SafeRelease(&m_pSequencerSource);
SafeRelease(&m_pPresentationClock);
}
재생 목록 세그먼트 추가 및 제거
메서드는 AddSegment
새 재생 목록 세그먼트를 추가합니다.
이 메서드는 다음 단계를 수행합니다.
- 재생 토폴로지를 만듭니다. 이 단계의 코드는 재생 토폴로지 만들기 항목에 나와 있습니다.
- IMFSequencerSource::AppendTopology를 호출하여 토폴로지를 재생 목록에 추가합니다.
- 첫 번째 세그먼트에서 재생 기간을 포함하는 MF_PD_DURATION 특성의 값을 가져옵니다.
- 조회 테이블에 세그먼트 ID 및 토폴로지 ID를 저장합니다.
// Adds a segment to the sequencer.
HRESULT CPlaylist::AddSegment(PCWSTR pszURL)
{
IMFMediaSource *pMediaSource = NULL;
IMFPresentationDescriptor *pPD = NULL;
IMFTopology *pTopology = NULL;
MFSequencerElementId SegmentId;
TOPOID TopologyID = 0;
HRESULT hr = CreateMediaSource(pszURL, &pMediaSource);
if (FAILED(hr))
{
goto done;
}
hr = pMediaSource->CreatePresentationDescriptor(&pPD);
if (FAILED(hr))
{
goto done;
}
hr = CreatePlaybackTopology(pMediaSource, pPD, m_hwndVideo, &pTopology);
if (FAILED(hr))
{
goto done;
}
hr = m_pSequencerSource->AppendTopology(
pTopology,
SequencerTopologyFlags_Last,
&SegmentId
);
if (FAILED(hr))
{
goto done;
}
hr = pTopology->GetTopologyID(&TopologyID);
if (FAILED(hr))
{
goto done;
}
if (m_count == 0)
{
// Get the segment duration
m_hnsSegmentDuration = MFGetAttributeUINT64(pPD, MF_PD_DURATION, 0);
}
m_segments[m_count].SegmentID = SegmentId;
m_segments[m_count].TopoID = TopologyID;
m_count++;
done:
SafeRelease(&pMediaSource);
SafeRelease(&pTopology);
SafeRelease(&pPD);
return hr;
}
메서드는 AddSegment
에 대한 프라이빗 CPlaylist
이며 다음 공용 메서드에서 호출됩니다.
// Adds a new segment to the playlist.
HRESULT CPlaylist::AddToPlaylist(PCWSTR pszURL)
{
if (NumSegments() >= MAX_PLAYLIST_SEGMENTS)
{
return MF_E_OUT_OF_RANGE;
}
HRESULT hr = S_OK;
IMFPresentationDescriptor *pPD = NULL;
BOOL bFirstSegment = (NumSegments() == 0);
if (!bFirstSegment)
{
// Remove the "last segment" flag from the last segment.
hr = m_pSequencerSource->UpdateTopologyFlags(LastSegment(), 0);
if (FAILED(hr))
{
goto done;
}
}
// Create the topology and add it to the sequencer.
hr = AddSegment(pszURL);
if (FAILED(hr))
{
goto done;
}
// If this is the first segment, queue it on the session.
if (bFirstSegment)
{
hr = m_pSource->CreatePresentationDescriptor(&pPD);
if (FAILED(hr))
{
goto done;
}
hr = QueueNextSegment(pPD);
if (FAILED(hr))
{
goto done;
}
}
if (m_state < Started)
{
m_state = OpenPending;
}
done:
SafeRelease(&pPD);
return hr;
}
첫 번째 재생 목록 세그먼트를 만들 때 다음과 같이 미디어 세션에서 세그먼트 토폴로지를 큐에 대기해야 합니다.
- IMFMediaSourceTopologyProvider 인터페이스에 대한 시퀀서 원본을 쿼리합니다.
- 프레젠테이션 설명자를 IMFMediaSourceTopologyProvider::GetMediaSourceTopology 메서드에 전달합니다. 이 메서드는 세그먼트 토폴로지에 대한 포인터를 가져옵니다. 이 토폴로지는 이전에 만든 재생 토폴로지와 정확히 동일하지 않습니다. 대신 해당 토폴로지의 수정된 버전입니다. 자세한 내용은 Sequencer 원본 정보를 참조하세요.
- IMFMediaSession::SetTopology를 호출하여 미디어 세션에서 토폴로지를 큐에 추가합니다.
다음 코드는 이러한 단계를 보여줍니다. 재생 목록이 다음 세그먼트를 미리 등록할 때도 동일한 코드가 사용됩니다.
// Queues the next topology on the session.
HRESULT CPlaylist::QueueNextSegment(IMFPresentationDescriptor *pPD)
{
IMFMediaSourceTopologyProvider *pTopoProvider = NULL;
IMFTopology *pTopology = NULL;
//Get the topology for the presentation descriptor
HRESULT hr = m_pSequencerSource->QueryInterface(IID_PPV_ARGS(&pTopoProvider));
if (FAILED(hr))
{
goto done;
}
hr = pTopoProvider->GetMediaSourceTopology(pPD, &pTopology);
if (FAILED(hr))
{
goto done;
}
//Set the topology on the media session
m_pSession->SetTopology(NULL, pTopology);
done:
SafeRelease(&pTopoProvider);
SafeRelease(&pTopology);
return hr;
}
재생 목록 세그먼트를 삭제하려면 IMFSequencerSource::D eleteTopology를 호출합니다. 세그먼트는 세그먼트 ID로 지정됩니다. 애플리케이션이 세그먼트 ID 목록을 캐시해야 하는 이유입니다.
// Deletes the corresponding topology from the sequencer source
HRESULT CPlaylist::DeleteSegment(DWORD index)
{
if (index >= m_count)
{
return E_INVALIDARG;
}
if (index == m_ActiveSegment)
{
return E_INVALIDARG;
}
MFSequencerElementId LastSegId = LastSegment();
MFSequencerElementId SegmentID = m_segments[index].SegmentID;
HRESULT hr = m_pSequencerSource->DeleteTopology( SegmentID );
if (FAILED(hr))
{
goto done;
}
//Delete the segment entry from the list.
// Move everything up one slot.
for (DWORD i = index; i < m_count - 1; i++)
{
m_segments[i] = m_segments[i + 1];
}
m_count --;
// Is the deleted topology the last one?
if (LastSegId == SegmentID)
{
//Get the new last segment id
LastSegId = LastSegment();
//set this topology as the last in the sequencer
hr = m_pSequencerSource->UpdateTopologyFlags(LastSegId, SequencerTopologyFlags_Last);
}
done:
return hr;
}
세션 이벤트 처리
HRESULT CPlaylist::OnTopologyStatus(IMFMediaEvent *pEvent)
{
IMFTopology *pTopology = NULL;
UINT value = 0;
DWORD SegmentIndex;
TOPOID TopoID;
HRESULT hr = pEvent->GetUINT32(MF_EVENT_TOPOLOGY_STATUS, &value);
if (FAILED(hr))
{
goto done;
}
switch(value)
{
case MF_TOPOSTATUS_STARTED_SOURCE:
// Get information about the new segment
hr = GetEventObject(pEvent, &pTopology);
if (FAILED(hr))
{
hr = S_OK;
goto done;
}
hr = pTopology->GetTopologyID(&TopoID);
if (FAILED(hr))
{
goto done;
}
hr = LookupTopoID(TopoID, &SegmentIndex);
if (FAILED(hr))
{
goto done;
}
m_ActiveSegment = SegmentIndex;
hr = GetDurationFromTopology(pTopology, &m_hnsSegmentDuration);
break;
case MF_TOPOSTATUS_ENDED:
m_ActiveSegment = (DWORD)-1;
break;
case MF_TOPOSTATUS_READY:
if (m_state == OpenPending)
{
m_state = Stopped;
}
break;
}
done:
SafeRelease(&pTopology);
return hr;
}
HRESULT CPlaylist::LookupTopoID(TOPOID id, DWORD *pIndex)
{
DWORD index;
for (index = 0; index < m_count; index++)
{
if (m_segments[ index ].TopoID == id)
{
break;
}
}
if (index == m_count)
{
return MF_E_NOT_FOUND;
}
*pIndex = index;
return S_OK;
}
HRESULT CPlaylist::OnSessionEvent(IMFMediaEvent *pEvent, MediaEventType meType)
{
if (meType == MESessionNotifyPresentationTime)
{
m_PresentationTimeOffset =
MFGetAttributeUINT64(pEvent, MF_EVENT_PRESENTATION_TIME_OFFSET, 0);
}
return S_OK;
}
HRESULT CPlaylist::OnNewPresentation(IMFMediaEvent *pEvent)
{
IMFPresentationDescriptor *pPD = NULL;
HRESULT hr = GetEventObject(pEvent, &pPD);
if (SUCCEEDED(hr))
{
// Queue the next segment on the media session
hr = QueueNextSegment(pPD);
}
SafeRelease(&pPD);
return hr;
}
// Skips to the specified segment in the sequencer source
HRESULT CPlaylist::SkipTo(DWORD index)
{
if (index >= m_count)
{
return E_INVALIDARG;
}
MFSequencerElementId ID = m_segments[index].SegmentID;
PROPVARIANT var;
HRESULT hr = MFCreateSequencerSegmentOffset(ID, NULL, &var);
if (SUCCEEDED(hr))
{
hr = m_pSession->Start(&MF_TIME_FORMAT_SEGMENT_OFFSET, &var);
PropVariantClear(&var);
}
return hr;
}
HRESULT GetDurationFromTopology(IMFTopology *pTopology, LONGLONG *phnsDuration)
{
*phnsDuration = 0;
IMFCollection *pSourceNodes = NULL;
IMFTopologyNode *pNode = NULL;
IMFPresentationDescriptor *pPD = NULL;
HRESULT hr = pTopology->GetSourceNodeCollection(&pSourceNodes);
if (FAILED(hr))
{
goto done;
}
hr = GetCollectionObject(pSourceNodes, 0, &pNode);
if (FAILED(hr))
{
goto done;
}
hr = pNode->GetUnknown(MF_TOPONODE_PRESENTATION_DESCRIPTOR, IID_PPV_ARGS(&pPD));
if (FAILED(hr))
{
goto done;
}
*phnsDuration = MFGetAttributeUINT64(pPD, MF_PD_DURATION, 0);
done:
SafeRelease(&pSourceNodes);
SafeRelease(&pNode);
SafeRelease(&pPD);
return hr;
}
HRESULT CPlaylist::GetPlaybackTime(
MFTIME *phnsPresentation, // Relative to start of presentation.
MFTIME *phnsSegment // Relative to start of segment.
)
{
*phnsPresentation = 0;
*phnsSegment = 0;
HRESULT hr = m_pPresentationClock->GetTime(phnsPresentation);
if (SUCCEEDED(hr))
{
*phnsSegment = (*phnsPresentation) - m_PresentationTimeOffset;
}
return hr;
}
관련 항목