Skip to content

Commit 783fd14

Browse files
committed
Fix for min/max inversion in mastering luminance
Now pushing metadata once per second to madVR resolving issues with it not being picked up Verified to work with madVR beta 131
1 parent 45beec5 commit 783fd14

8 files changed

+94
-79
lines changed

CHANGELOG.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
0.1.0
2+
- Fix for min/max inversion in mastering luminance
3+
- Now pushing metadata once per second to madVR resolving issues with it not being picked up
4+
- Verified to work with madVR beta 131
5+
6+
0.0.2
7+
- Added config file to set parameters
8+
- Added HDFury Vertex2 auto config file param generator
9+
10+
0.0.1
11+
- First release

README.md

Lines changed: 35 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,41 @@
11
# [DirectShow Metadata Injector Filter](https://github.com/defl/directshow_metadata_injector_filter)
22

3-
This is a DirectShow filter that can inject metadata related to high dynamic range (HDR) and wide color gamut (WCG)
4-
into a directshow graph.
3+
This is a DirectShow filter that can inject metadata related to high dynamic range (HDR) and wide color gamut (WCG) the graph so it can be used by capable renderers like [madVR](http://madvr.com/).
4+
5+
It is usable for sources which do not properly relay this information like consumer video capture cards such as the [AVerMedia Live Gamer 4K - GC573](https://www.avermedia.com/us/product-detail/GC573) who technically do HDR data but refuse to forward the correct HDR metadata through their DirectShow source filters. This leads to downstream renderers making a mess of the picture as they don't know how to interpret it.
56

6-
This is usable for sources which do not properly relay this information like consumer video capture cards
7-
[AVerMedia Live Gamer 4K - GC573](https://www.avermedia.com/us/product-detail/GC573) who technically do HDR
8-
data but refuse to forward the correct HDR metdata through their DirectShow source filters.
9-
This leads to downstream renderers making a mess of the picture as they don't know how to interpret it.
107
Example [before](images/without.png) and [after](images/with.png) screenshots probably speak for themselves.
118

12-
## Features
9+
# Features
1310

1411
* Supported metadata
15-
* HDR primaries & whitepoint as xy coordinates
12+
* HDR primaries & whitepoint as CIE1931 xy coordinates
1613
* HDR min/max mastering luminance
1714
* HDR content light levels (MaxCLL, MaxFALL)
1815
* HDR Transfer Matrix (BT.2020 etc)
1916
* HDR Primaries (DCI-P3, BT.2020 etc)
2017
* HDR Video transfer function (SMPTE ST 2084 (PQ), HLG etc)
21-
* Configuration through an ini file (default in same directory)
18+
* Configuration through an .ini file (by default in same directory)
2219
* Tested players
2320
* Potplayer
2421
* Tested DirectShow sources
2522
* AVerMedia Live Gamer 4K - GC573
26-
* Blackmagic Design DeckLink Mini Recorder 4K
2723
* Tested DirectShow renderers
2824
* madVR
25+
26+
2927

30-
## Install
28+
# Install
3129

3230
* Install VS2019 x64 runtime
31+
3332
* Download directshow_metadata_injector_filter.ax to a location you will not delete (and which is not admin only under windows like program files)
33+
3434
* Copy the example_config.ini as directshow_metadata_injector_filter.ini to the same dir
3535

36-
Potplayer
36+
37+
38+
**Potplayer**
3739

3840
* Open potplayer
3941
* Open preferences (F5)
@@ -45,47 +47,45 @@ Potplayer
4547
* Bottom right of screen set priority to "Prefer"
4648
*(If you're running a AVerMedia Live Gamer 4K - GC573 don't forget to set it's output to P010 for 10bit, you can do this in preferences under device->webcam->format, see [here](images/potplayer_avermedia_settings.png))*
4749

48-
optional: hdfury_virtex2_ini_generator.py
4950

50-
* Install Python
51-
* Install python/requirements.txt modules
52-
* Run hdfury_virtex2_ini_generator.py <ip of vertex2> <directshow_metadata_injector_filter.ini file from above>
5351

54-
## Poor mans' madVRlabs Envy?
52+
# Configuration
5553

56-
Schematically this looks like: source (Apple TV4k or blueray player) -> HDFurty Vertex2 -> HTPC (madVR, nvidia GPU, capture card) -> display
54+
Configuration is via a .ini file which contains all the settings. There is an example_config.ini in the project's code repo. Copy that to the same location as where you downloaded the .ax file and rename to directshow_metadata_injector_filter.ini. The settings are well documented and speak for themselves.
5755

58-
Notes:
59-
* The sound you want to run off of the Vertex2 split to a processor/receiver if you plan on running any sort of interesting format like Atmos. Beware that delay might be high (>200ms) so get one that can handle it.
60-
* Note that in some jurisdictions it's allowed to remove HDCP for the right reasons. This is not hard, but figuring out how to do this is left as an exercise to the reader.
61-
* The hdfury_virtex2_ini_generator.py connects to the fury and updates the filter's config ever 10 seconds. A stop-start of the stream in potplayer will engage the new settings.
6256

63-
If you like this, and have the means, please buy a real [envy](https://madvrenvy.com/) to support further madVR development. It is really, really a much better experience.
6457

65-
## Configuration
58+
# HDFury Virtex2 auto config generator
6659

67-
Configuration is via a .ini file which contains all the settings. There is an example_config.ini. Copy that to the same location as where you downloaded the .ax file and rename to directshow_metadata_injector_filter.ini.
60+
If you have a HDFury Virtex2 you can automate the metadata generation. For this to work you need
61+
to place the device before the input to the capture server (you want to do this anyway because higher end audio is notoriously poor though such setups). This way the device will see the same HDR meta data as the capture card and the filter can inject the correct metadata in the DirectShow stream.
6862

69-
There is a small Python program included in the python/ subdir which can automatically generate this file for a range of devices. For this to work you need
70-
to place the device before the input to the capture server (you want to do this anyway because higher end audio is notriously poor though such setups). This
71-
way the device will see the same HDR meta data as the capture card and the filter can inject the correct metadata in the directshow stream. The config is
72-
re-generated every few seconds and a quick stop-start will reload everything and get you the right parameters (after a menu-switch or a new movie starts for
73-
example).
63+
The config is re-generated every few seconds and a quick stop-start will reload everything and get you the right parameters (after a menu-switch or a new movie starts for example).
64+
65+
Installation and use:
66+
67+
* Install Python
68+
* Install python/requirements.txt modules
69+
* Run hdfury_virtex2_ini_generator.py <ip of vertex2> <directshow_metadata_injector_filter.ini file from above>
7470

75-
Supported devices:
7671

77-
* HDFury Vertex2
7872

7973

80-
## Build
74+
# Build
8175

8276
**C++**: Open with Visual Studio 2019 VC++, should just work out of the box.
8377

8478
**Python**: Just have a python 3.x interpretor and install the packages mentioned in requirements.txt
8579

86-
## FAQ
80+
# FAQ
81+
82+
**Is this plug and play?**
83+
No, because this filter does not offer anything new to get an video stream decoded it will not automatically be added to a DirectShow graph. It needs to be forced to be included, see install for an example.
84+
85+
**After starting the video the screen remains black**
86+
This happens if there is another filter in between this filter and madVR. At that point the filter can't get memory for sending the metadata and will not copy the image data as well. Fix your graph/player.
87+
8788

88-
Is this plug and play? No, because this filter does not offer anything new to get an video stream decoded it will not automatically be added to a DirectShow graph. It needs to be forced to be included, see install for an example.
8989

9090
## Credits
9191

cpp/directshow_metadata_injector_filter/directshow_metadata_injector_filter.vcxproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@
175175
</ProjectReference>
176176
</ItemGroup>
177177
<ItemGroup>
178+
<None Include="..\..\example_config.ini" />
178179
<None Include="directshow_metadata_injector_filter.def" />
179180
</ItemGroup>
180181
<ItemGroup>

cpp/directshow_metadata_injector_filter/directshow_metadata_injector_filter.vcxproj.filters

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,5 +60,8 @@
6060
<None Include="directshow_metadata_injector_filter.def">
6161
<Filter>Source Files</Filter>
6262
</None>
63+
<None Include="..\..\example_config.ini">
64+
<Filter>Resource Files</Filter>
65+
</None>
6366
</ItemGroup>
6467
</Project>

cpp/directshow_metadata_injector_filter/guids.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
// {C3BD8F60-565F-404D-9B8F-D7E38BFA9B5E}
77
DEFINE_GUID(
8-
CLSID_METADATA_INJECTOR_FILTER,
8+
CLSID_METADATA_INJECTOR_FILTER,
99
0xc3bd8f60, 0x565f, 0x404d, 0x9b, 0x8f, 0xd7, 0xe3, 0x8b, 0xfa, 0x9b, 0x5e);
1010

1111

cpp/directshow_metadata_injector_filter/metadata_injector_filter.cpp

Lines changed: 35 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ CFactoryTemplate g_Templates[] = {
6565
&CLSID_METADATA_INJECTOR_FILTER,
6666
MetadataInjectorFilter::CreateInstance,
6767
NULL,
68-
&sMIPSetup
68+
&sMIPSetup
6969
},
7070
{
7171
L"Metadata Injector Properties",
@@ -120,43 +120,38 @@ HRESULT MetadataInjectorFilter::Transform(IMediaSample* pIn, IMediaSample* pOut)
120120
if (FAILED(hr))
121121
return hr;
122122

123-
if(mSendSideData)
123+
// Update once per second
124+
if ((mFrameCounter % mFramesPerSecond) == 0)
124125
{
126+
// This can fail if you have a filter behind this which does not understand side data (ie, it's not going directly to madVR)
125127
IMediaSideData* pMediaSideData = nullptr;
126128
if (FAILED(hr = pOut->QueryInterface(&pMediaSideData)))
127129
return hr;
128-
129-
{
130-
MediaSideDataHDR hdr;
131-
ZeroMemory(&hdr, sizeof(hdr));
132-
133-
hdr.display_primaries_x[0] = mDisplayPrimaryGreenX;
134-
hdr.display_primaries_x[1] = mDisplayPrimaryBlueX;
135-
hdr.display_primaries_x[2] = mDisplayPrimaryRedX;
136-
hdr.display_primaries_y[0] = mDisplayPrimaryGreenY;
137-
hdr.display_primaries_y[1] = mDisplayPrimaryBlueY;
138-
hdr.display_primaries_y[2] = mDisplayPrimaryRedY;
139-
140-
hdr.white_point_x = mWhitePointX;
141-
hdr.white_point_y = mWhitePointY;
142-
143-
hdr.max_display_mastering_luminance = mMasteringLuminanceMax;
144-
hdr.min_display_mastering_luminance = mMasteringLuminanceMin;
145-
pMediaSideData->SetSideData(IID_MediaSideDataHDR, (const BYTE*)&hdr, sizeof(hdr));
146-
}
147-
148-
{
149-
MediaSideDataHDRContentLightLevel hdrLightLevel;
150-
hdrLightLevel.MaxCLL = mMaxCLL;
151-
hdrLightLevel.MaxFALL = mMaxFALL;
152-
153-
pMediaSideData->SetSideData(IID_MediaSideDataHDRContentLightLevel, (const BYTE*)&hdrLightLevel, sizeof(hdrLightLevel));
154-
}
130+
131+
MediaSideDataHDR hdr;
132+
ZeroMemory(&hdr, sizeof(hdr));
133+
hdr.display_primaries_x[0] = mDisplayPrimaryGreenX;
134+
hdr.display_primaries_x[1] = mDisplayPrimaryBlueX;
135+
hdr.display_primaries_x[2] = mDisplayPrimaryRedX;
136+
hdr.display_primaries_y[0] = mDisplayPrimaryGreenY;
137+
hdr.display_primaries_y[1] = mDisplayPrimaryBlueY;
138+
hdr.display_primaries_y[2] = mDisplayPrimaryRedY;
139+
hdr.white_point_x = mWhitePointX;
140+
hdr.white_point_y = mWhitePointY;
141+
hdr.max_display_mastering_luminance = mMasteringLuminanceMax;
142+
hdr.min_display_mastering_luminance = mMasteringLuminanceMin;
143+
pMediaSideData->SetSideData(IID_MediaSideDataHDR, (const BYTE*)&hdr, sizeof(hdr));
144+
145+
MediaSideDataHDRContentLightLevel hdrLightLevel;
146+
ZeroMemory(&hdrLightLevel, sizeof(hdrLightLevel));
147+
hdrLightLevel.MaxCLL = mMaxCLL;
148+
hdrLightLevel.MaxFALL = mMaxFALL;
149+
pMediaSideData->SetSideData(IID_MediaSideDataHDRContentLightLevel, (const BYTE*)&hdrLightLevel, sizeof(hdrLightLevel));
155150

156151
pMediaSideData->Release();
157-
mSendSideData = false;
158152
}
159153

154+
++mFrameCounter;
160155
return NOERROR;
161156
}
162157

@@ -348,7 +343,7 @@ HRESULT MetadataInjectorFilter::CheckInputType(const CMediaType* mtIn)
348343
HRESULT MetadataInjectorFilter::CheckTransform(const CMediaType* mtIn, const CMediaType* mtOut)
349344
{
350345
CheckPointer(mtIn, E_POINTER);
351-
CheckPointer(mtOut, E_POINTER);
346+
CheckPointer(mtOut, E_POINTER);
352347

353348
return NOERROR;
354349
}
@@ -379,7 +374,7 @@ HRESULT MetadataInjectorFilter::DecideBufferSize(IMemAllocator* pAlloc, ALLOCATO
379374

380375
ASSERT(Actual.cBuffers == 1);
381376

382-
if (pProperties->cBuffers > Actual.cBuffers ||
377+
if (pProperties->cBuffers > Actual.cBuffers ||
383378
pProperties->cbBuffer > Actual.cbBuffer)
384379
return E_FAIL;
385380

@@ -400,6 +395,9 @@ HRESULT MetadataInjectorFilter::GetMediaType(int iPosition, CMediaType* pMediaTy
400395

401396
BuildFakeMediaType(pMediaType);
402397

398+
const VIDEOINFOHEADER* const pvi = (VIDEOINFOHEADER*)m_pInput->CurrentMediaType().Format();
399+
mFramesPerSecond = 1.0 / (pvi->AvgTimePerFrame / 10000000.0); // time unit is 100ns
400+
403401
return NOERROR;
404402
}
405403

@@ -444,7 +442,7 @@ STDMETHODIMP MetadataInjectorFilter::GetClassID(CLSID* pClsid)
444442
HRESULT MetadataInjectorFilter::ScribbleToStream(IStream* pStream)
445443
{
446444
HRESULT hr = pStream->Write(mConfigFilename.c_str(), mConfigFilename.length() + 1, NULL);
447-
if (FAILED(hr))
445+
if (FAILED(hr))
448446
return hr;
449447

450448
return NOERROR;
@@ -495,10 +493,10 @@ void MetadataInjectorFilter::BuildFakeMediaType(CMediaType* pMediaType)
495493
pvi2->dwPictAspectRatioX = mAspectRatioX;
496494
pvi2->dwPictAspectRatioY = mAspectRatioY;
497495

498-
// dwControlFlags is a 32bit int. With AMCONTROL_COLORINFO_PRESENT the upper 24 bits are used by DXVA_ExtendedFormat.
499-
// That struct is 32 bits so it's lower member (SampleFormat) is actually overbooked with the value of dwConotrolFlags
500-
// so can't be used. LAV has defined some out-of-spec but compatile with madVR values for the more modern formats,
501-
// which we use as well see
496+
// dwControlFlags is a 32bit int. With AMCONTROL_COLORINFO_PRESENT the upper 24 bits are used by DXVA_ExtendedFormat.
497+
// That struct is 32 bits so it's lower member (SampleFormat) is actually overbooked with the value of dwConotrolFlags
498+
// so can't be used. LAV has defined some out-of-spec but compatile with madVR values for the more modern formats,
499+
// which we use as well see
502500
// https://github.com/Nevcairiel/LAVFilters/blob/ddef56ae155d436f4301346408f4fdba755197d6/decoder/LAVVideo/Media.cpp
503501

504502
DXVA_ExtendedFormat* colorimetry = (DXVA_ExtendedFormat*)&(pvi2->dwControlFlags);

cpp/directshow_metadata_injector_filter/metadata_injector_filter.h

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,21 +61,22 @@ class MetadataInjectorFilter:
6161

6262
std::string mConfigFilename;
6363

64-
bool mSendSideData = true;
65-
64+
unsigned int mFramesPerSecond = 0;
65+
unsigned int mFrameCounter = 0;
66+
6667
double mDisplayPrimaryGreenX;
6768
double mDisplayPrimaryBlueX;
6869
double mDisplayPrimaryRedX;
6970
double mDisplayPrimaryGreenY;
7071
double mDisplayPrimaryBlueY;
7172
double mDisplayPrimaryRedY;
72-
73+
7374
double mWhitePointX;
7475
double mWhitePointY;
75-
76+
7677
double mMasteringLuminanceMin;
7778
double mMasteringLuminanceMax;
78-
79+
7980
unsigned int mMaxCLL;
8081
unsigned int mMaxFALL;
8182

cpp/directshow_metadata_injector_filter/metadata_injector_properties.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55

66
#include "metadata_injector_interface.h"
77

8-
class MetadataInjectorProperties:
8+
9+
class MetadataInjectorProperties:
910
public CBasePropertyPage
1011
{
1112

0 commit comments

Comments
 (0)