/*
Copyright (C) 2010, Heikki Salo
All rights reserved.
Distributed under the BSD license:
http://www.opensource.org/licenses/bsd-license.php
*/
#include "stdafx.h"
#include "DPMedia.hpp"
#include "DPUtils.hpp"
#include "DPWindow.hpp"
#include "DPTexture.hpp"
#include "DPDevice.hpp"
#pragma comment(lib, "evr_vista.lib")
#pragma comment(lib, "mf_vista.lib")
#pragma comment(lib, "mfplat_vista.lib")
#pragma comment(lib, "mfuuid.lib")
#pragma comment(lib, "strmiids.lib")
#pragma comment(lib, "Shlwapi.lib")
bool DPIsMediaInitialized = false;
UINT DPMediaCount = 0;
void DPInitMedia()
{
if (DPIsMediaInitialized)
return;
TestHR(MFStartup(MF_VERSION));
DPIsMediaInitialized = true;
}
void DPCloseMedia()
{
if (!DPIsMediaInitialized)
return;
MFShutdown();
DPIsMediaInitialized = false;
}
void DPMedia::release()
{
if (window) {
if (window->getMedia() == this)
window->setMedia(0);
window = 0;
}
if (mediaSession) {
mediaSession->Close(); //XXX should sync in here...
}
if (mediaSource)
mediaSource->Shutdown();
if (mediaSession) {
mediaSession->Shutdown();
}
SafeRelease(audioVolume);
SafeRelease(videoDisplay);
SafeRelease(mediaSource);
SafeRelease(mediaSession);
}
DPMedia::~DPMedia()
{
DPMediaCount--;
release();
}
DPMedia::DPMedia(python::object& pyPath, DPWindow* argWin)
{
mediaSession = 0;
mediaSource = 0;
videoDisplay = 0;
audioVolume = 0;
window = argWin;
if (window && window->getMedia()) {
DPThrow("Window is already associated with a Media");
}
pyunicode path(pyPath);
DPInitMedia();
//XXX - A lot of references will leak if there is an error, fix it.
TestHR(MFCreateMediaSession(0, &mediaSession));
IMFSourceResolver* solver = 0;
TestHR(MFCreateSourceResolver(&solver));
MF_OBJECT_TYPE objectType = MF_OBJECT_INVALID;
IUnknown* mediaSourceOriginal = 0;
TestHR(solver->CreateObjectFromURL(path.c_str(), MF_RESOLUTION_MEDIASOURCE, 0, &objectType, &mediaSourceOriginal));
TestHR(mediaSourceOriginal->QueryInterface<IMFMediaSource>(&mediaSource));
mediaSourceOriginal->Release();
solver->Release();
//Create topology
IMFTopology* topology = 0;
TestHR(MFCreateTopology(&topology));
IMFPresentationDescriptor* sourceDesc = 0;
TestHR(mediaSource->CreatePresentationDescriptor(&sourceDesc));
DWORD sourceStreams = 0;
TestHR(sourceDesc->GetStreamDescriptorCount(&sourceStreams));
HWND windowHandle = 0;
if (window) {
windowHandle = window->getHandle();
}
for (DWORD i=0; i < sourceStreams; ++i) {
AddBranchToPartialTopology(topology, sourceDesc, i, windowHandle);
}
TestHR(mediaSession->SetTopology(0, topology));
sourceDesc->Release();
topology->Release();
if (window && window->isDPWindow()) {
//Other windows don't know how to respond to WM_DP_MEDIAEVENT.
DPMediaAsyncCallback* callback = DPMediaAsyncCallback::createNew(this);
TestHR(mediaSession->BeginGetEvent(callback, 0));
callback->Release();
}
if (window) {
window->setMedia(this);
}
DPMediaCount++;
}
void DPMedia::play()
{
PROPVARIANT varStart;
PropVariantInit(&varStart);
varStart.vt = VT_EMPTY;
HRESULT hr = mediaSession->Start(&GUID_NULL, &varStart);
PropVariantClear(&varStart);
TestHR(hr);
}
void DPMedia::setPosition(MFTIME position)
{
PROPVARIANT varStart;
varStart.vt = VT_I8;
varStart.hVal.QuadPart = position;
TestHR(mediaSession->Start(0, &varStart));
}
void DPMedia::pause()
{
TestHR(mediaSession->Pause());
}
UINT64 DPMedia::getPosition()
{
IMFClock* mediaClock = 0;
TestHR(mediaSession->GetClock(&mediaClock));
IMFPresentationClock* presentClock = 0;
HRESULT hr = mediaClock->QueryInterface<IMFPresentationClock>(&presentClock);
mediaClock->Release();
TestHR(hr);
//Don't check GetTime()'s HRESULT
MFTIME mediaTime = 0;
presentClock->GetTime(&mediaTime);
presentClock->Release();
return mediaTime;
}
UINT64 DPMedia::getDuration()
{
IMFPresentationDescriptor* desc;
TestHR(mediaSource->CreatePresentationDescriptor(&desc));
UINT64 duration = 0;
HRESULT hr = desc->GetUINT64(MF_PD_DURATION, &duration);
desc->Release();
TestHR(hr);
return duration;
}
python::object DPMedia::getEvents()
{
python::list events;
while (true) {
IMFMediaEvent* mediaEvent = 0;
HRESULT hr = mediaSession->GetEvent(MF_EVENT_FLAG_NO_WAIT, &mediaEvent);
if (hr == MF_E_NO_EVENTS_AVAILABLE) {
break;
}
TestHR(hr);
MediaEventType eventType;
hr = mediaEvent->GetType(&eventType);
if (SUCCEEDED(hr)) {
onEvent(mediaEvent, eventType);
}
mediaEvent->Release();
TestHR(hr);
events.append((DWORD)eventType);
}
return events;
}
python::object DPMedia::getVideoSize()
{
if (!hasVideo())
DPThrow("No video");
SIZE videoSize = {0};
TestHR(videoDisplay->GetNativeVideoSize(&videoSize, 0));
return python::make_tuple(videoSize.cx, videoSize.cy);
}
float DPMedia::getVolume()
{
if (!hasAudio())
DPThrow("No audio");
float level = 0.0f;
TestHR(audioVolume->GetMasterVolume(&level));
return level;
}
void DPMedia::setVolume(float vol)
{
if (!hasAudio())
DPThrow("No audio");
TestHR(audioVolume->SetMasterVolume(vol));
}
DPTexture* DPMedia::getTexture()
{
if (!hasVideo())
DPThrow("No video for the texture");
ID3D11Device* device = getDPDevice()->getDevice();
BITMAPINFOHEADER header;
header.biSize = sizeof(header);
BYTE* data = 0;
DWORD size = 0;
LONGLONG timeStamp = 0;
HRESULT hr;
Py_BEGIN_ALLOW_THREADS
hr = videoDisplay->GetCurrentImage(&header, &data, &size, &timeStamp);
Py_END_ALLOW_THREADS
TestHR(hr);
BITMAPFILEHEADER fileHeader;
fileHeader.bfType = 0x4D42;
fileHeader.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + size;
fileHeader.bfReserved1 = 0;
fileHeader.bfReserved2 = 0;
fileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
boost::scoped_array<BYTE> bitmap(new (std::nothrow) BYTE[fileHeader.bfSize]);
if (bitmap) {
//Use D3DX11CreateTextureFromMemory(), so we have to add
//headers to the data. You could create a new texture and initialize
//it directly, but this is easier.
ID3D11Resource* resultResource = 0;
Py_BEGIN_ALLOW_THREADS
memcpy(bitmap.get(), &fileHeader, sizeof(fileHeader));
memcpy(bitmap.get() + sizeof(fileHeader), &header, sizeof(header));
memcpy(bitmap.get() + sizeof(fileHeader) + sizeof(header), data, size);
CoTaskMemFree(data);
hr = D3DX11CreateTextureFromMemory(device,
bitmap.get(), fileHeader.bfSize, 0, 0, &resultResource, 0);
Py_END_ALLOW_THREADS
TestHR(hr);
ID3D11Texture2D* resultTexture = 0;
hr = resultResource->QueryInterface<ID3D11Texture2D>(&resultTexture);
resultResource->Release();
TestHR(hr);
DPTexture* dptexture = new DPTexture(resultTexture);
resultTexture->Release();
return dptexture;
}
else {
CoTaskMemFree(data);
DPThrow("Can't allocate memory");
}
return 0; //Should never get in here.
}
void DPMedia::repaint()
{
if (videoDisplay) {
videoDisplay->RepaintVideo();
}
}
void DPMedia::resize(UINT w, UINT h)
{
if (videoDisplay) {
RECT rect = { 0, 0, w, h };
videoDisplay->SetVideoPosition(0, &rect);
}
}
//onEvent() is called from the window proc / getEvents().
void DPMedia::onEvent(IMFMediaEvent* mediaEvent, MediaEventType eventType)
{
if (eventType == MESessionTopologyStatus) {
MF_TOPOSTATUS topoStatus = MF_TOPOSTATUS_INVALID;
HRESULT hr = mediaEvent->GetUINT32(MF_EVENT_TOPOLOGY_STATUS, (UINT32*)&topoStatus);
if (SUCCEEDED(hr)) {
if (topoStatus == MF_TOPOSTATUS_READY) {
topologyReady();
}
}
}
}
void DPMedia::topologyReady()
{
//Video, if any.
SafeRelease(videoDisplay);
MFGetService(mediaSession, MR_VIDEO_RENDER_SERVICE, IID_PPV_ARGS(&videoDisplay));
//Sound, if any.
SafeRelease(audioVolume);
MFGetService(mediaSession, MR_POLICY_VOLUME_SERVICE, IID_PPV_ARGS(&audioVolume));
}
void DPMedia::AddBranchToPartialTopology(IMFTopology* topology, IMFPresentationDescriptor* sourceDesc, DWORD index, HWND windowHandle)
{
BOOL selected = FALSE;
IMFStreamDescriptor* sourceSD = 0;
TestHR(sourceDesc->GetStreamDescriptorByIndex(index, &selected, &sourceSD));
if (selected) {
IMFTopologyNode* sourceNode = 0;
CreateSourceStreamNode(mediaSource, sourceDesc, sourceSD, &sourceNode);
IMFTopologyNode* outputNode = 0;
CreateOutputNode(sourceSD, windowHandle, &outputNode);
TestHR(topology->AddNode(sourceNode));
TestHR(topology->AddNode(outputNode));
TestHR(sourceNode->ConnectOutput(0, outputNode, 0));
outputNode->Release();
sourceNode->Release();
}
sourceSD->Release();
}
void DPMedia::CreateSourceStreamNode(IMFMediaSource* source, IMFPresentationDescriptor* sourcePD,
IMFStreamDescriptor* sourceSD, IMFTopologyNode** outNode)
{
IMFTopologyNode* node = 0;
TestHR(MFCreateTopologyNode(MF_TOPOLOGY_SOURCESTREAM_NODE, &node));
TestHR(node->SetUnknown(MF_TOPONODE_SOURCE, source));
TestHR(node->SetUnknown(MF_TOPONODE_PRESENTATION_DESCRIPTOR, sourcePD));
TestHR(node->SetUnknown(MF_TOPONODE_STREAM_DESCRIPTOR, sourceSD));
*outNode = node;
}
void DPMedia::CreateOutputNode(IMFStreamDescriptor* sourceSD, HWND whandle, IMFTopologyNode** outNode)
{
DWORD streamID = 0;
TestHR(sourceSD->GetStreamIdentifier(&streamID));
IMFMediaTypeHandler* handler = 0;
TestHR(sourceSD->GetMediaTypeHandler(&handler));
GUID guidMajorType = GUID_NULL;
TestHR(handler->GetMajorType(&guidMajorType));
IMFTopologyNode* node = 0;
TestHR(MFCreateTopologyNode(MF_TOPOLOGY_OUTPUT_NODE, &node));
IMFActivate* activateRenderer = 0;
if (guidMajorType == MFMediaType_Audio) {
TestHR(MFCreateAudioRendererActivate(&activateRenderer));
}
else if (guidMajorType == MFMediaType_Video) {
TestHR(MFCreateVideoRendererActivate(whandle, &activateRenderer));
}
else {
DPThrow("Unsupported media type");
}
TestHR(node->SetObject(activateRenderer));
activateRenderer->Release();
handler->Release();
*outNode = node;
}
DPMediaAsyncCallback* DPMediaAsyncCallback::createNew(DPMedia* arg)
{
DPMediaAsyncCallback* async = new DPMediaAsyncCallback(arg);
return async;
}
HRESULT DPMediaAsyncCallback::QueryInterface(REFIID riid, void** ppv)
{
static const QITAB qit[] =
{
QITABENT(DPMediaAsyncCallback, IMFAsyncCallback),
{ 0 },
};
return QISearch(this, qit, riid, ppv);
}
ULONG DPMediaAsyncCallback::AddRef()
{
return InterlockedIncrement(&refCount);
}
ULONG DPMediaAsyncCallback::Release()
{
ULONG newCount = InterlockedDecrement(&refCount);
if (newCount == 0) {
delete this;
}
return newCount;
}
//Invoke() is called asynchronously.
HRESULT DPMediaAsyncCallback::Invoke(IMFAsyncResult* result)
{
IMFMediaEvent* mediaEvent = 0;
HRESULT hr = media->mediaSession->EndGetEvent(result, &mediaEvent);
if (SUCCEEDED(hr)) {
MediaEventType eventType;
hr = mediaEvent->GetType(&eventType);
if (SUCCEEDED(hr)) {
if (eventType != MESessionClosed) {
media->mediaSession->BeginGetEvent(this, 0);
}
if (media->window) { //Not fully thread safe.
mediaEvent->AddRef(); //Released in the window proc.
PostMessage(media->window->getHandle(), WM_DP_MEDIAEVENT,
reinterpret_cast<WPARAM>(mediaEvent), (LPARAM)eventType);
}
}
mediaEvent->Release();
}
return S_OK;
}
DPMediaAsyncCallback::~DPMediaAsyncCallback()
{
}
void ExportDPMedia()
{
using namespace boost::python;
class_<DPMedia, boost::noncopyable>("Media", init<python::object&, DPWindow*>())
.def("play", &DPMedia::play)
.def("pause", &DPMedia::pause)
.def("getDuration", &DPMedia::getDuration)
.def("getPosition", &DPMedia::getPosition)
.def("setPosition", &DPMedia::setPosition)
.def("getEvents", &DPMedia::getEvents)
.def("hasVideo", &DPMedia::hasVideo)
.def("hasAudio", &DPMedia::hasAudio)
.def("release", &DPMedia::release)
.def("getVideoSize", &DPMedia::getVideoSize)
.def("getVolume", &DPMedia::getVolume)
.def("setVolume", &DPMedia::setVolume)
.def("getTexture", &DPMedia::getTexture, return_value_policy<manage_new_object>())
;
}