diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9f82add --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.vs/ +*.vcxproj.user +x64/ +/packages/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..c48091c --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +# Softcam + +This is a library to make a virtual webcam on Windows. + + +## How it works + +This software is a DLL which implements a DirectShow filter of Video Input Device category. +Once it is installed to the system, any camera application which is using DirectShow API will recognize the filter as a camera-like device attached to the system. +And thankfully many of camera applications running on Windows use DirectShow API to access camera devices. + +This DLL provides Softcam Sender API too. Using this API, your application can create a virtual webcam instance and send arbitrary images as a video stream to the camera application. + +Your code will look like this: + +``` +auto camera = scCreateCamera(320, 240, 60); +for (;;) +{ + uint8_t image[320 * 240 * 3]; + DrawSomething(image); + scSendFrame(camera, image); +} +``` +. + +## How to build and run + +Following software is required. + +- Visual Studio 2019 (installed with Desktop development with C++) + +The procedure is as follows. + +1. Open `softcam.sln`, choose configuration `Release` and build the solution. If it successfully builds, you will get `softcam.dll` in `x64/Release` directory. +2. Open `examples/softcam_installer/softcam_installer.sln`, choose configuration `Release` and build the solution. Then you will get `softcam_installer.exe` in `examples/softcam_installer/x64/Release` directory. +3. Then you register `softcam.dll` to your system by double clicking the `RegisterSoftcam.bat` in the `examples/softcam_installer` directory. Note since this process requires Windows administrator permissions, `softcam_installer.exe` will request the permissions to the user through UAC. You can also unregister the DLL using `UnregisterSoftcam.bat` with the same manner. +4. Open `examples/sender/sender.sln`, choose configuration `Release` and build the solution. +5. Run `sender.exe`. If it worked, you will see the message '`Ok. Softcam is now active.`' in the console. This means that you got a new virtual webcam running on the system. +6. To see the video stream which is being sent from the `sender.exe`, you may try my web application Compare ([https://tshino.github.io/compare/compare.html](https://tshino.github.io/compare/compare.html)) on your browser. This is an image comparison tool which is written in JavaScript, and it can take a capture from a webcam. Open the above link and click the camera icon which appeares on the side bar. If it shows a different camera image, you need to change the settings of your browser to select '`DirectShow Softcam`' as main camera device. In case of Chrome or Edge, the camera icon on the address bar is a shortcut to the camera settings. + + +## License + +[MIT](LICENSE). + +Note that the Base Classes libary (in [src/baseclasses](src/baseclasses) directory) is a part of [Microsft Windows Samples](https://github.com/microsoft/Windows-classic-samples) (Base Classes is in [Samples/Win7Samples/multimedia/directshow/baseclasses](https://github.com/microsoft/Windows-classic-samples/tree/master/Samples/Win7Samples/multimedia/directshow/baseclasses) directory) which is also provided under MIT license. diff --git a/examples/sender/sender.cpp b/examples/sender/sender.cpp new file mode 100644 index 0000000..14e6fb2 --- /dev/null +++ b/examples/sender/sender.cpp @@ -0,0 +1,166 @@ +#include +#include +#include +#include +#include + + +const int WIDTH = 320; +const int HEIGHT = 240; + + +/// A Rendering Example of Bouncing Balls +class BouncingBalls +{ + public: + static constexpr int NUM_BALLS = 2; + static constexpr float RADIUS = 30.0f; + static constexpr float GRAVITY = 400.0f; + static constexpr float STIFFNESS = 600.0f; + + struct Ball + { + float x = RADIUS, y = RADIUS * 2.0f; + float vx = 100.0f, vy = 0.0f; + float ax = 0.0f, ay = 0.0f; + float rx = RADIUS, ry = RADIUS; + + void move(float delta) + { + vx += delta * ax; + vy += delta * ay; + x += delta * vx; + y += delta * vy; + float cx = std::min(std::max(x, RADIUS), (float)WIDTH - RADIUS); + float cy = std::min(y, (float)HEIGHT - RADIUS); + ax = (cx - x) * STIFFNESS; + ay = (cy - y) * STIFFNESS + GRAVITY; + rx = std::max(RADIUS - std::abs(cx - x), RADIUS * 0.1f); + ry = std::max(RADIUS - std::abs(cy - y), RADIUS * 0.1f); + } + void collide(Ball& other) + { + float dx = x - other.x; + float dy = y - other.y; + float d = std::sqrt(dx * dx + dy * dy); + if (d <= RADIUS * 2.0f) + { + float p = RADIUS * 2.0f - d; + float nx = dx / std::max(RADIUS, d); + float ny = dy / std::max(RADIUS, d); + ax += p * STIFFNESS * nx; + ay += p * STIFFNESS * ny; + other.ax -= p * STIFFNESS * nx; + other.ay -= p * STIFFNESS * ny; + } + } + bool hit(int sx, int sy) const + { + float dx = (float(sx) + 0.5f - x) / rx; + float dy = (float(sy) + 0.5f - y) / ry; + return 1.0f >= dx * dx + dy * dy; + } + }; + + Ball balls[NUM_BALLS]; + + BouncingBalls() + { + for (int i = 0; i < NUM_BALLS; i++) + { + balls[i].x = (float)(WIDTH * (i + 1) / (2 * NUM_BALLS)); + balls[i].y = (float)(HEIGHT * i / (2 * NUM_BALLS)) + RADIUS * 2.0f; + balls[i].vx = RADIUS * 2.0f * (float)(i + 1); + } + } + void move(float delta) + { + for (int i = 0; i < NUM_BALLS; i++) + { + balls[i].move(delta); + } + for (int i = 0; i < NUM_BALLS; i++) + { + for (int j = i + 1; j < NUM_BALLS; j++) + { + balls[i].collide(balls[j]); + } + } + } + void draw(unsigned char *image) + { + for (int y = 0; y < HEIGHT; y++) + { + for (int x = 0; x < WIDTH; x++) + { + if (std::any_of( + balls, balls + NUM_BALLS, + [x,y](Ball& ball) { return ball.hit(x, y); })) + { + int red = x * 256 / WIDTH; + int green = 255; + int blue = y * 256 / HEIGHT; + + image[0] = (unsigned char)blue; + image[1] = (unsigned char)green; + image[2] = (unsigned char)red; + } + else + { + image[0] = 0; + image[1] = 0; + image[2] = 0; + } + image += 3; + } + } + } +}; + + +int main() +{ + // First, create a virtual camera instance with scCreateCamera(). + // A virtual camera is a source of a video image stream. + // The dimension width and height can be arbitrary positive numbers + // which are multiples of four. + // The third argument framerate is used to make sending frames at regular intervals. + // This framerate argument can be omitted, and the default framerate is 60. + // If you want to send every frame immediately without the frame rate regulator, + // specify 0 to the framerate argument, then it will be a variable frame rate. + scCamera cam = scCreateCamera(WIDTH, HEIGHT, 60); + if (!cam) + { + std::printf("failed to create camera\n"); + return 1; + } + std::printf("Ok. Softcam is now active.\n"); + + // Here, we wait for an application to connect to this camera. + // You can comment out this line to start sending frames immediately + // no matter there is a receiver or not. + scWaitForConnection(cam); + + // Our canvas is a simple array of RGB pixels. + // Note that the color component order is BGR, not RGB. + // This is due to the convention of DirectShow. + std::vector image(WIDTH * HEIGHT * 3); + + // This is an example class for drawing something to the canvas. + BouncingBalls balls; + + for (int i = 0; i < 100000; i++) + { + // Draw bouncing balls. + balls.move(1.0f / 60.0f); + balls.draw(image.data()); + + // Send the image as a newly captured frame of the camera. + scSendFrame(cam, image.data()); + } + + // Delete the camera instance. + // The receiver application will no longer receive new frames from this camera. + scDeleteCamera(cam); + std::printf("Softcam has been shut down.\n"); +} diff --git a/examples/sender/sender.sln b/examples/sender/sender.sln new file mode 100644 index 0000000..2226dfa --- /dev/null +++ b/examples/sender/sender.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30523.141 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sender", "sender.vcxproj", "{07DDA575-7366-4ED4-A5C0-CA1D9C2FBD94}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {07DDA575-7366-4ED4-A5C0-CA1D9C2FBD94}.Debug|x64.ActiveCfg = Debug|x64 + {07DDA575-7366-4ED4-A5C0-CA1D9C2FBD94}.Debug|x64.Build.0 = Debug|x64 + {07DDA575-7366-4ED4-A5C0-CA1D9C2FBD94}.Release|x64.ActiveCfg = Release|x64 + {07DDA575-7366-4ED4-A5C0-CA1D9C2FBD94}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {5DEC6E41-1C14-49D2-BA60-6E49F81B95D2} + EndGlobalSection +EndGlobal diff --git a/examples/sender/sender.vcxproj b/examples/sender/sender.vcxproj new file mode 100644 index 0000000..6bc7192 --- /dev/null +++ b/examples/sender/sender.vcxproj @@ -0,0 +1,98 @@ + + + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {07dda575-7366-4ed4-a5c0-ca1d9c2fbd94} + sender + 10.0 + + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + + + + + + + + + + + + + + + true + + + false + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + $(ProjectDir)..\..\src + + + Console + true + $(ProjectDir)..\..\x64\$(Configuration);%(AdditionalLibraryDirectories) + softcam.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + copy $(ProjectDir)..\..\x64\$(Configuration)\softcam.dll $(OutDir) + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + $(ProjectDir)..\..\src + + + Console + true + true + true + $(ProjectDir)..\..\x64\$(Configuration);%(AdditionalLibraryDirectories) + softcam.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + copy $(ProjectDir)..\..\x64\$(Configuration)\softcam.dll $(OutDir) + + + + + + + + + \ No newline at end of file diff --git a/examples/sender/sender.vcxproj.filters b/examples/sender/sender.vcxproj.filters new file mode 100644 index 0000000..6e0826e --- /dev/null +++ b/examples/sender/sender.vcxproj.filters @@ -0,0 +1,18 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + + + Source Files + + + \ No newline at end of file diff --git a/examples/softcam_installer/RegisterSoftcam.bat b/examples/softcam_installer/RegisterSoftcam.bat new file mode 100644 index 0000000..6b35f0f --- /dev/null +++ b/examples/softcam_installer/RegisterSoftcam.bat @@ -0,0 +1,23 @@ +@echo off + +set INSTALLER=x64\Release\softcam_installer.exe +set TARGET=..\..\x64\Release\softcam.dll + +echo ############################################################## +echo Softcam Installer (softcam_installer.exe) will install Softcam +echo (softcam.dll) to your system. +echo ############################################################## +echo. + +%INSTALLER% register %TARGET% + +if %ERRORLEVEL% == 0 ( + echo. + echo Successfully done. + echo. +) else ( + echo. + echo The process has been canceled or failed. + echo. +) +pause diff --git a/examples/softcam_installer/UnregisterSoftcam.bat b/examples/softcam_installer/UnregisterSoftcam.bat new file mode 100644 index 0000000..c78263d --- /dev/null +++ b/examples/softcam_installer/UnregisterSoftcam.bat @@ -0,0 +1,23 @@ +@echo off + +set INSTALLER=x64\Release\softcam_installer.exe +set TARGET=..\..\x64\Release\softcam.dll + +echo ################################################################ +echo Softcam Installer (softcam_installer.exe) will uninstall Softcam +echo (softcam.dll) from your system. +echo ################################################################ +echo. + +%INSTALLER% unregister %TARGET% + +if %ERRORLEVEL% == 0 ( + echo. + echo Successfully done. + echo. +) else ( + echo. + echo The process has been canceled or failed. + echo. +) +pause diff --git a/examples/softcam_installer/softcam_installer.cpp b/examples/softcam_installer/softcam_installer.cpp new file mode 100644 index 0000000..da359af --- /dev/null +++ b/examples/softcam_installer/softcam_installer.cpp @@ -0,0 +1,80 @@ +#include +#include +#include +#include + + +void Message(const std::string& message) +{ + MessageBoxA(NULL, message.c_str(), "Softcam Installer", MB_OK); +} + + +HMODULE LoadDLL(const std::string& path) +{ + HMODULE hmod = LoadLibraryA(path.c_str()); + if (!hmod) + { + Message("Error: can't load DLL"); + std::exit(1); + } + return hmod; +} + + +template +Func* GetProc(HMODULE hmod, const std::string& name) +{ + Func* func = (Func*)GetProcAddress(hmod, name.c_str()); + if (!func) + { + Message("Error: can't find function " + name + " in DLL"); + std::exit(1); + } + return func; +} + + +int WINAPI WinMain( + HINSTANCE hInstance, + HINSTANCE hPrevInstance, + LPSTR lpszCmdLine, + int nCmdShow) +{ + if (__argc != 3) + { + Message( + "Usage:\n" + " softcam_installer.exe register \n" + " softcam_installer.exe unregister "); + return 0; + } + + std::string cmd = __argv[1], path = __argv[2]; + + if (cmd == "register") + { + auto hmod = LoadDLL(path); + auto RegisterServer = GetProc(hmod, "DllRegisterServer"); + + RegisterServer(); + + Message("softcam.dll has been successfully registered to the system"); + return 0; + } + else if (cmd == "unregister") + { + auto hmod = LoadDLL(path); + auto UnregisterServer = GetProc(hmod, "DllUnregisterServer"); + + UnregisterServer(); + + Message("softcam.dll has been successfully unregistered from the system"); + return 0; + } + else + { + Message("Error: invalid option"); + return 1; + } +} diff --git a/examples/softcam_installer/softcam_installer.sln b/examples/softcam_installer/softcam_installer.sln new file mode 100644 index 0000000..b21293e --- /dev/null +++ b/examples/softcam_installer/softcam_installer.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30523.141 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "softcam_installer", "softcam_installer.vcxproj", "{93A84B31-722E-4346-B455-79E915E21B7A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {93A84B31-722E-4346-B455-79E915E21B7A}.Debug|x64.ActiveCfg = Debug|x64 + {93A84B31-722E-4346-B455-79E915E21B7A}.Debug|x64.Build.0 = Debug|x64 + {93A84B31-722E-4346-B455-79E915E21B7A}.Release|x64.ActiveCfg = Release|x64 + {93A84B31-722E-4346-B455-79E915E21B7A}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {41622781-2BBF-4110-BAE9-81622A97CFBF} + EndGlobalSection +EndGlobal diff --git a/examples/softcam_installer/softcam_installer.vcxproj b/examples/softcam_installer/softcam_installer.vcxproj new file mode 100644 index 0000000..5285c66 --- /dev/null +++ b/examples/softcam_installer/softcam_installer.vcxproj @@ -0,0 +1,86 @@ + + + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {93a84b31-722e-4346-b455-79e915e21b7a} + softcam_installer + 10.0 + + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + + + + + + + + + + + + + + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + true + + + true + RequireAdministrator + Windows + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + true + + + true + true + true + RequireAdministrator + Windows + + + + + + + + + \ No newline at end of file diff --git a/examples/softcam_installer/softcam_installer.vcxproj.filters b/examples/softcam_installer/softcam_installer.vcxproj.filters new file mode 100644 index 0000000..10814d1 --- /dev/null +++ b/examples/softcam_installer/softcam_installer.vcxproj.filters @@ -0,0 +1,22 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + ソース ファイル + + + \ No newline at end of file diff --git a/softcam.sln b/softcam.sln new file mode 100644 index 0000000..b4d56d6 --- /dev/null +++ b/softcam.sln @@ -0,0 +1,55 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30523.141 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "softcam", "src\softcam\softcam.vcxproj", "{52968D29-AA5F-41A4-BF1E-4C50F1098373}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "BaseClasses", "src\baseclasses\BaseClasses.vcxproj", "{E8A3F6FA-AE1C-4C8E-A0B6-9C8480324EAA}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "dll_tests", "tests\dll_tests\dll_tests.vcxproj", "{BF2211BE-932A-4E5D-AA41-42304FB243FA}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "softcamcore", "src\softcamcore\softcamcore.vcxproj", "{DF9D5A2D-3BED-4D1A-8484-22A654C9AD76}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{188CE909-5FAB-49B2-9C9E-5F3405CF343A}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "core_tests", "tests\core_tests\core_tests.vcxproj", "{13B2EA6E-E43F-4B6A-9709-B25181CB8115}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {52968D29-AA5F-41A4-BF1E-4C50F1098373}.Debug|x64.ActiveCfg = Debug|x64 + {52968D29-AA5F-41A4-BF1E-4C50F1098373}.Debug|x64.Build.0 = Debug|x64 + {52968D29-AA5F-41A4-BF1E-4C50F1098373}.Release|x64.ActiveCfg = Release|x64 + {52968D29-AA5F-41A4-BF1E-4C50F1098373}.Release|x64.Build.0 = Release|x64 + {E8A3F6FA-AE1C-4C8E-A0B6-9C8480324EAA}.Debug|x64.ActiveCfg = Debug|x64 + {E8A3F6FA-AE1C-4C8E-A0B6-9C8480324EAA}.Debug|x64.Build.0 = Debug|x64 + {E8A3F6FA-AE1C-4C8E-A0B6-9C8480324EAA}.Release|x64.ActiveCfg = Release|x64 + {E8A3F6FA-AE1C-4C8E-A0B6-9C8480324EAA}.Release|x64.Build.0 = Release|x64 + {BF2211BE-932A-4E5D-AA41-42304FB243FA}.Debug|x64.ActiveCfg = Debug|x64 + {BF2211BE-932A-4E5D-AA41-42304FB243FA}.Debug|x64.Build.0 = Debug|x64 + {BF2211BE-932A-4E5D-AA41-42304FB243FA}.Release|x64.ActiveCfg = Release|x64 + {BF2211BE-932A-4E5D-AA41-42304FB243FA}.Release|x64.Build.0 = Release|x64 + {DF9D5A2D-3BED-4D1A-8484-22A654C9AD76}.Debug|x64.ActiveCfg = Debug|x64 + {DF9D5A2D-3BED-4D1A-8484-22A654C9AD76}.Debug|x64.Build.0 = Debug|x64 + {DF9D5A2D-3BED-4D1A-8484-22A654C9AD76}.Release|x64.ActiveCfg = Release|x64 + {DF9D5A2D-3BED-4D1A-8484-22A654C9AD76}.Release|x64.Build.0 = Release|x64 + {13B2EA6E-E43F-4B6A-9709-B25181CB8115}.Debug|x64.ActiveCfg = Debug|x64 + {13B2EA6E-E43F-4B6A-9709-B25181CB8115}.Debug|x64.Build.0 = Debug|x64 + {13B2EA6E-E43F-4B6A-9709-B25181CB8115}.Release|x64.ActiveCfg = Release|x64 + {13B2EA6E-E43F-4B6A-9709-B25181CB8115}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {BF2211BE-932A-4E5D-AA41-42304FB243FA} = {188CE909-5FAB-49B2-9C9E-5F3405CF343A} + {13B2EA6E-E43F-4B6A-9709-B25181CB8115} = {188CE909-5FAB-49B2-9C9E-5F3405CF343A} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {17187F81-AAB0-43FB-9364-23825A0BF643} + EndGlobalSection +EndGlobal diff --git a/src/baseclasses/BaseClasses.vcxproj b/src/baseclasses/BaseClasses.vcxproj new file mode 100644 index 0000000..2db669f --- /dev/null +++ b/src/baseclasses/BaseClasses.vcxproj @@ -0,0 +1,121 @@ + + + + + Debug + x64 + + + Release + x64 + + + + {E8A3F6FA-AE1C-4C8E-A0B6-9C8480324EAA} + BaseClasses + Win32Proj + + + + StaticLibrary + v142 + Unicode + + + StaticLibrary + v142 + Unicode + + + + + + + + + + + + + <_ProjectFileVersion>16.0.30028.132 + + + $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + + + $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + + + + X64 + + + .;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + + Level3 + ProgramDatabase + true + MultiThreadedDebugDLL + Disabled + true + + + strmiids.lib;%(AdditionalDependencies) + + + + + X64 + + + .;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + + Level3 + ProgramDatabase + true + true + true + + + strmiids.lib;%(AdditionalDependencies) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/baseclasses/BaseClasses.vcxproj.filters b/src/baseclasses/BaseClasses.vcxproj.filters new file mode 100644 index 0000000..5b0fdbc --- /dev/null +++ b/src/baseclasses/BaseClasses.vcxproj.filters @@ -0,0 +1,102 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/src/baseclasses/amfilter.cpp b/src/baseclasses/amfilter.cpp new file mode 100644 index 0000000..263f2cf --- /dev/null +++ b/src/baseclasses/amfilter.cpp @@ -0,0 +1,4713 @@ +//------------------------------------------------------------------------------ +// File: AMFilter.cpp +// +// Desc: DirectShow base classes - implements class hierarchy for streams +// architecture. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +//===================================================================== +//===================================================================== +// The following classes are declared in this header: +// +// +// CBaseFilter Support for IBaseFilter (incl. IMediaFilter) +// CEnumPins Enumerate input and output pins +// CEnumMediaTypes Enumerate the preferred pin formats +// CBasePin Abstract base class for IPin interface +// CBaseOutputPin Adds data provider member functions +// CMediaSample Basic transport unit for IMemInputPin +// CBaseAllocator General list guff for most allocators +// CMemAllocator Implements memory buffer allocation +// +//===================================================================== +//===================================================================== + +#include +#include + +#ifdef DXMPERF +#include "dxmperf.h" +#endif // DXMPERF + + +//===================================================================== +// Helpers +//===================================================================== +STDAPI CreateMemoryAllocator(__deref_out IMemAllocator **ppAllocator) +{ + return CoCreateInstance(CLSID_MemoryAllocator, + 0, + CLSCTX_INPROC_SERVER, + IID_IMemAllocator, + (void **)ppAllocator); +} + +// Put this one here rather than in ctlutil.cpp to avoid linking +// anything brought in by ctlutil.cpp +STDAPI CreatePosPassThru( + __in_opt LPUNKNOWN pAgg, + BOOL bRenderer, + IPin *pPin, + __deref_out IUnknown **ppPassThru +) +{ + *ppPassThru = NULL; + IUnknown *pUnkSeek; + HRESULT hr = CoCreateInstance(CLSID_SeekingPassThru, + pAgg, + CLSCTX_INPROC_SERVER, + IID_IUnknown, + (void **)&pUnkSeek + ); + if (FAILED(hr)) { + return hr; + } + + ISeekingPassThru *pPassThru; + hr = pUnkSeek->QueryInterface(IID_ISeekingPassThru, (void**)&pPassThru); + if (FAILED(hr)) { + pUnkSeek->Release(); + return hr; + } + hr = pPassThru->Init(bRenderer, pPin); + pPassThru->Release(); + if (FAILED(hr)) { + pUnkSeek->Release(); + return hr; + } + *ppPassThru = pUnkSeek; + return S_OK; +} + + + +#define CONNECT_TRACE_LEVEL 3 + + +//===================================================================== +//===================================================================== +// Implements CBaseFilter +//===================================================================== +//===================================================================== + + +/* Override this to say what interfaces we support and where */ + +STDMETHODIMP CBaseFilter::NonDelegatingQueryInterface(REFIID riid, + __deref_out void **ppv) +{ + /* Do we have this interface */ + + if (riid == IID_IBaseFilter) { + return GetInterface((IBaseFilter *) this, ppv); + } else if (riid == IID_IMediaFilter) { + return GetInterface((IMediaFilter *) this, ppv); + } else if (riid == IID_IPersist) { + return GetInterface((IPersist *) this, ppv); + } else if (riid == IID_IAMovieSetup) { + return GetInterface((IAMovieSetup *) this, ppv); + } else { + return CUnknown::NonDelegatingQueryInterface(riid, ppv); + } +} + +#ifdef DEBUG +STDMETHODIMP_(ULONG) CBaseFilter::NonDelegatingRelease() +{ + if (m_cRef == 1) { + KASSERT(m_pGraph == NULL); + } + return CUnknown::NonDelegatingRelease(); +} +#endif + + +/* Constructor */ + +CBaseFilter::CBaseFilter(__in_opt LPCTSTR pName, + __inout_opt LPUNKNOWN pUnk, + __in CCritSec *pLock, + REFCLSID clsid) : + CUnknown( pName, pUnk ), + m_pLock(pLock), + m_clsid(clsid), + m_State(State_Stopped), + m_pClock(NULL), + m_pGraph(NULL), + m_pSink(NULL), + m_pName(NULL), + m_PinVersion(1) +{ +#ifdef DXMPERF + PERFLOG_CTOR( pName ? pName : L"CBaseFilter", (IBaseFilter *) this ); +#endif // DXMPERF + + ASSERT(pLock != NULL); +} + +/* Passes in a redundant HRESULT argument */ + +CBaseFilter::CBaseFilter(__in_opt LPCTSTR pName, + __in_opt LPUNKNOWN pUnk, + __in CCritSec *pLock, + REFCLSID clsid, + __inout HRESULT *phr) : + CUnknown( pName, pUnk ), + m_pLock(pLock), + m_clsid(clsid), + m_State(State_Stopped), + m_pClock(NULL), + m_pGraph(NULL), + m_pSink(NULL), + m_pName(NULL), + m_PinVersion(1) +{ +#ifdef DXMPERF + PERFLOG_CTOR( pName ? pName : L"CBaseFilter", (IBaseFilter *) this ); +#endif // DXMPERF + + ASSERT(pLock != NULL); + UNREFERENCED_PARAMETER(phr); +} + +#ifdef UNICODE +CBaseFilter::CBaseFilter(__in_opt LPCSTR pName, + __in_opt LPUNKNOWN pUnk, + __in CCritSec *pLock, + REFCLSID clsid) : + CUnknown( pName, pUnk ), + m_pLock(pLock), + m_clsid(clsid), + m_State(State_Stopped), + m_pClock(NULL), + m_pGraph(NULL), + m_pSink(NULL), + m_pName(NULL), + m_PinVersion(1) +{ +#ifdef DXMPERF + PERFLOG_CTOR( L"CBaseFilter", (IBaseFilter *) this ); +#endif // DXMPERF + + ASSERT(pLock != NULL); +} +CBaseFilter::CBaseFilter(__in_opt LPCSTR pName, + __in_opt LPUNKNOWN pUnk, + __in CCritSec *pLock, + REFCLSID clsid, + __inout HRESULT *phr) : + CUnknown( pName, pUnk ), + m_pLock(pLock), + m_clsid(clsid), + m_State(State_Stopped), + m_pClock(NULL), + m_pGraph(NULL), + m_pSink(NULL), + m_pName(NULL), + m_PinVersion(1) +{ +#ifdef DXMPERF + PERFLOG_CTOR( L"CBaseFilter", (IBaseFilter *) this ); +#endif // DXMPERF + + ASSERT(pLock != NULL); + UNREFERENCED_PARAMETER(phr); +} +#endif + +/* Destructor */ + +CBaseFilter::~CBaseFilter() +{ +#ifdef DXMPERF + PERFLOG_DTOR( L"CBaseFilter", (IBaseFilter *) this ); +#endif // DXMPERF + + // NOTE we do NOT hold references on the filtergraph for m_pGraph or m_pSink + // When we did we had the circular reference problem. Nothing would go away. + + delete[] m_pName; + + // must be stopped, but can't call Stop here since + // our critsec has been destroyed. + + /* Release any clock we were using */ + if (m_pClock) { + m_pClock->Release(); + m_pClock = NULL; + } +} + +/* Return the filter's clsid */ +STDMETHODIMP +CBaseFilter::GetClassID(__out CLSID *pClsID) +{ + CheckPointer(pClsID,E_POINTER); + ValidateReadWritePtr(pClsID,sizeof(CLSID)); + *pClsID = m_clsid; + return NOERROR; +} + +/* Override this if your state changes are not done synchronously */ +STDMETHODIMP +CBaseFilter::GetState(DWORD dwMSecs, __out FILTER_STATE *State) +{ + UNREFERENCED_PARAMETER(dwMSecs); + CheckPointer(State,E_POINTER); + ValidateReadWritePtr(State,sizeof(FILTER_STATE)); + + *State = m_State; + return S_OK; +} + + +/* Set the clock we will use for synchronisation */ + +STDMETHODIMP +CBaseFilter::SetSyncSource(__in_opt IReferenceClock *pClock) +{ + CAutoLock cObjectLock(m_pLock); + + // Ensure the new one does not go away - even if the same as the old + if (pClock) { + pClock->AddRef(); + } + + // if we have a clock, release it + if (m_pClock) { + m_pClock->Release(); + } + + // Set the new reference clock (might be NULL) + // Should we query it to ensure it is a clock? Consider for a debug build. + m_pClock = pClock; + + return NOERROR; +} + +/* Return the clock we are using for synchronisation */ +STDMETHODIMP +CBaseFilter::GetSyncSource(__deref_out_opt IReferenceClock **pClock) +{ + CheckPointer(pClock,E_POINTER); + ValidateReadWritePtr(pClock,sizeof(IReferenceClock *)); + CAutoLock cObjectLock(m_pLock); + + if (m_pClock) { + // returning an interface... addref it... + m_pClock->AddRef(); + } + *pClock = (IReferenceClock*)m_pClock; + return NOERROR; +} + + + +// override CBaseMediaFilter Stop method, to deactivate any pins this +// filter has. +STDMETHODIMP +CBaseFilter::Stop() +{ + CAutoLock cObjectLock(m_pLock); + HRESULT hr = NOERROR; + + // notify all pins of the state change + if (m_State != State_Stopped) { + int cPins = GetPinCount(); + for (int c = 0; c < cPins; c++) { + + CBasePin *pPin = GetPin(c); + if (NULL == pPin) { + break; + } + + // Disconnected pins are not activated - this saves pins worrying + // about this state themselves. We ignore the return code to make + // sure everyone is inactivated regardless. The base input pin + // class can return an error if it has no allocator but Stop can + // be used to resync the graph state after something has gone bad + + if (pPin->IsConnected()) { + HRESULT hrTmp = pPin->Inactive(); + if (FAILED(hrTmp) && SUCCEEDED(hr)) { + hr = hrTmp; + } + } + } + } + +#ifdef DXMPERF + PERFLOG_STOP( m_pName ? m_pName : L"CBaseFilter", (IBaseFilter *) this, m_State ); +#endif // DXMPERF + + m_State = State_Stopped; + return hr; +} + + +// override CBaseMediaFilter Pause method to activate any pins +// this filter has (also called from Run) + +STDMETHODIMP +CBaseFilter::Pause() +{ + CAutoLock cObjectLock(m_pLock); + + // notify all pins of the change to active state + if (m_State == State_Stopped) { + int cPins = GetPinCount(); + for (int c = 0; c < cPins; c++) { + + CBasePin *pPin = GetPin(c); + if (NULL == pPin) { + break; + } + + // Disconnected pins are not activated - this saves pins + // worrying about this state themselves + + if (pPin->IsConnected()) { + HRESULT hr = pPin->Active(); + if (FAILED(hr)) { + return hr; + } + } + } + } + + +#ifdef DXMPERF + PERFLOG_PAUSE( m_pName ? m_pName : L"CBaseFilter", (IBaseFilter *) this, m_State ); +#endif // DXMPERF + + m_State = State_Paused; + return S_OK; +} + +// Put the filter into a running state. + +// The time parameter is the offset to be added to the samples' +// stream time to get the reference time at which they should be presented. +// +// you can either add these two and compare it against the reference clock, +// or you can call CBaseFilter::StreamTime and compare that against +// the sample timestamp. + +STDMETHODIMP +CBaseFilter::Run(REFERENCE_TIME tStart) +{ + CAutoLock cObjectLock(m_pLock); + + // remember the stream time offset + m_tStart = tStart; + + if (m_State == State_Stopped){ + HRESULT hr = Pause(); + + if (FAILED(hr)) { + return hr; + } + } + // notify all pins of the change to active state + if (m_State != State_Running) { + int cPins = GetPinCount(); + for (int c = 0; c < cPins; c++) { + + CBasePin *pPin = GetPin(c); + if (NULL == pPin) { + break; + } + + // Disconnected pins are not activated - this saves pins + // worrying about this state themselves + + if (pPin->IsConnected()) { + HRESULT hr = pPin->Run(tStart); + if (FAILED(hr)) { + return hr; + } + } + } + } + +#ifdef DXMPERF + PERFLOG_RUN( m_pName ? m_pName : L"CBaseFilter", (IBaseFilter *) this, tStart, m_State ); +#endif // DXMPERF + + m_State = State_Running; + return S_OK; +} + +// +// return the current stream time - samples with start timestamps of this +// time or before should be rendered by now +HRESULT +CBaseFilter::StreamTime(CRefTime& rtStream) +{ + // Caller must lock for synchronization + // We can't grab the filter lock because we want to be able to call + // this from worker threads without deadlocking + + if (m_pClock == NULL) { + return VFW_E_NO_CLOCK; + } + + // get the current reference time + HRESULT hr = m_pClock->GetTime((REFERENCE_TIME*)&rtStream); + if (FAILED(hr)) { + return hr; + } + + // subtract the stream offset to get stream time + rtStream -= m_tStart; + + return S_OK; +} + + +/* Create an enumerator for the pins attached to this filter */ + +STDMETHODIMP +CBaseFilter::EnumPins(__deref_out IEnumPins **ppEnum) +{ + CheckPointer(ppEnum,E_POINTER); + ValidateReadWritePtr(ppEnum,sizeof(IEnumPins *)); + + /* Create a new ref counted enumerator */ + + *ppEnum = new CEnumPins(this, + NULL); + + return *ppEnum == NULL ? E_OUTOFMEMORY : NOERROR; +} + + +// default behaviour of FindPin is to assume pins are named +// by their pin names +STDMETHODIMP +CBaseFilter::FindPin( + LPCWSTR Id, + __deref_out IPin ** ppPin +) +{ + CheckPointer(ppPin,E_POINTER); + ValidateReadWritePtr(ppPin,sizeof(IPin *)); + + // We're going to search the pin list so maintain integrity + CAutoLock lck(m_pLock); + int iCount = GetPinCount(); + for (int i = 0; i < iCount; i++) { + CBasePin *pPin = GetPin(i); + if (NULL == pPin) { + break; + } + + if (0 == lstrcmpW(pPin->Name(), Id)) { + // Found one that matches + // + // AddRef() and return it + *ppPin = pPin; + pPin->AddRef(); + return S_OK; + } + } + *ppPin = NULL; + return VFW_E_NOT_FOUND; +} + +/* Return information about this filter */ + +STDMETHODIMP +CBaseFilter::QueryFilterInfo(__out FILTER_INFO * pInfo) +{ + CheckPointer(pInfo,E_POINTER); + ValidateReadWritePtr(pInfo,sizeof(FILTER_INFO)); + + if (m_pName) { + (void)StringCchCopyW(pInfo->achName, NUMELMS(pInfo->achName), m_pName); + } else { + pInfo->achName[0] = L'\0'; + } + pInfo->pGraph = m_pGraph; + if (m_pGraph) + m_pGraph->AddRef(); + return NOERROR; +} + + +/* Provide the filter with a filter graph */ + +STDMETHODIMP +CBaseFilter::JoinFilterGraph( + __inout_opt IFilterGraph * pGraph, + __in_opt LPCWSTR pName) +{ + CAutoLock cObjectLock(m_pLock); + + // NOTE: we no longer hold references on the graph (m_pGraph, m_pSink) + + m_pGraph = pGraph; + if (m_pGraph) { + HRESULT hr = m_pGraph->QueryInterface(IID_IMediaEventSink, + (void**) &m_pSink); + if (FAILED(hr)) { + ASSERT(m_pSink == NULL); + } + else m_pSink->Release(); // we do NOT keep a reference on it. + } else { + // if graph pointer is null, then we should + // also release the IMediaEventSink on the same object - we don't + // refcount it, so just set it to null + m_pSink = NULL; + } + + + if (m_pName) { + delete[] m_pName; + m_pName = NULL; + } + + if (pName) { + size_t namelen; + HRESULT hr = StringCchLengthW(pName, STRSAFE_MAX_CCH, &namelen); + if (FAILED(hr)) { + return hr; + } + m_pName = new WCHAR[namelen + 1]; + if (m_pName) { + (void)StringCchCopyW(m_pName, namelen + 1, pName); + } else { + return E_OUTOFMEMORY; + } + } + +#ifdef DXMPERF + PERFLOG_JOINGRAPH( m_pName ? m_pName : L"CBaseFilter",(IBaseFilter *) this, pGraph ); +#endif // DXMPERF + + return NOERROR; +} + + +// return a Vendor information string. Optional - may return E_NOTIMPL. +// memory returned should be freed using CoTaskMemFree +// default implementation returns E_NOTIMPL +STDMETHODIMP +CBaseFilter::QueryVendorInfo( + __deref_out LPWSTR* pVendorInfo) +{ + UNREFERENCED_PARAMETER(pVendorInfo); + return E_NOTIMPL; +} + + +// send an event notification to the filter graph if we know about it. +// returns S_OK if delivered, S_FALSE if the filter graph does not sink +// events, or an error otherwise. +HRESULT +CBaseFilter::NotifyEvent( + long EventCode, + LONG_PTR EventParam1, + LONG_PTR EventParam2) +{ + // Snapshot so we don't have to lock up + IMediaEventSink *pSink = m_pSink; + if (pSink) { + if (EC_COMPLETE == EventCode) { + EventParam2 = (LONG_PTR)(IBaseFilter*)this; + } + + return pSink->Notify(EventCode, EventParam1, EventParam2); + } else { + return E_NOTIMPL; + } +} + +// Request reconnect +// pPin is the pin to reconnect +// pmt is the type to reconnect with - can be NULL +// Calls ReconnectEx on the filter graph +HRESULT +CBaseFilter::ReconnectPin( + IPin *pPin, + __in_opt AM_MEDIA_TYPE const *pmt +) +{ + IFilterGraph2 *pGraph2; + if (m_pGraph != NULL) { + HRESULT hr = m_pGraph->QueryInterface(IID_IFilterGraph2, (void **)&pGraph2); + if (SUCCEEDED(hr)) { + hr = pGraph2->ReconnectEx(pPin, pmt); + pGraph2->Release(); + return hr; + } else { + return m_pGraph->Reconnect(pPin); + } + } else { + return E_NOINTERFACE; + } +} + + + +/* This is the same idea as the media type version does for type enumeration + on pins but for the list of pins available. So if the list of pins you + provide changes dynamically then either override this virtual function + to provide the version number, or more simply call IncrementPinVersion */ + +LONG CBaseFilter::GetPinVersion() +{ + return m_PinVersion; +} + + +/* Increment the current pin version cookie */ + +void CBaseFilter::IncrementPinVersion() +{ + InterlockedIncrement(&m_PinVersion); +} + +/* register filter */ + +STDMETHODIMP CBaseFilter::Register() +{ + // get setup data, if it exists + // + LPAMOVIESETUP_FILTER psetupdata = GetSetupData(); + + // check we've got data + // + if( NULL == psetupdata ) return S_FALSE; + + // init is ref counted so call just in case + // we're being called cold. + // + HRESULT hr = CoInitialize( (LPVOID)NULL ); + ASSERT( SUCCEEDED(hr) ); + + // get hold of IFilterMapper + // + IFilterMapper *pIFM; + hr = CoCreateInstance( CLSID_FilterMapper + , NULL + , CLSCTX_INPROC_SERVER + , IID_IFilterMapper + , (void **)&pIFM ); + if( SUCCEEDED(hr) ) + { + hr = AMovieSetupRegisterFilter( psetupdata, pIFM, TRUE ); + pIFM->Release(); + } + + // and clear up + // + CoFreeUnusedLibraries(); + CoUninitialize(); + + return NOERROR; +} + + +/* unregister filter */ + +STDMETHODIMP CBaseFilter::Unregister() +{ + // get setup data, if it exists + // + LPAMOVIESETUP_FILTER psetupdata = GetSetupData(); + + // check we've got data + // + if( NULL == psetupdata ) return S_FALSE; + + // OLE init is ref counted so call + // just in case we're being called cold. + // + HRESULT hr = CoInitialize( (LPVOID)NULL ); + ASSERT( SUCCEEDED(hr) ); + + // get hold of IFilterMapper + // + IFilterMapper *pIFM; + hr = CoCreateInstance( CLSID_FilterMapper + , NULL + , CLSCTX_INPROC_SERVER + , IID_IFilterMapper + , (void **)&pIFM ); + if( SUCCEEDED(hr) ) + { + hr = AMovieSetupRegisterFilter( psetupdata, pIFM, FALSE ); + + // release interface + // + pIFM->Release(); + } + + // clear up + // + CoFreeUnusedLibraries(); + CoUninitialize(); + + // handle one acceptable "error" - that + // of filter not being registered! + // (couldn't find a suitable #define'd + // name for the error!) + // + if( 0x80070002 == hr) + return NOERROR; + else + return hr; +} + + +//===================================================================== +//===================================================================== +// Implements CEnumPins +//===================================================================== +//===================================================================== + + +CEnumPins::CEnumPins(__in CBaseFilter *pFilter, + __in_opt CEnumPins *pEnumPins) : + m_Position(0), + m_PinCount(0), + m_pFilter(pFilter), + m_cRef(1), // Already ref counted + m_PinCache(NAME("Pin Cache")) +{ + +#ifdef DEBUG + m_dwCookie = DbgRegisterObjectCreation("CEnumPins", 0); +#endif + + /* We must be owned by a filter derived from CBaseFilter */ + + ASSERT(pFilter != NULL); + + /* Hold a reference count on our filter */ + m_pFilter->AddRef(); + + /* Are we creating a new enumerator */ + + if (pEnumPins == NULL) { + m_Version = m_pFilter->GetPinVersion(); + m_PinCount = m_pFilter->GetPinCount(); + } else { + ASSERT(m_Position <= m_PinCount); + m_Position = pEnumPins->m_Position; + m_PinCount = pEnumPins->m_PinCount; + m_Version = pEnumPins->m_Version; + m_PinCache.AddTail(&(pEnumPins->m_PinCache)); + } +} + + +/* Destructor releases the reference count on our filter NOTE since we hold + a reference count on the filter who created us we know it is safe to + release it, no access can be made to it afterwards though as we have just + caused the last reference count to go and the object to be deleted */ + +CEnumPins::~CEnumPins() +{ + m_pFilter->Release(); + +#ifdef DEBUG + DbgRegisterObjectDestruction(m_dwCookie); +#endif +} + + +/* Override this to say what interfaces we support where */ + +STDMETHODIMP +CEnumPins::QueryInterface(REFIID riid, __deref_out void **ppv) +{ + CheckPointer(ppv, E_POINTER); + + /* Do we have this interface */ + + if (riid == IID_IEnumPins || riid == IID_IUnknown) { + return GetInterface((IEnumPins *) this, ppv); + } else { + *ppv = NULL; + return E_NOINTERFACE; + } +} + +STDMETHODIMP_(ULONG) +CEnumPins::AddRef() +{ + return InterlockedIncrement(&m_cRef); +} + +STDMETHODIMP_(ULONG) +CEnumPins::Release() +{ + ULONG cRef = InterlockedDecrement(&m_cRef); + if (cRef == 0) { + delete this; + } + return cRef; +} + +/* One of an enumerator's basic member functions allows us to create a cloned + interface that initially has the same state. Since we are taking a snapshot + of an object (current position and all) we must lock access at the start */ + +STDMETHODIMP +CEnumPins::Clone(__deref_out IEnumPins **ppEnum) +{ + CheckPointer(ppEnum,E_POINTER); + ValidateReadWritePtr(ppEnum,sizeof(IEnumPins *)); + HRESULT hr = NOERROR; + + /* Check we are still in sync with the filter */ + if (AreWeOutOfSync() == TRUE) { + *ppEnum = NULL; + hr = VFW_E_ENUM_OUT_OF_SYNC; + } else { + *ppEnum = new CEnumPins(m_pFilter, + this); + if (*ppEnum == NULL) { + hr = E_OUTOFMEMORY; + } + } + return hr; +} + + +/* Return the next pin after the current position */ + +STDMETHODIMP +CEnumPins::Next(ULONG cPins, // place this many pins... + __out_ecount(cPins) IPin **ppPins, // ...in this array + __out_opt ULONG *pcFetched) // actual count passed returned here +{ + CheckPointer(ppPins,E_POINTER); + ValidateReadWritePtr(ppPins,cPins * sizeof(IPin *)); + + ASSERT(ppPins); + + if (pcFetched!=NULL) { + ValidateWritePtr(pcFetched, sizeof(ULONG)); + *pcFetched = 0; // default unless we succeed + } + // now check that the parameter is valid + else if (cPins>1) { // pcFetched == NULL + return E_INVALIDARG; + } + ULONG cFetched = 0; // increment as we get each one. + + /* Check we are still in sync with the filter */ + if (AreWeOutOfSync() == TRUE) { + // If we are out of sync, we should refresh the enumerator. + // This will reset the position and update the other members, but + // will not clear cache of pins we have already returned. + Refresh(); + } + + /* Return each pin interface NOTE GetPin returns CBasePin * not addrefed + so we must QI for the IPin (which increments its reference count) + If while we are retrieving a pin from the filter an error occurs we + assume that our internal state is stale with respect to the filter + (for example someone has deleted a pin) so we + return VFW_E_ENUM_OUT_OF_SYNC */ + + while (cFetched < cPins && m_PinCount > m_Position) { + + /* Get the next pin object from the filter */ + + CBasePin *pPin = m_pFilter->GetPin(m_Position++); + if (pPin == NULL) { + // If this happend, and it's not the first time through, then we've got a problem, + // since we should really go back and release the iPins, which we have previously + // AddRef'ed. + ASSERT( cFetched==0 ); + return VFW_E_ENUM_OUT_OF_SYNC; + } + + /* We only want to return this pin, if it is not in our cache */ + if (0 == m_PinCache.Find(pPin)) + { + /* From the object get an IPin interface */ + + *ppPins = pPin; + pPin->AddRef(); + + cFetched++; + ppPins++; + + m_PinCache.AddTail(pPin); + } + } + + if (pcFetched!=NULL) { + *pcFetched = cFetched; + } + + return (cPins==cFetched ? NOERROR : S_FALSE); +} + + +/* Skip over one or more entries in the enumerator */ + +STDMETHODIMP +CEnumPins::Skip(ULONG cPins) +{ + /* Check we are still in sync with the filter */ + if (AreWeOutOfSync() == TRUE) { + return VFW_E_ENUM_OUT_OF_SYNC; + } + + /* Work out how many pins are left to skip over */ + /* We could position at the end if we are asked to skip too many... */ + /* ..which would match the base implementation for CEnumMediaTypes::Skip */ + + ULONG PinsLeft = m_PinCount - m_Position; + if (cPins > PinsLeft) { + return S_FALSE; + } + m_Position += cPins; + return NOERROR; +} + + +/* Set the current position back to the start */ +/* Reset has 4 simple steps: + * + * Set position to head of list + * Sync enumerator with object being enumerated + * Clear the cache of pins already returned + * return S_OK + */ + +STDMETHODIMP +CEnumPins::Reset() +{ + m_Version = m_pFilter->GetPinVersion(); + m_PinCount = m_pFilter->GetPinCount(); + + m_Position = 0; + + // Clear the cache + m_PinCache.RemoveAll(); + + return S_OK; +} + + +/* Set the current position back to the start */ +/* Refresh has 3 simple steps: + * + * Set position to head of list + * Sync enumerator with object being enumerated + * return S_OK + */ + +STDMETHODIMP +CEnumPins::Refresh() +{ + m_Version = m_pFilter->GetPinVersion(); + m_PinCount = m_pFilter->GetPinCount(); + + m_Position = 0; + return S_OK; +} + + +//===================================================================== +//===================================================================== +// Implements CEnumMediaTypes +//===================================================================== +//===================================================================== + + +CEnumMediaTypes::CEnumMediaTypes(__in CBasePin *pPin, + __in_opt CEnumMediaTypes *pEnumMediaTypes) : + m_Position(0), + m_pPin(pPin), + m_cRef(1) +{ + +#ifdef DEBUG + m_dwCookie = DbgRegisterObjectCreation("CEnumMediaTypes", 0); +#endif + + /* We must be owned by a pin derived from CBasePin */ + + ASSERT(pPin != NULL); + + /* Hold a reference count on our pin */ + m_pPin->AddRef(); + + /* Are we creating a new enumerator */ + + if (pEnumMediaTypes == NULL) { + m_Version = m_pPin->GetMediaTypeVersion(); + return; + } + + m_Position = pEnumMediaTypes->m_Position; + m_Version = pEnumMediaTypes->m_Version; +} + + +/* Destructor releases the reference count on our base pin. NOTE since we hold + a reference count on the pin who created us we know it is safe to release + it, no access can be made to it afterwards though as we might have just + caused the last reference count to go and the object to be deleted */ + +CEnumMediaTypes::~CEnumMediaTypes() +{ +#ifdef DEBUG + DbgRegisterObjectDestruction(m_dwCookie); +#endif + m_pPin->Release(); +} + + +/* Override this to say what interfaces we support where */ + +STDMETHODIMP +CEnumMediaTypes::QueryInterface(REFIID riid, __deref_out void **ppv) +{ + CheckPointer(ppv, E_POINTER); + + /* Do we have this interface */ + + if (riid == IID_IEnumMediaTypes || riid == IID_IUnknown) { + return GetInterface((IEnumMediaTypes *) this, ppv); + } else { + *ppv = NULL; + return E_NOINTERFACE; + } +} + +STDMETHODIMP_(ULONG) +CEnumMediaTypes::AddRef() +{ + return InterlockedIncrement(&m_cRef); +} + +STDMETHODIMP_(ULONG) +CEnumMediaTypes::Release() +{ + ULONG cRef = InterlockedDecrement(&m_cRef); + if (cRef == 0) { + delete this; + } + return cRef; +} + +/* One of an enumerator's basic member functions allows us to create a cloned + interface that initially has the same state. Since we are taking a snapshot + of an object (current position and all) we must lock access at the start */ + +STDMETHODIMP +CEnumMediaTypes::Clone(__deref_out IEnumMediaTypes **ppEnum) +{ + CheckPointer(ppEnum,E_POINTER); + ValidateReadWritePtr(ppEnum,sizeof(IEnumMediaTypes *)); + HRESULT hr = NOERROR; + + /* Check we are still in sync with the pin */ + if (AreWeOutOfSync() == TRUE) { + *ppEnum = NULL; + hr = VFW_E_ENUM_OUT_OF_SYNC; + } else { + + *ppEnum = new CEnumMediaTypes(m_pPin, + this); + + if (*ppEnum == NULL) { + hr = E_OUTOFMEMORY; + } + } + return hr; +} + + +/* Enumerate the next pin(s) after the current position. The client using this + interface passes in a pointer to an array of pointers each of which will + be filled in with a pointer to a fully initialised media type format + Return NOERROR if it all works, + S_FALSE if fewer than cMediaTypes were enumerated. + VFW_E_ENUM_OUT_OF_SYNC if the enumerator has been broken by + state changes in the filter + The actual count always correctly reflects the number of types in the array. +*/ + +STDMETHODIMP +CEnumMediaTypes::Next(ULONG cMediaTypes, // place this many types... + __out_ecount(cMediaTypes) AM_MEDIA_TYPE **ppMediaTypes, // ...in this array + __out ULONG *pcFetched) // actual count passed +{ + CheckPointer(ppMediaTypes,E_POINTER); + ValidateReadWritePtr(ppMediaTypes,cMediaTypes * sizeof(AM_MEDIA_TYPE *)); + /* Check we are still in sync with the pin */ + if (AreWeOutOfSync() == TRUE) { + return VFW_E_ENUM_OUT_OF_SYNC; + } + + if (pcFetched!=NULL) { + ValidateWritePtr(pcFetched, sizeof(ULONG)); + *pcFetched = 0; // default unless we succeed + } + // now check that the parameter is valid + else if (cMediaTypes>1) { // pcFetched == NULL + return E_INVALIDARG; + } + ULONG cFetched = 0; // increment as we get each one. + + /* Return each media type by asking the filter for them in turn - If we + have an error code retured to us while we are retrieving a media type + we assume that our internal state is stale with respect to the filter + (for example the window size changing) so we return + VFW_E_ENUM_OUT_OF_SYNC */ + + while (cMediaTypes) { + + CMediaType cmt; + + HRESULT hr = m_pPin->GetMediaType(m_Position++, &cmt); + if (S_OK != hr) { + break; + } + + /* We now have a CMediaType object that contains the next media type + but when we assign it to the array position we CANNOT just assign + the AM_MEDIA_TYPE structure because as soon as the object goes out of + scope it will delete the memory we have just copied. The function + we use is CreateMediaType which allocates a task memory block */ + + /* Transfer across the format block manually to save an allocate + and free on the format block and generally go faster */ + + *ppMediaTypes = (AM_MEDIA_TYPE *)CoTaskMemAlloc(sizeof(AM_MEDIA_TYPE)); + if (*ppMediaTypes == NULL) { + break; + } + + /* Do a regular copy */ + **ppMediaTypes = cmt; + + /* Make sure the destructor doesn't free these */ + cmt.pbFormat = NULL; + cmt.cbFormat = NULL; + cmt.pUnk = NULL; + + + ppMediaTypes++; + cFetched++; + cMediaTypes--; + } + + if (pcFetched!=NULL) { + *pcFetched = cFetched; + } + + return ( cMediaTypes==0 ? NOERROR : S_FALSE ); +} + + +/* Skip over one or more entries in the enumerator */ + +STDMETHODIMP +CEnumMediaTypes::Skip(ULONG cMediaTypes) +{ + // If we're skipping 0 elements we're guaranteed to skip the + // correct number of elements + if (cMediaTypes == 0) { + return S_OK; + } + + /* Check we are still in sync with the pin */ + if (AreWeOutOfSync() == TRUE) { + return VFW_E_ENUM_OUT_OF_SYNC; + } + + m_Position += cMediaTypes; + + /* See if we're over the end */ + CMediaType cmt; + return S_OK == m_pPin->GetMediaType(m_Position - 1, &cmt) ? S_OK : S_FALSE; +} + + +/* Set the current position back to the start */ +/* Reset has 3 simple steps: + * + * set position to head of list + * sync enumerator with object being enumerated + * return S_OK + */ + +STDMETHODIMP +CEnumMediaTypes::Reset() + +{ + m_Position = 0; + + // Bring the enumerator back into step with the current state. This + // may be a noop but ensures that the enumerator will be valid on the + // next call. + m_Version = m_pPin->GetMediaTypeVersion(); + return NOERROR; +} + + +//===================================================================== +//===================================================================== +// Implements CBasePin +//===================================================================== +//===================================================================== + + +/* NOTE The implementation of this class calls the CUnknown constructor with + a NULL outer unknown pointer. This has the effect of making us a self + contained class, ie any QueryInterface, AddRef or Release calls will be + routed to the class's NonDelegatingUnknown methods. You will typically + find that the classes that do this then override one or more of these + virtual functions to provide more specialised behaviour. A good example + of this is where a class wants to keep the QueryInterface internal but + still wants its lifetime controlled by the external object */ + +/* Constructor */ + +CBasePin::CBasePin(__in_opt LPCTSTR pObjectName, + __in CBaseFilter *pFilter, + __in CCritSec *pLock, + __inout HRESULT *phr, + __in_opt LPCWSTR pName, + PIN_DIRECTION dir) : + CUnknown( pObjectName, NULL ), + m_pFilter(pFilter), + m_pLock(pLock), + m_pName(NULL), + m_Connected(NULL), + m_dir(dir), + m_bRunTimeError(FALSE), + m_pQSink(NULL), + m_TypeVersion(1), + m_tStart(), + m_tStop(MAX_TIME), + m_bCanReconnectWhenActive(false), + m_bTryMyTypesFirst(false), + m_dRate(1.0) +{ + /* WARNING - pFilter is often not a properly constituted object at + this state (in particular QueryInterface may not work) - this + is because its owner is often its containing object and we + have been called from the containing object's constructor so + the filter's owner has not yet had its CUnknown constructor + called + */ +#ifdef DXMPERF + PERFLOG_CTOR( pName ? pName : L"CBasePin", (IPin *) this ); +#endif // DXMPERF + + ASSERT(pFilter != NULL); + ASSERT(pLock != NULL); + + if (pName) { + size_t cchName; + HRESULT hr = StringCchLengthW(pName, STRSAFE_MAX_CCH, &cchName); + if (SUCCEEDED(hr)) { + m_pName = new WCHAR[cchName + 1]; + if (m_pName) { + (void)StringCchCopyW(m_pName, cchName + 1, pName); + } + } + } + +#ifdef DEBUG + m_cRef = 0; +#endif +} + +#ifdef UNICODE +CBasePin::CBasePin(__in_opt LPCSTR pObjectName, + __in CBaseFilter *pFilter, + __in CCritSec *pLock, + __inout HRESULT *phr, + __in_opt LPCWSTR pName, + PIN_DIRECTION dir) : + CUnknown( pObjectName, NULL ), + m_pFilter(pFilter), + m_pLock(pLock), + m_pName(NULL), + m_Connected(NULL), + m_dir(dir), + m_bRunTimeError(FALSE), + m_pQSink(NULL), + m_TypeVersion(1), + m_tStart(), + m_tStop(MAX_TIME), + m_bCanReconnectWhenActive(false), + m_bTryMyTypesFirst(false), + m_dRate(1.0) +{ + /* WARNING - pFilter is often not a properly constituted object at + this state (in particular QueryInterface may not work) - this + is because its owner is often its containing object and we + have been called from the containing object's constructor so + the filter's owner has not yet had its CUnknown constructor + called + */ +#ifdef DXMPERF + PERFLOG_CTOR( pName ? pName : L"CBasePin", (IPin *) this ); +#endif // DXMPERF + + ASSERT(pFilter != NULL); + ASSERT(pLock != NULL); + + if (pName) { + size_t cchName; + HRESULT hr = StringCchLengthW(pName, STRSAFE_MAX_CCH, &cchName); + if (SUCCEEDED(hr)) { + m_pName = new WCHAR[cchName + 1]; + if (m_pName) { + (void)StringCchCopyW(m_pName, cchName + 1, pName); + } + } + } + + +#ifdef DEBUG + m_cRef = 0; +#endif +} +#endif + +/* Destructor since a connected pin holds a reference count on us there is + no way that we can be deleted unless we are not currently connected */ + +CBasePin::~CBasePin() +{ +#ifdef DXMPERF + PERFLOG_DTOR( m_pName ? m_pName : L"CBasePin", (IPin *) this ); +#endif // DXMPERF + + // We don't call disconnect because if the filter is going away + // all the pins must have a reference count of zero so they must + // have been disconnected anyway - (but check the assumption) + ASSERT(m_Connected == FALSE); + + delete[] m_pName; + + // check the internal reference count is consistent + ASSERT(m_cRef == 0); +} + + +/* Override this to say what interfaces we support and where */ + +STDMETHODIMP +CBasePin::NonDelegatingQueryInterface(REFIID riid, __deref_out void ** ppv) +{ + /* Do we have this interface */ + + if (riid == IID_IPin) { + return GetInterface((IPin *) this, ppv); + } else if (riid == IID_IQualityControl) { + return GetInterface((IQualityControl *) this, ppv); + } else { + return CUnknown::NonDelegatingQueryInterface(riid, ppv); + } +} + + +/* Override to increment the owning filter's reference count */ + +STDMETHODIMP_(ULONG) +CBasePin::NonDelegatingAddRef() +{ + ASSERT(InterlockedIncrement(&m_cRef) > 0); + return m_pFilter->AddRef(); +} + + +/* Override to decrement the owning filter's reference count */ + +STDMETHODIMP_(ULONG) +CBasePin::NonDelegatingRelease() +{ + ASSERT(InterlockedDecrement(&m_cRef) >= 0); + return m_pFilter->Release(); +} + + +/* Displays pin connection information */ + +#ifdef DEBUG +void +CBasePin::DisplayPinInfo(IPin *pReceivePin) +{ + + if (DbgCheckModuleLevel(LOG_TRACE, CONNECT_TRACE_LEVEL)) { + PIN_INFO ConnectPinInfo; + PIN_INFO ReceivePinInfo; + + if (FAILED(QueryPinInfo(&ConnectPinInfo))) { + StringCchCopyW(ConnectPinInfo.achName, sizeof(ConnectPinInfo.achName)/sizeof(WCHAR), L"Bad Pin"); + } else { + QueryPinInfoReleaseFilter(ConnectPinInfo); + } + + if (FAILED(pReceivePin->QueryPinInfo(&ReceivePinInfo))) { + StringCchCopyW(ReceivePinInfo.achName, sizeof(ReceivePinInfo.achName)/sizeof(WCHAR), L"Bad Pin"); + } else { + QueryPinInfoReleaseFilter(ReceivePinInfo); + } + + DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT("Trying to connect Pins :"))); + DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT(" <%ls>"), ConnectPinInfo.achName)); + DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT(" <%ls>"), ReceivePinInfo.achName)); + } +} +#endif + + +/* Displays general information on the pin media type */ + +#ifdef DEBUG +void CBasePin::DisplayTypeInfo(IPin *pPin, const CMediaType *pmt) +{ + UNREFERENCED_PARAMETER(pPin); + if (DbgCheckModuleLevel(LOG_TRACE, CONNECT_TRACE_LEVEL)) { + DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT("Trying media type:"))); + DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT(" major type: %hs"), + GuidNames[*pmt->Type()])); + DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT(" sub type : %hs"), + GuidNames[*pmt->Subtype()])); + } +} +#endif + +/* Asked to connect to a pin. A pin is always attached to an owning filter + object so we always delegate our locking to that object. We first of all + retrieve a media type enumerator for the input pin and see if we accept + any of the formats that it would ideally like, failing that we retrieve + our enumerator and see if it will accept any of our preferred types */ + +STDMETHODIMP +CBasePin::Connect( + IPin * pReceivePin, + __in_opt const AM_MEDIA_TYPE *pmt // optional media type +) +{ + CheckPointer(pReceivePin,E_POINTER); + ValidateReadPtr(pReceivePin,sizeof(IPin)); + CAutoLock cObjectLock(m_pLock); + DisplayPinInfo(pReceivePin); + + /* See if we are already connected */ + + if (m_Connected) { + DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT("Already connected"))); + return VFW_E_ALREADY_CONNECTED; + } + + /* See if the filter is active */ + if (!IsStopped() && !m_bCanReconnectWhenActive) { + return VFW_E_NOT_STOPPED; + } + + + // Find a mutually agreeable media type - + // Pass in the template media type. If this is partially specified, + // each of the enumerated media types will need to be checked against + // it. If it is non-null and fully specified, we will just try to connect + // with this. + + const CMediaType * ptype = (CMediaType*)pmt; + HRESULT hr = AgreeMediaType(pReceivePin, ptype); + if (FAILED(hr)) { + DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT("Failed to agree type"))); + + // Since the procedure is already returning an error code, there + // is nothing else this function can do to report the error. + EXECUTE_ASSERT( SUCCEEDED( BreakConnect() ) ); + +#ifdef DXMPERF + PERFLOG_CONNECT( (IPin *) this, pReceivePin, hr, pmt ); +#endif // DXMPERF + + return hr; + } + + DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT("Connection succeeded"))); + +#ifdef DXMPERF + PERFLOG_CONNECT( (IPin *) this, pReceivePin, NOERROR, pmt ); +#endif // DXMPERF + + return NOERROR; +} + +// given a specific media type, attempt a connection (includes +// checking that the type is acceptable to this pin) +HRESULT +CBasePin::AttemptConnection( + IPin* pReceivePin, // connect to this pin + const CMediaType* pmt // using this type +) +{ + // The caller should hold the filter lock becasue this function + // uses m_Connected. The caller should also hold the filter lock + // because this function calls SetMediaType(), IsStopped() and + // CompleteConnect(). + ASSERT(CritCheckIn(m_pLock)); + + // Check that the connection is valid -- need to do this for every + // connect attempt since BreakConnect will undo it. + HRESULT hr = CheckConnect(pReceivePin); + if (FAILED(hr)) { + DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT("CheckConnect failed"))); + + // Since the procedure is already returning an error code, there + // is nothing else this function can do to report the error. + EXECUTE_ASSERT( SUCCEEDED( BreakConnect() ) ); + + return hr; + } + + DisplayTypeInfo(pReceivePin, pmt); + + /* Check we will accept this media type */ + + hr = CheckMediaType(pmt); + if (hr == NOERROR) { + + /* Make ourselves look connected otherwise ReceiveConnection + may not be able to complete the connection + */ + m_Connected = pReceivePin; + m_Connected->AddRef(); + hr = SetMediaType(pmt); + if (SUCCEEDED(hr)) { + /* See if the other pin will accept this type */ + + hr = pReceivePin->ReceiveConnection((IPin *)this, pmt); + if (SUCCEEDED(hr)) { + /* Complete the connection */ + + hr = CompleteConnect(pReceivePin); + if (SUCCEEDED(hr)) { + return hr; + } else { + DbgLog((LOG_TRACE, + CONNECT_TRACE_LEVEL, + TEXT("Failed to complete connection"))); + pReceivePin->Disconnect(); + } + } + } + } else { + // we cannot use this media type + + // return a specific media type error if there is one + // or map a general failure code to something more helpful + // (in particular S_FALSE gets changed to an error code) + if (SUCCEEDED(hr) || + (hr == E_FAIL) || + (hr == E_INVALIDARG)) { + hr = VFW_E_TYPE_NOT_ACCEPTED; + } + } + + // BreakConnect and release any connection here in case CheckMediaType + // failed, or if we set anything up during a call back during + // ReceiveConnection. + + // Since the procedure is already returning an error code, there + // is nothing else this function can do to report the error. + EXECUTE_ASSERT( SUCCEEDED( BreakConnect() ) ); + + /* If failed then undo our state */ + if (m_Connected) { + m_Connected->Release(); + m_Connected = NULL; + } + + return hr; +} + +/* Given an enumerator we cycle through all the media types it proposes and + firstly suggest them to our derived pin class and if that succeeds try + them with the pin in a ReceiveConnection call. This means that if our pin + proposes a media type we still check in here that we can support it. This + is deliberate so that in simple cases the enumerator can hold all of the + media types even if some of them are not really currently available */ + +HRESULT CBasePin::TryMediaTypes( + IPin *pReceivePin, + __in_opt const CMediaType *pmt, + IEnumMediaTypes *pEnum) +{ + /* Reset the current enumerator position */ + + HRESULT hr = pEnum->Reset(); + if (FAILED(hr)) { + return hr; + } + + CMediaType *pMediaType = NULL; + ULONG ulMediaCount = 0; + + // attempt to remember a specific error code if there is one + HRESULT hrFailure = S_OK; + + for (;;) { + + /* Retrieve the next media type NOTE each time round the loop the + enumerator interface will allocate another AM_MEDIA_TYPE structure + If we are successful then we copy it into our output object, if + not then we must delete the memory allocated before returning */ + + hr = pEnum->Next(1, (AM_MEDIA_TYPE**)&pMediaType,&ulMediaCount); + if (hr != S_OK) { + if (S_OK == hrFailure) { + hrFailure = VFW_E_NO_ACCEPTABLE_TYPES; + } + return hrFailure; + } + + + ASSERT(ulMediaCount == 1); + ASSERT(pMediaType); + + // check that this matches the partial type (if any) + + if (pMediaType && + ((pmt == NULL) || + pMediaType->MatchesPartial(pmt))) { + + hr = AttemptConnection(pReceivePin, pMediaType); + + // attempt to remember a specific error code + if (FAILED(hr) && + SUCCEEDED(hrFailure) && + (hr != E_FAIL) && + (hr != E_INVALIDARG) && + (hr != VFW_E_TYPE_NOT_ACCEPTED)) { + hrFailure = hr; + } + } else { + hr = VFW_E_NO_ACCEPTABLE_TYPES; + } + + if(pMediaType) { + DeleteMediaType(pMediaType); + pMediaType = NULL; + } + + if (S_OK == hr) { + return hr; + } + } +} + + +/* This is called to make the connection, including the taask of finding + a media type for the pin connection. pmt is the proposed media type + from the Connect call: if this is fully specified, we will try that. + Otherwise we enumerate and try all the input pin's types first and + if that fails we then enumerate and try all our preferred media types. + For each media type we check it against pmt (if non-null and partially + specified) as well as checking that both pins will accept it. + */ + +HRESULT CBasePin::AgreeMediaType( + IPin *pReceivePin, + const CMediaType *pmt) +{ + ASSERT(pReceivePin); + IEnumMediaTypes *pEnumMediaTypes = NULL; + + // if the media type is fully specified then use that + if ( (pmt != NULL) && (!pmt->IsPartiallySpecified())) { + + // if this media type fails, then we must fail the connection + // since if pmt is nonnull we are only allowed to connect + // using a type that matches it. + + return AttemptConnection(pReceivePin, pmt); + } + + + /* Try the other pin's enumerator */ + + HRESULT hrFailure = VFW_E_NO_ACCEPTABLE_TYPES; + + for (int i = 0; i < 2; i++) { + HRESULT hr; + if (i == (int)m_bTryMyTypesFirst) { + hr = pReceivePin->EnumMediaTypes(&pEnumMediaTypes); + } else { + hr = EnumMediaTypes(&pEnumMediaTypes); + } + if (SUCCEEDED(hr)) { + ASSERT(pEnumMediaTypes); + hr = TryMediaTypes(pReceivePin,pmt,pEnumMediaTypes); + pEnumMediaTypes->Release(); + if (SUCCEEDED(hr)) { + return NOERROR; + } else { + // try to remember specific error codes if there are any + if ((hr != E_FAIL) && + (hr != E_INVALIDARG) && + (hr != VFW_E_TYPE_NOT_ACCEPTED)) { + hrFailure = hr; + } + } + } + } + + return hrFailure; +} + + +/* Called when we want to complete a connection to another filter. Failing + this will also fail the connection and disconnect the other pin as well */ + +HRESULT +CBasePin::CompleteConnect(IPin *pReceivePin) +{ + UNREFERENCED_PARAMETER(pReceivePin); + return NOERROR; +} + + +/* This is called to set the format for a pin connection - CheckMediaType + will have been called to check the connection format and if it didn't + return an error code then this (virtual) function will be invoked */ + +HRESULT +CBasePin::SetMediaType(const CMediaType *pmt) +{ + HRESULT hr = m_mt.Set(*pmt); + if (FAILED(hr)) { + return hr; + } + + return NOERROR; +} + + +/* This is called during Connect() to provide a virtual method that can do + any specific check needed for connection such as QueryInterface. This + base class method just checks that the pin directions don't match */ + +HRESULT +CBasePin::CheckConnect(IPin * pPin) +{ + /* Check that pin directions DONT match */ + + PIN_DIRECTION pd; + pPin->QueryDirection(&pd); + + ASSERT((pd == PINDIR_OUTPUT) || (pd == PINDIR_INPUT)); + ASSERT((m_dir == PINDIR_OUTPUT) || (m_dir == PINDIR_INPUT)); + + // we should allow for non-input and non-output connections? + if (pd == m_dir) { + return VFW_E_INVALID_DIRECTION; + } + return NOERROR; +} + + +/* This is called when we realise we can't make a connection to the pin and + must undo anything we did in CheckConnect - override to release QIs done */ + +HRESULT +CBasePin::BreakConnect() +{ + return NOERROR; +} + + +/* Called normally by an output pin on an input pin to try and establish a + connection. +*/ + +STDMETHODIMP +CBasePin::ReceiveConnection( + IPin * pConnector, // this is the pin who we will connect to + const AM_MEDIA_TYPE *pmt // this is the media type we will exchange +) +{ + CheckPointer(pConnector,E_POINTER); + CheckPointer(pmt,E_POINTER); + ValidateReadPtr(pConnector,sizeof(IPin)); + ValidateReadPtr(pmt,sizeof(AM_MEDIA_TYPE)); + CAutoLock cObjectLock(m_pLock); + + /* Are we already connected */ + if (m_Connected) { + return VFW_E_ALREADY_CONNECTED; + } + + /* See if the filter is active */ + if (!IsStopped() && !m_bCanReconnectWhenActive) { + return VFW_E_NOT_STOPPED; + } + + HRESULT hr = CheckConnect(pConnector); + if (FAILED(hr)) { + // Since the procedure is already returning an error code, there + // is nothing else this function can do to report the error. + EXECUTE_ASSERT( SUCCEEDED( BreakConnect() ) ); + +#ifdef DXMPERF + PERFLOG_RXCONNECT( pConnector, (IPin *) this, hr, pmt ); +#endif // DXMPERF + + return hr; + } + + /* Ask derived class if this media type is ok */ + + CMediaType * pcmt = (CMediaType*) pmt; + hr = CheckMediaType(pcmt); + if (hr != NOERROR) { + // no -we don't support this media type + + // Since the procedure is already returning an error code, there + // is nothing else this function can do to report the error. + EXECUTE_ASSERT( SUCCEEDED( BreakConnect() ) ); + + // return a specific media type error if there is one + // or map a general failure code to something more helpful + // (in particular S_FALSE gets changed to an error code) + if (SUCCEEDED(hr) || + (hr == E_FAIL) || + (hr == E_INVALIDARG)) { + hr = VFW_E_TYPE_NOT_ACCEPTED; + } + +#ifdef DXMPERF + PERFLOG_RXCONNECT( pConnector, (IPin *) this, hr, pmt ); +#endif // DXMPERF + + return hr; + } + + /* Complete the connection */ + + m_Connected = pConnector; + m_Connected->AddRef(); + hr = SetMediaType(pcmt); + if (SUCCEEDED(hr)) { + hr = CompleteConnect(pConnector); + if (SUCCEEDED(hr)) { + +#ifdef DXMPERF + PERFLOG_RXCONNECT( pConnector, (IPin *) this, NOERROR, pmt ); +#endif // DXMPERF + + return NOERROR; + } + } + + DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT("Failed to set the media type or failed to complete the connection."))); + m_Connected->Release(); + m_Connected = NULL; + + // Since the procedure is already returning an error code, there + // is nothing else this function can do to report the error. + EXECUTE_ASSERT( SUCCEEDED( BreakConnect() ) ); + +#ifdef DXMPERF + PERFLOG_RXCONNECT( pConnector, (IPin *) this, hr, pmt ); +#endif // DXMPERF + + return hr; +} + + +/* Called when we want to terminate a pin connection */ + +STDMETHODIMP +CBasePin::Disconnect() +{ + CAutoLock cObjectLock(m_pLock); + + /* See if the filter is active */ + if (!IsStopped()) { + return VFW_E_NOT_STOPPED; + } + + return DisconnectInternal(); +} + +STDMETHODIMP +CBasePin::DisconnectInternal() +{ + ASSERT(CritCheckIn(m_pLock)); + + if (m_Connected) { + HRESULT hr = BreakConnect(); + if( FAILED( hr ) ) { + +#ifdef DXMPERF + PERFLOG_DISCONNECT( (IPin *) this, m_Connected, hr ); +#endif // DXMPERF + + // There is usually a bug in the program if BreakConnect() fails. + DbgBreak( "WARNING: BreakConnect() failed in CBasePin::Disconnect()." ); + return hr; + } + + m_Connected->Release(); + m_Connected = NULL; + +#ifdef DXMPERF + PERFLOG_DISCONNECT( (IPin *) this, m_Connected, S_OK ); +#endif // DXMPERF + + return S_OK; + } else { + // no connection - not an error + +#ifdef DXMPERF + PERFLOG_DISCONNECT( (IPin *) this, m_Connected, S_FALSE ); +#endif // DXMPERF + + return S_FALSE; + } +} + + +/* Return an AddRef()'d pointer to the connected pin if there is one */ +STDMETHODIMP +CBasePin::ConnectedTo( + __deref_out IPin **ppPin +) +{ + CheckPointer(ppPin,E_POINTER); + ValidateReadWritePtr(ppPin,sizeof(IPin *)); + // + // It's pointless to lock here. + // The caller should ensure integrity. + // + + IPin *pPin = m_Connected; + *ppPin = pPin; + if (pPin != NULL) { + pPin->AddRef(); + return S_OK; + } else { + ASSERT(*ppPin == NULL); + return VFW_E_NOT_CONNECTED; + } +} + +/* Return the media type of the connection */ +STDMETHODIMP +CBasePin::ConnectionMediaType( + __out AM_MEDIA_TYPE *pmt +) +{ + CheckPointer(pmt,E_POINTER); + ValidateReadWritePtr(pmt,sizeof(AM_MEDIA_TYPE)); + CAutoLock cObjectLock(m_pLock); + + /* Copy constructor of m_mt allocates the memory */ + if (IsConnected()) { + CopyMediaType( pmt, &m_mt ); + return S_OK; + } else { + ((CMediaType *)pmt)->InitMediaType(); + return VFW_E_NOT_CONNECTED; + } +} + +/* Return information about the filter we are connect to */ + +STDMETHODIMP +CBasePin::QueryPinInfo( + __out PIN_INFO * pInfo +) +{ + CheckPointer(pInfo,E_POINTER); + ValidateReadWritePtr(pInfo,sizeof(PIN_INFO)); + + pInfo->pFilter = m_pFilter; + if (m_pFilter) { + m_pFilter->AddRef(); + } + + if (m_pName) { + (void)StringCchCopyW(pInfo->achName, NUMELMS(pInfo->achName), m_pName); + } else { + pInfo->achName[0] = L'\0'; + } + + pInfo->dir = m_dir; + + return NOERROR; +} + +STDMETHODIMP +CBasePin::QueryDirection( + __out PIN_DIRECTION * pPinDir +) +{ + CheckPointer(pPinDir,E_POINTER); + ValidateReadWritePtr(pPinDir,sizeof(PIN_DIRECTION)); + + *pPinDir = m_dir; + return NOERROR; +} + +// Default QueryId to return the pin's name +STDMETHODIMP +CBasePin::QueryId( + __deref_out LPWSTR * Id +) +{ + // We're not going away because someone's got a pointer to us + // so there's no need to lock + + return AMGetWideString(Name(), Id); +} + +/* Does this pin support this media type WARNING this interface function does + not lock the main object as it is meant to be asynchronous by nature - if + the media types you support depend on some internal state that is updated + dynamically then you will need to implement locking in a derived class */ + +STDMETHODIMP +CBasePin::QueryAccept( + const AM_MEDIA_TYPE *pmt +) +{ + CheckPointer(pmt,E_POINTER); + ValidateReadPtr(pmt,sizeof(AM_MEDIA_TYPE)); + + /* The CheckMediaType method is valid to return error codes if the media + type is horrible, an example might be E_INVALIDARG. What we do here + is map all the error codes into either S_OK or S_FALSE regardless */ + + HRESULT hr = CheckMediaType((CMediaType*)pmt); + if (FAILED(hr)) { + return S_FALSE; + } + // note that the only defined success codes should be S_OK and S_FALSE... + return hr; +} + + +/* This can be called to return an enumerator for the pin's list of preferred + media types. An input pin is not obliged to have any preferred formats + although it can do. For example, the window renderer has a preferred type + which describes a video image that matches the current window size. All + output pins should expose at least one preferred format otherwise it is + possible that neither pin has any types and so no connection is possible */ + +STDMETHODIMP +CBasePin::EnumMediaTypes( + __deref_out IEnumMediaTypes **ppEnum +) +{ + CheckPointer(ppEnum,E_POINTER); + ValidateReadWritePtr(ppEnum,sizeof(IEnumMediaTypes *)); + + /* Create a new ref counted enumerator */ + + *ppEnum = new CEnumMediaTypes(this, + NULL); + + if (*ppEnum == NULL) { + return E_OUTOFMEMORY; + } + + return NOERROR; +} + + + +/* This is a virtual function that returns a media type corresponding with + place iPosition in the list. This base class simply returns an error as + we support no media types by default but derived classes should override */ + +HRESULT CBasePin::GetMediaType(int iPosition, __inout CMediaType *pMediaType) +{ + UNREFERENCED_PARAMETER(iPosition); + UNREFERENCED_PARAMETER(pMediaType); + return E_UNEXPECTED; +} + + +/* This is a virtual function that returns the current media type version. + The base class initialises the media type enumerators with the value 1 + By default we always returns that same value. A Derived class may change + the list of media types available and after doing so it should increment + the version either in a method derived from this, or more simply by just + incrementing the m_TypeVersion base pin variable. The type enumerators + call this when they want to see if their enumerations are out of date */ + +LONG CBasePin::GetMediaTypeVersion() +{ + return m_TypeVersion; +} + + +/* Increment the cookie representing the current media type version */ + +void CBasePin::IncrementTypeVersion() +{ + InterlockedIncrement(&m_TypeVersion); +} + + +/* Called by IMediaFilter implementation when the state changes from Stopped + to either paused or running and in derived classes could do things like + commit memory and grab hardware resource (the default is to do nothing) */ + +HRESULT +CBasePin::Active(void) +{ + return NOERROR; +} + +/* Called by IMediaFilter implementation when the state changes from + to either paused to running and in derived classes could do things like + commit memory and grab hardware resource (the default is to do nothing) */ + +HRESULT +CBasePin::Run(REFERENCE_TIME tStart) +{ + UNREFERENCED_PARAMETER(tStart); + return NOERROR; +} + + +/* Also called by the IMediaFilter implementation when the state changes to + Stopped at which point you should decommit allocators and free hardware + resources you grabbed in the Active call (default is also to do nothing) */ + +HRESULT +CBasePin::Inactive(void) +{ + m_bRunTimeError = FALSE; + return NOERROR; +} + + +// Called when no more data will arrive +STDMETHODIMP +CBasePin::EndOfStream(void) +{ + return S_OK; +} + + +STDMETHODIMP +CBasePin::SetSink(IQualityControl * piqc) +{ + CAutoLock cObjectLock(m_pLock); + if (piqc) ValidateReadPtr(piqc,sizeof(IQualityControl)); + m_pQSink = piqc; + return NOERROR; +} // SetSink + + +STDMETHODIMP +CBasePin::Notify(IBaseFilter * pSender, Quality q) +{ + UNREFERENCED_PARAMETER(q); + UNREFERENCED_PARAMETER(pSender); + DbgBreak("IQualityControl::Notify not over-ridden from CBasePin. (IGNORE is OK)"); + return E_NOTIMPL; +} //Notify + + +// NewSegment notifies of the start/stop/rate applying to the data +// about to be received. Default implementation records data and +// returns S_OK. +// Override this to pass downstream. +STDMETHODIMP +CBasePin::NewSegment( + REFERENCE_TIME tStart, + REFERENCE_TIME tStop, + double dRate) +{ + m_tStart = tStart; + m_tStop = tStop; + m_dRate = dRate; + + return S_OK; +} + + +//===================================================================== +//===================================================================== +// Implements CBaseOutputPin +//===================================================================== +//===================================================================== + + +CBaseOutputPin::CBaseOutputPin(__in_opt LPCTSTR pObjectName, + __in CBaseFilter *pFilter, + __in CCritSec *pLock, + __inout HRESULT *phr, + __in_opt LPCWSTR pName) : + CBasePin(pObjectName, pFilter, pLock, phr, pName, PINDIR_OUTPUT), + m_pAllocator(NULL), + m_pInputPin(NULL) +{ + ASSERT(pFilter); +} + +#ifdef UNICODE +CBaseOutputPin::CBaseOutputPin(__in_opt LPCSTR pObjectName, + __in CBaseFilter *pFilter, + __in CCritSec *pLock, + __inout HRESULT *phr, + __in_opt LPCWSTR pName) : + CBasePin(pObjectName, pFilter, pLock, phr, pName, PINDIR_OUTPUT), + m_pAllocator(NULL), + m_pInputPin(NULL) +{ + ASSERT(pFilter); +} +#endif + +/* This is called after a media type has been proposed + + Try to complete the connection by agreeing the allocator +*/ +HRESULT +CBaseOutputPin::CompleteConnect(IPin *pReceivePin) +{ + UNREFERENCED_PARAMETER(pReceivePin); + return DecideAllocator(m_pInputPin, &m_pAllocator); +} + + +/* This method is called when the output pin is about to try and connect to + an input pin. It is at this point that you should try and grab any extra + interfaces that you need, in this case IMemInputPin. Because this is + only called if we are not currently connected we do NOT need to call + BreakConnect. This also makes it easier to derive classes from us as + BreakConnect is only called when we actually have to break a connection + (or a partly made connection) and not when we are checking a connection */ + +/* Overriden from CBasePin */ + +HRESULT +CBaseOutputPin::CheckConnect(IPin * pPin) +{ + HRESULT hr = CBasePin::CheckConnect(pPin); + if (FAILED(hr)) { + return hr; + } + + // get an input pin and an allocator interface + hr = pPin->QueryInterface(IID_IMemInputPin, (void **) &m_pInputPin); + if (FAILED(hr)) { + return hr; + } + return NOERROR; +} + + +/* Overriden from CBasePin */ + +HRESULT +CBaseOutputPin::BreakConnect() +{ + /* Release any allocator we hold */ + + if (m_pAllocator) { + // Always decommit the allocator because a downstream filter may or + // may not decommit the connection's allocator. A memory leak could + // occur if the allocator is not decommited when a connection is broken. + HRESULT hr = m_pAllocator->Decommit(); + if( FAILED( hr ) ) { + return hr; + } + + m_pAllocator->Release(); + m_pAllocator = NULL; + } + + /* Release any input pin interface we hold */ + + if (m_pInputPin) { + m_pInputPin->Release(); + m_pInputPin = NULL; + } + return NOERROR; +} + + +/* This is called when the input pin didn't give us a valid allocator */ + +HRESULT +CBaseOutputPin::InitAllocator(__deref_out IMemAllocator **ppAlloc) +{ + return CreateMemoryAllocator(ppAlloc); +} + + +/* Decide on an allocator, override this if you want to use your own allocator + Override DecideBufferSize to call SetProperties. If the input pin fails + the GetAllocator call then this will construct a CMemAllocator and call + DecideBufferSize on that, and if that fails then we are completely hosed. + If the you succeed the DecideBufferSize call, we will notify the input + pin of the selected allocator. NOTE this is called during Connect() which + therefore looks after grabbing and locking the object's critical section */ + +// We query the input pin for its requested properties and pass this to +// DecideBufferSize to allow it to fulfill requests that it is happy +// with (eg most people don't care about alignment and are thus happy to +// use the downstream pin's alignment request). + +HRESULT +CBaseOutputPin::DecideAllocator(IMemInputPin *pPin, __deref_out IMemAllocator **ppAlloc) +{ + HRESULT hr = NOERROR; + *ppAlloc = NULL; + + // get downstream prop request + // the derived class may modify this in DecideBufferSize, but + // we assume that he will consistently modify it the same way, + // so we only get it once + ALLOCATOR_PROPERTIES prop; + ZeroMemory(&prop, sizeof(prop)); + + // whatever he returns, we assume prop is either all zeros + // or he has filled it out. + pPin->GetAllocatorRequirements(&prop); + + // if he doesn't care about alignment, then set it to 1 + if (prop.cbAlign == 0) { + prop.cbAlign = 1; + } + + /* Try the allocator provided by the input pin */ + + hr = pPin->GetAllocator(ppAlloc); + if (SUCCEEDED(hr)) { + + hr = DecideBufferSize(*ppAlloc, &prop); + if (SUCCEEDED(hr)) { + hr = pPin->NotifyAllocator(*ppAlloc, FALSE); + if (SUCCEEDED(hr)) { + return NOERROR; + } + } + } + + /* If the GetAllocator failed we may not have an interface */ + + if (*ppAlloc) { + (*ppAlloc)->Release(); + *ppAlloc = NULL; + } + + /* Try the output pin's allocator by the same method */ + + hr = InitAllocator(ppAlloc); + if (SUCCEEDED(hr)) { + + // note - the properties passed here are in the same + // structure as above and may have been modified by + // the previous call to DecideBufferSize + hr = DecideBufferSize(*ppAlloc, &prop); + if (SUCCEEDED(hr)) { + hr = pPin->NotifyAllocator(*ppAlloc, FALSE); + if (SUCCEEDED(hr)) { + return NOERROR; + } + } + } + + /* Likewise we may not have an interface to release */ + + if (*ppAlloc) { + (*ppAlloc)->Release(); + *ppAlloc = NULL; + } + return hr; +} + + +/* This returns an empty sample buffer from the allocator WARNING the same + dangers and restrictions apply here as described below for Deliver() */ + +HRESULT +CBaseOutputPin::GetDeliveryBuffer(__deref_out IMediaSample ** ppSample, + __in_opt REFERENCE_TIME * pStartTime, + __in_opt REFERENCE_TIME * pEndTime, + DWORD dwFlags) +{ + if (m_pAllocator != NULL) { + return m_pAllocator->GetBuffer(ppSample,pStartTime,pEndTime,dwFlags); + } else { + return E_NOINTERFACE; + } +} + + +/* Deliver a filled-in sample to the connected input pin. NOTE the object must + have locked itself before calling us otherwise we may get halfway through + executing this method only to find the filter graph has got in and + disconnected us from the input pin. If the filter has no worker threads + then the lock is best applied on Receive(), otherwise it should be done + when the worker thread is ready to deliver. There is a wee snag to worker + threads that this shows up. The worker thread must lock the object when + it is ready to deliver a sample, but it may have to wait until a state + change has completed, but that may never complete because the state change + is waiting for the worker thread to complete. The way to handle this is for + the state change code to grab the critical section, then set an abort event + for the worker thread, then release the critical section and wait for the + worker thread to see the event we set and then signal that it has finished + (with another event). At which point the state change code can complete */ + +// note (if you've still got any breath left after reading that) that you +// need to release the sample yourself after this call. if the connected +// input pin needs to hold onto the sample beyond the call, it will addref +// the sample itself. + +// of course you must release this one and call GetDeliveryBuffer for the +// next. You cannot reuse it directly. + +HRESULT +CBaseOutputPin::Deliver(IMediaSample * pSample) +{ + if (m_pInputPin == NULL) { + return VFW_E_NOT_CONNECTED; + } + +#ifdef DXMPERF + PERFLOG_DELIVER( m_pName ? m_pName : L"CBaseOutputPin", (IPin *) this, (IPin *) m_pInputPin, pSample, &m_mt ); +#endif // DXMPERF + + return m_pInputPin->Receive(pSample); +} + + +// called from elsewhere in our filter to pass EOS downstream to +// our connected input pin +HRESULT +CBaseOutputPin::DeliverEndOfStream(void) +{ + // remember this is on IPin not IMemInputPin + if (m_Connected == NULL) { + return VFW_E_NOT_CONNECTED; + } + return m_Connected->EndOfStream(); +} + + +/* Commit the allocator's memory, this is called through IMediaFilter + which is responsible for locking the object before calling us */ + +HRESULT +CBaseOutputPin::Active(void) +{ + if (m_pAllocator == NULL) { + return VFW_E_NO_ALLOCATOR; + } + return m_pAllocator->Commit(); +} + + +/* Free up or unprepare allocator's memory, this is called through + IMediaFilter which is responsible for locking the object first */ + +HRESULT +CBaseOutputPin::Inactive(void) +{ + m_bRunTimeError = FALSE; + if (m_pAllocator == NULL) { + return VFW_E_NO_ALLOCATOR; + } + return m_pAllocator->Decommit(); +} + +// we have a default handling of EndOfStream which is to return +// an error, since this should be called on input pins only +STDMETHODIMP +CBaseOutputPin::EndOfStream(void) +{ + return E_UNEXPECTED; +} + + +// BeginFlush should be called on input pins only +STDMETHODIMP +CBaseOutputPin::BeginFlush(void) +{ + return E_UNEXPECTED; +} + +// EndFlush should be called on input pins only +STDMETHODIMP +CBaseOutputPin::EndFlush(void) +{ + return E_UNEXPECTED; +} + +// call BeginFlush on the connected input pin +HRESULT +CBaseOutputPin::DeliverBeginFlush(void) +{ + // remember this is on IPin not IMemInputPin + if (m_Connected == NULL) { + return VFW_E_NOT_CONNECTED; + } + return m_Connected->BeginFlush(); +} + +// call EndFlush on the connected input pin +HRESULT +CBaseOutputPin::DeliverEndFlush(void) +{ + // remember this is on IPin not IMemInputPin + if (m_Connected == NULL) { + return VFW_E_NOT_CONNECTED; + } + return m_Connected->EndFlush(); +} +// deliver NewSegment to connected pin +HRESULT +CBaseOutputPin::DeliverNewSegment( + REFERENCE_TIME tStart, + REFERENCE_TIME tStop, + double dRate) +{ + if (m_Connected == NULL) { + return VFW_E_NOT_CONNECTED; + } + return m_Connected->NewSegment(tStart, tStop, dRate); +} + + +//===================================================================== +//===================================================================== +// Memory allocation class, implements CMediaSample +//===================================================================== +//===================================================================== + + +/* NOTE The implementation of this class calls the CUnknown constructor with + a NULL outer unknown pointer. This has the effect of making us a self + contained class, ie any QueryInterface, AddRef or Release calls will be + routed to the class's NonDelegatingUnknown methods. You will typically + find that the classes that do this then override one or more of these + virtual functions to provide more specialised behaviour. A good example + of this is where a class wants to keep the QueryInterface internal but + still wants it's lifetime controlled by the external object */ + +/* The last two parameters have default values of NULL and zero */ + +CMediaSample::CMediaSample(__in_opt LPCTSTR pName, + __in_opt CBaseAllocator *pAllocator, + __inout_opt HRESULT *phr, + __in_bcount_opt(length) LPBYTE pBuffer, + LONG length) : + m_pBuffer(pBuffer), // Initialise the buffer + m_cbBuffer(length), // And it's length + m_lActual(length), // By default, actual = length + m_pMediaType(NULL), // No media type change + m_dwFlags(0), // Nothing set + m_cRef(0), // 0 ref count + m_dwTypeSpecificFlags(0), // Type specific flags + m_dwStreamId(AM_STREAM_MEDIA), // Stream id + m_pAllocator(pAllocator) // Allocator +{ +#ifdef DXMPERF + PERFLOG_CTOR( pName ? pName : L"CMediaSample", (IMediaSample *) this ); +#endif // DXMPERF + + /* We must have an owner and it must also be derived from class + CBaseAllocator BUT we do not hold a reference count on it */ + + ASSERT(pAllocator); + + if (length < 0) { + *phr = VFW_E_BUFFER_OVERFLOW; + m_cbBuffer = 0; + } +} + +#ifdef UNICODE +CMediaSample::CMediaSample(__in_opt LPCSTR pName, + __in_opt CBaseAllocator *pAllocator, + __inout_opt HRESULT *phr, + __in_bcount_opt(length) LPBYTE pBuffer, + LONG length) : + m_pBuffer(pBuffer), // Initialise the buffer + m_cbBuffer(length), // And it's length + m_lActual(length), // By default, actual = length + m_pMediaType(NULL), // No media type change + m_dwFlags(0), // Nothing set + m_cRef(0), // 0 ref count + m_dwTypeSpecificFlags(0), // Type specific flags + m_dwStreamId(AM_STREAM_MEDIA), // Stream id + m_pAllocator(pAllocator) // Allocator +{ +#ifdef DXMPERF + PERFLOG_CTOR( L"CMediaSample", (IMediaSample *) this ); +#endif // DXMPERF + + /* We must have an owner and it must also be derived from class + CBaseAllocator BUT we do not hold a reference count on it */ + + ASSERT(pAllocator); +} +#endif + +/* Destructor deletes the media type memory */ + +CMediaSample::~CMediaSample() +{ +#ifdef DXMPERF + PERFLOG_DTOR( L"CMediaSample", (IMediaSample *) this ); +#endif // DXMPERF + + if (m_pMediaType) { + DeleteMediaType(m_pMediaType); + } +} + +/* Override this to publicise our interfaces */ + +STDMETHODIMP +CMediaSample::QueryInterface(REFIID riid, __deref_out void **ppv) +{ + if (riid == IID_IMediaSample || + riid == IID_IMediaSample2 || + riid == IID_IUnknown) { + return GetInterface((IMediaSample *) this, ppv); + } else { + *ppv = NULL; + return E_NOINTERFACE; + } +} + +STDMETHODIMP_(ULONG) +CMediaSample::AddRef() +{ + return InterlockedIncrement(&m_cRef); +} + + +// -- CMediaSample lifetimes -- +// +// On final release of this sample buffer it is not deleted but +// returned to the freelist of the owning memory allocator +// +// The allocator may be waiting for the last buffer to be placed on the free +// list in order to decommit all the memory, so the ReleaseBuffer() call may +// result in this sample being deleted. We also need to hold a refcount on +// the allocator to stop that going away until we have finished with this. +// However, we cannot release the allocator before the ReleaseBuffer, as the +// release may cause us to be deleted. Similarly we can't do it afterwards. +// +// Thus we must leave it to the allocator to hold an addref on our behalf. +// When he issues us in GetBuffer, he addref's himself. When ReleaseBuffer +// is called, he releases himself, possibly causing us and him to be deleted. + + +STDMETHODIMP_(ULONG) +CMediaSample::Release() +{ + /* Decrement our own private reference count */ + LONG lRef; + if (m_cRef == 1) { + lRef = 0; + m_cRef = 0; + } else { + lRef = InterlockedDecrement(&m_cRef); + } + ASSERT(lRef >= 0); + + DbgLog((LOG_MEMORY,3,TEXT(" Unknown %X ref-- = %d"), + this, m_cRef)); + + /* Did we release our final reference count */ + if (lRef == 0) { + /* Free all resources */ + if (m_dwFlags & Sample_TypeChanged) { + SetMediaType(NULL); + } + ASSERT(m_pMediaType == NULL); + m_dwFlags = 0; + m_dwTypeSpecificFlags = 0; + m_dwStreamId = AM_STREAM_MEDIA; + + /* This may cause us to be deleted */ + // Our refcount is reliably 0 thus no-one will mess with us + m_pAllocator->ReleaseBuffer(this); + } + return (ULONG)lRef; +} + + +// set the buffer pointer and length. Used by allocators that +// want variable sized pointers or pointers into already-read data. +// This is only available through a CMediaSample* not an IMediaSample* +// and so cannot be changed by clients. +HRESULT +CMediaSample::SetPointer(__in_bcount(cBytes) BYTE * ptr, LONG cBytes) +{ + if (cBytes < 0) { + return VFW_E_BUFFER_OVERFLOW; + } + m_pBuffer = ptr; // new buffer area (could be null) + m_cbBuffer = cBytes; // length of buffer + m_lActual = cBytes; // length of data in buffer (assume full) + + return S_OK; +} + + +// get me a read/write pointer to this buffer's memory. I will actually +// want to use sizeUsed bytes. +STDMETHODIMP +CMediaSample::GetPointer(__deref_out BYTE ** ppBuffer) +{ + ValidateReadWritePtr(ppBuffer,sizeof(BYTE *)); + + // creator must have set pointer either during + // constructor or by SetPointer + ASSERT(m_pBuffer); + + *ppBuffer = m_pBuffer; + return NOERROR; +} + + +// return the size in bytes of this buffer +STDMETHODIMP_(LONG) +CMediaSample::GetSize(void) +{ + return m_cbBuffer; +} + + +// get the stream time at which this sample should start and finish. +STDMETHODIMP +CMediaSample::GetTime( + __out REFERENCE_TIME * pTimeStart, // put time here + __out REFERENCE_TIME * pTimeEnd +) +{ + ValidateReadWritePtr(pTimeStart,sizeof(REFERENCE_TIME)); + ValidateReadWritePtr(pTimeEnd,sizeof(REFERENCE_TIME)); + + if (!(m_dwFlags & Sample_StopValid)) { + if (!(m_dwFlags & Sample_TimeValid)) { + return VFW_E_SAMPLE_TIME_NOT_SET; + } else { + *pTimeStart = m_Start; + + // Make sure old stuff works + *pTimeEnd = m_Start + 1; + return VFW_S_NO_STOP_TIME; + } + } + + *pTimeStart = m_Start; + *pTimeEnd = m_End; + return NOERROR; +} + + +// Set the stream time at which this sample should start and finish. +// NULL pointers means the time is reset +STDMETHODIMP +CMediaSample::SetTime( + __in_opt REFERENCE_TIME * pTimeStart, + __in_opt REFERENCE_TIME * pTimeEnd +) +{ + if (pTimeStart == NULL) { + ASSERT(pTimeEnd == NULL); + m_dwFlags &= ~(Sample_TimeValid | Sample_StopValid); + } else { + if (pTimeEnd == NULL) { + m_Start = *pTimeStart; + m_dwFlags |= Sample_TimeValid; + m_dwFlags &= ~Sample_StopValid; + } else { + ValidateReadPtr(pTimeStart,sizeof(REFERENCE_TIME)); + ValidateReadPtr(pTimeEnd,sizeof(REFERENCE_TIME)); + ASSERT(*pTimeEnd >= *pTimeStart); + + m_Start = *pTimeStart; + m_End = *pTimeEnd; + m_dwFlags |= Sample_TimeValid | Sample_StopValid; + } + } + return NOERROR; +} + + +// get the media times (eg bytes) for this sample +STDMETHODIMP +CMediaSample::GetMediaTime( + __out LONGLONG * pTimeStart, + __out LONGLONG * pTimeEnd +) +{ + ValidateReadWritePtr(pTimeStart,sizeof(LONGLONG)); + ValidateReadWritePtr(pTimeEnd,sizeof(LONGLONG)); + + if (!(m_dwFlags & Sample_MediaTimeValid)) { + return VFW_E_MEDIA_TIME_NOT_SET; + } + + *pTimeStart = m_MediaStart; + *pTimeEnd = (m_MediaStart + m_MediaEnd); + return NOERROR; +} + + +// Set the media times for this sample +STDMETHODIMP +CMediaSample::SetMediaTime( + __in_opt LONGLONG * pTimeStart, + __in_opt LONGLONG * pTimeEnd +) +{ + if (pTimeStart == NULL) { + ASSERT(pTimeEnd == NULL); + m_dwFlags &= ~Sample_MediaTimeValid; + } else { + if (NULL == pTimeEnd) { + return E_POINTER; + } + ValidateReadPtr(pTimeStart,sizeof(LONGLONG)); + ValidateReadPtr(pTimeEnd,sizeof(LONGLONG)); + ASSERT(*pTimeEnd >= *pTimeStart); + + m_MediaStart = *pTimeStart; + m_MediaEnd = (LONG)(*pTimeEnd - *pTimeStart); + m_dwFlags |= Sample_MediaTimeValid; + } + return NOERROR; +} + + +STDMETHODIMP +CMediaSample::IsSyncPoint(void) +{ + if (m_dwFlags & Sample_SyncPoint) { + return S_OK; + } else { + return S_FALSE; + } +} + + +STDMETHODIMP +CMediaSample::SetSyncPoint(BOOL bIsSyncPoint) +{ + if (bIsSyncPoint) { + m_dwFlags |= Sample_SyncPoint; + } else { + m_dwFlags &= ~Sample_SyncPoint; + } + return NOERROR; +} + +// returns S_OK if there is a discontinuity in the data (this same is +// not a continuation of the previous stream of data +// - there has been a seek). +STDMETHODIMP +CMediaSample::IsDiscontinuity(void) +{ + if (m_dwFlags & Sample_Discontinuity) { + return S_OK; + } else { + return S_FALSE; + } +} + +// set the discontinuity property - TRUE if this sample is not a +// continuation, but a new sample after a seek. +STDMETHODIMP +CMediaSample::SetDiscontinuity(BOOL bDiscont) +{ + // should be TRUE or FALSE + if (bDiscont) { + m_dwFlags |= Sample_Discontinuity; + } else { + m_dwFlags &= ~Sample_Discontinuity; + } + return S_OK; +} + +STDMETHODIMP +CMediaSample::IsPreroll(void) +{ + if (m_dwFlags & Sample_Preroll) { + return S_OK; + } else { + return S_FALSE; + } +} + + +STDMETHODIMP +CMediaSample::SetPreroll(BOOL bIsPreroll) +{ + if (bIsPreroll) { + m_dwFlags |= Sample_Preroll; + } else { + m_dwFlags &= ~Sample_Preroll; + } + return NOERROR; +} + +STDMETHODIMP_(LONG) +CMediaSample::GetActualDataLength(void) +{ + return m_lActual; +} + + +STDMETHODIMP +CMediaSample::SetActualDataLength(LONG lActual) +{ + if (lActual > m_cbBuffer || lActual < 0) { + ASSERT(lActual <= GetSize()); + return VFW_E_BUFFER_OVERFLOW; + } + m_lActual = lActual; + return NOERROR; +} + + +/* These allow for limited format changes in band */ + +STDMETHODIMP +CMediaSample::GetMediaType(__deref_out AM_MEDIA_TYPE **ppMediaType) +{ + ValidateReadWritePtr(ppMediaType,sizeof(AM_MEDIA_TYPE *)); + ASSERT(ppMediaType); + + /* Do we have a new media type for them */ + + if (!(m_dwFlags & Sample_TypeChanged)) { + ASSERT(m_pMediaType == NULL); + *ppMediaType = NULL; + return S_FALSE; + } + + ASSERT(m_pMediaType); + + /* Create a copy of our media type */ + + *ppMediaType = CreateMediaType(m_pMediaType); + if (*ppMediaType == NULL) { + return E_OUTOFMEMORY; + } + return NOERROR; +} + + +/* Mark this sample as having a different format type */ + +STDMETHODIMP +CMediaSample::SetMediaType(__in_opt AM_MEDIA_TYPE *pMediaType) +{ + /* Delete the current media type */ + + if (m_pMediaType) { + DeleteMediaType(m_pMediaType); + m_pMediaType = NULL; + } + + /* Mechanism for resetting the format type */ + + if (pMediaType == NULL) { + m_dwFlags &= ~Sample_TypeChanged; + return NOERROR; + } + + ASSERT(pMediaType); + ValidateReadPtr(pMediaType,sizeof(AM_MEDIA_TYPE)); + + /* Take a copy of the media type */ + + m_pMediaType = CreateMediaType(pMediaType); + if (m_pMediaType == NULL) { + m_dwFlags &= ~Sample_TypeChanged; + return E_OUTOFMEMORY; + } + + m_dwFlags |= Sample_TypeChanged; + return NOERROR; +} + +// Set and get properties (IMediaSample2) +STDMETHODIMP CMediaSample::GetProperties( + DWORD cbProperties, + __out_bcount(cbProperties) BYTE * pbProperties +) +{ + if (0 != cbProperties) { + CheckPointer(pbProperties, E_POINTER); + // Return generic stuff up to the length + AM_SAMPLE2_PROPERTIES Props; + Props.cbData = min(cbProperties, sizeof(Props)); + Props.dwSampleFlags = m_dwFlags & ~Sample_MediaTimeValid; + Props.dwTypeSpecificFlags = m_dwTypeSpecificFlags; + Props.pbBuffer = m_pBuffer; + Props.cbBuffer = m_cbBuffer; + Props.lActual = m_lActual; + Props.tStart = m_Start; + Props.tStop = m_End; + Props.dwStreamId = m_dwStreamId; + if (m_dwFlags & AM_SAMPLE_TYPECHANGED) { + Props.pMediaType = m_pMediaType; + } else { + Props.pMediaType = NULL; + } + CopyMemory(pbProperties, &Props, Props.cbData); + } + return S_OK; +} + +#define CONTAINS_FIELD(type, field, offset) \ + ((FIELD_OFFSET(type, field) + sizeof(((type *)0)->field)) <= offset) + +HRESULT CMediaSample::SetProperties( + DWORD cbProperties, + __in_bcount(cbProperties) const BYTE * pbProperties +) +{ + + /* Generic properties */ + AM_MEDIA_TYPE *pMediaType = NULL; + + if (CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, cbData, cbProperties)) { + CheckPointer(pbProperties, E_POINTER); + AM_SAMPLE2_PROPERTIES *pProps = + (AM_SAMPLE2_PROPERTIES *)pbProperties; + + /* Don't use more data than is actually there */ + if (pProps->cbData < cbProperties) { + cbProperties = pProps->cbData; + } + /* We only handle IMediaSample2 */ + if (cbProperties > sizeof(*pProps) || + pProps->cbData > sizeof(*pProps)) { + return E_INVALIDARG; + } + /* Do checks first, the assignments (for backout) */ + if (CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, dwSampleFlags, cbProperties)) { + /* Check the flags */ + if (pProps->dwSampleFlags & + (~Sample_ValidFlags | Sample_MediaTimeValid)) { + return E_INVALIDARG; + } + /* Check a flag isn't being set for a property + not being provided + */ + if ((pProps->dwSampleFlags & AM_SAMPLE_TIMEVALID) && + !(m_dwFlags & AM_SAMPLE_TIMEVALID) && + !CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, tStop, cbProperties)) { + return E_INVALIDARG; + } + } + /* NB - can't SET the pointer or size */ + if (CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, pbBuffer, cbProperties)) { + + /* Check pbBuffer */ + if (pProps->pbBuffer != 0 && pProps->pbBuffer != m_pBuffer) { + return E_INVALIDARG; + } + } + if (CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, cbBuffer, cbProperties)) { + + /* Check cbBuffer */ + if (pProps->cbBuffer != 0 && pProps->cbBuffer != m_cbBuffer) { + return E_INVALIDARG; + } + } + if (CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, cbBuffer, cbProperties) && + CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, lActual, cbProperties)) { + + /* Check lActual */ + if (pProps->cbBuffer < pProps->lActual) { + return E_INVALIDARG; + } + } + + if (CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, pMediaType, cbProperties)) { + + /* Check pMediaType */ + if (pProps->dwSampleFlags & AM_SAMPLE_TYPECHANGED) { + CheckPointer(pProps->pMediaType, E_POINTER); + pMediaType = CreateMediaType(pProps->pMediaType); + if (pMediaType == NULL) { + return E_OUTOFMEMORY; + } + } + } + + /* Now do the assignments */ + if (CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, dwStreamId, cbProperties)) { + m_dwStreamId = pProps->dwStreamId; + } + if (CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, dwSampleFlags, cbProperties)) { + /* Set the flags */ + m_dwFlags = pProps->dwSampleFlags | + (m_dwFlags & Sample_MediaTimeValid); + m_dwTypeSpecificFlags = pProps->dwTypeSpecificFlags; + } else { + if (CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, dwTypeSpecificFlags, cbProperties)) { + m_dwTypeSpecificFlags = pProps->dwTypeSpecificFlags; + } + } + + if (CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, lActual, cbProperties)) { + /* Set lActual */ + m_lActual = pProps->lActual; + } + + if (CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, tStop, cbProperties)) { + + /* Set the times */ + m_End = pProps->tStop; + } + if (CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, tStart, cbProperties)) { + + /* Set the times */ + m_Start = pProps->tStart; + } + + if (CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, pMediaType, cbProperties)) { + /* Set pMediaType */ + if (pProps->dwSampleFlags & AM_SAMPLE_TYPECHANGED) { + if (m_pMediaType != NULL) { + DeleteMediaType(m_pMediaType); + } + m_pMediaType = pMediaType; + } + } + + /* Fix up the type changed flag to correctly reflect the current state + If, for instance the input contained no type change but the + output does then if we don't do this we'd lose the + output media type. + */ + if (m_pMediaType) { + m_dwFlags |= Sample_TypeChanged; + } else { + m_dwFlags &= ~Sample_TypeChanged; + } + } + + return S_OK; +} + + +// +// The streaming thread calls IPin::NewSegment(), IPin::EndOfStream(), +// IMemInputPin::Receive() and IMemInputPin::ReceiveMultiple() on the +// connected input pin. The application thread calls Block(). The +// following class members can only be called by the streaming thread. +// +// Deliver() +// DeliverNewSegment() +// StartUsingOutputPin() +// StopUsingOutputPin() +// ChangeOutputFormat() +// ChangeMediaType() +// DynamicReconnect() +// +// The following class members can only be called by the application thread. +// +// Block() +// SynchronousBlockOutputPin() +// AsynchronousBlockOutputPin() +// + +CDynamicOutputPin::CDynamicOutputPin( + __in_opt LPCTSTR pObjectName, + __in CBaseFilter *pFilter, + __in CCritSec *pLock, + __inout HRESULT *phr, + __in_opt LPCWSTR pName) : + CBaseOutputPin(pObjectName, pFilter, pLock, phr, pName), + m_hStopEvent(NULL), + m_pGraphConfig(NULL), + m_bPinUsesReadOnlyAllocator(FALSE), + m_BlockState(NOT_BLOCKED), + m_hUnblockOutputPinEvent(NULL), + m_hNotifyCallerPinBlockedEvent(NULL), + m_dwBlockCallerThreadID(0), + m_dwNumOutstandingOutputPinUsers(0) +{ + HRESULT hr = Initialize(); + if( FAILED( hr ) ) { + *phr = hr; + return; + } +} + +#ifdef UNICODE +CDynamicOutputPin::CDynamicOutputPin( + __in_opt LPCSTR pObjectName, + __in CBaseFilter *pFilter, + __in CCritSec *pLock, + __inout HRESULT *phr, + __in_opt LPCWSTR pName) : + CBaseOutputPin(pObjectName, pFilter, pLock, phr, pName), + m_hStopEvent(NULL), + m_pGraphConfig(NULL), + m_bPinUsesReadOnlyAllocator(FALSE), + m_BlockState(NOT_BLOCKED), + m_hUnblockOutputPinEvent(NULL), + m_hNotifyCallerPinBlockedEvent(NULL), + m_dwBlockCallerThreadID(0), + m_dwNumOutstandingOutputPinUsers(0) +{ + HRESULT hr = Initialize(); + if( FAILED( hr ) ) { + *phr = hr; + return; + } +} +#endif + +CDynamicOutputPin::~CDynamicOutputPin() +{ + if(NULL != m_hUnblockOutputPinEvent) { + // This call should not fail because we have access to m_hUnblockOutputPinEvent + // and m_hUnblockOutputPinEvent is a valid event. + EXECUTE_ASSERT(::CloseHandle(m_hUnblockOutputPinEvent)); + } + + if(NULL != m_hNotifyCallerPinBlockedEvent) { + // This call should not fail because we have access to m_hNotifyCallerPinBlockedEvent + // and m_hNotifyCallerPinBlockedEvent is a valid event. + EXECUTE_ASSERT(::CloseHandle(m_hNotifyCallerPinBlockedEvent)); + } +} + +HRESULT CDynamicOutputPin::Initialize(void) +{ + m_hUnblockOutputPinEvent = ::CreateEvent( NULL, // The event will have the default security descriptor. + TRUE, // This is a manual reset event. + TRUE, // The event is initially signaled. + NULL ); // The event is not named. + + // CreateEvent() returns NULL if an error occurs. + if(NULL == m_hUnblockOutputPinEvent) { + return AmGetLastErrorToHResult(); + } + + // Set flag to say we can reconnect while streaming. + SetReconnectWhenActive(true); + + return S_OK; +} + +STDMETHODIMP CDynamicOutputPin::NonDelegatingQueryInterface(REFIID riid, __deref_out void **ppv) +{ + if(riid == IID_IPinFlowControl) { + return GetInterface(static_cast(this), ppv); + } else { + return CBaseOutputPin::NonDelegatingQueryInterface(riid, ppv); + } +} + +STDMETHODIMP CDynamicOutputPin::Disconnect(void) +{ + CAutoLock cObjectLock(m_pLock); + return DisconnectInternal(); +} + +STDMETHODIMP CDynamicOutputPin::Block(DWORD dwBlockFlags, HANDLE hEvent) +{ + const DWORD VALID_FLAGS = AM_PIN_FLOW_CONTROL_BLOCK; + + // Check for illegal flags. + if(dwBlockFlags & ~VALID_FLAGS) { + return E_INVALIDARG; + } + + // Make sure the event is unsignaled. + if((dwBlockFlags & AM_PIN_FLOW_CONTROL_BLOCK) && (NULL != hEvent)) { + if( !::ResetEvent( hEvent ) ) { + return AmGetLastErrorToHResult(); + } + } + + // No flags are set if we are unblocking the output pin. + if(0 == dwBlockFlags) { + + // This parameter should be NULL because unblock operations are always synchronous. + // There is no need to notify the caller when the event is done. + if(NULL != hEvent) { + return E_INVALIDARG; + } + } + + #ifdef DEBUG + AssertValid(); + #endif // DEBUG + + HRESULT hr; + + if(dwBlockFlags & AM_PIN_FLOW_CONTROL_BLOCK) { + // IPinFlowControl::Block()'s hEvent parameter is NULL if the block is synchronous. + // If hEvent is not NULL, the block is asynchronous. + if(NULL == hEvent) { + hr = SynchronousBlockOutputPin(); + } else { + hr = AsynchronousBlockOutputPin(hEvent); + } + } else { + hr = UnblockOutputPin(); + } + + #ifdef DEBUG + AssertValid(); + #endif // DEBUG + + if(FAILED(hr)) { + return hr; + } + + return S_OK; +} + +HRESULT CDynamicOutputPin::SynchronousBlockOutputPin(void) +{ + HANDLE hNotifyCallerPinBlockedEvent = :: CreateEvent( NULL, // The event will have the default security attributes. + FALSE, // This is an automatic reset event. + FALSE, // The event is initially unsignaled. + NULL ); // The event is not named. + + // CreateEvent() returns NULL if an error occurs. + if(NULL == hNotifyCallerPinBlockedEvent) { + return AmGetLastErrorToHResult(); + } + + HRESULT hr = AsynchronousBlockOutputPin(hNotifyCallerPinBlockedEvent); + if(FAILED(hr)) { + // This call should not fail because we have access to hNotifyCallerPinBlockedEvent + // and hNotifyCallerPinBlockedEvent is a valid event. + EXECUTE_ASSERT(::CloseHandle(hNotifyCallerPinBlockedEvent)); + + return hr; + } + + hr = WaitEvent(hNotifyCallerPinBlockedEvent); + + // This call should not fail because we have access to hNotifyCallerPinBlockedEvent + // and hNotifyCallerPinBlockedEvent is a valid event. + EXECUTE_ASSERT(::CloseHandle(hNotifyCallerPinBlockedEvent)); + + if(FAILED(hr)) { + return hr; + } + + return S_OK; +} + +HRESULT CDynamicOutputPin::AsynchronousBlockOutputPin(HANDLE hNotifyCallerPinBlockedEvent) +{ + // This function holds the m_BlockStateLock because it uses + // m_dwBlockCallerThreadID, m_BlockState and + // m_hNotifyCallerPinBlockedEvent. + CAutoLock alBlockStateLock(&m_BlockStateLock); + + if(NOT_BLOCKED != m_BlockState) { + if(m_dwBlockCallerThreadID == ::GetCurrentThreadId()) { + return VFW_E_PIN_ALREADY_BLOCKED_ON_THIS_THREAD; + } else { + return VFW_E_PIN_ALREADY_BLOCKED; + } + } + + BOOL fSuccess = ::DuplicateHandle( ::GetCurrentProcess(), + hNotifyCallerPinBlockedEvent, + ::GetCurrentProcess(), + &m_hNotifyCallerPinBlockedEvent, + EVENT_MODIFY_STATE, + FALSE, + 0 ); + if( !fSuccess ) { + return AmGetLastErrorToHResult(); + } + + m_BlockState = PENDING; + m_dwBlockCallerThreadID = ::GetCurrentThreadId(); + + // The output pin cannot be blocked if the streaming thread is + // calling IPin::NewSegment(), IPin::EndOfStream(), IMemInputPin::Receive() + // or IMemInputPin::ReceiveMultiple() on the connected input pin. Also, it + // cannot be blocked if the streaming thread is calling DynamicReconnect(), + // ChangeMediaType() or ChangeOutputFormat(). + if(!StreamingThreadUsingOutputPin()) { + + // The output pin can be immediately blocked. + BlockOutputPin(); + } + + return S_OK; +} + +void CDynamicOutputPin::BlockOutputPin(void) +{ + // The caller should always hold the m_BlockStateLock because this function + // uses m_BlockState and m_hNotifyCallerPinBlockedEvent. + ASSERT(CritCheckIn(&m_BlockStateLock)); + + // This function should not be called if the streaming thread is modifying + // the connection state or it's passing data downstream. + ASSERT(!StreamingThreadUsingOutputPin()); + + // This should not fail because we successfully created the event + // and we have the security permissions to change it's state. + EXECUTE_ASSERT(::ResetEvent(m_hUnblockOutputPinEvent)); + + // This event should not fail because AsynchronousBlockOutputPin() successfully + // duplicated this handle and we have the appropriate security permissions. + EXECUTE_ASSERT(::SetEvent(m_hNotifyCallerPinBlockedEvent)); + EXECUTE_ASSERT(::CloseHandle(m_hNotifyCallerPinBlockedEvent)); + + m_BlockState = BLOCKED; + m_hNotifyCallerPinBlockedEvent = NULL; +} + +HRESULT CDynamicOutputPin::UnblockOutputPin(void) +{ + // UnblockOutputPin() holds the m_BlockStateLock because it + // uses m_BlockState, m_dwBlockCallerThreadID and + // m_hNotifyCallerPinBlockedEvent. + CAutoLock alBlockStateLock(&m_BlockStateLock); + + if(NOT_BLOCKED == m_BlockState) { + return S_FALSE; + } + + // This should not fail because we successfully created the event + // and we have the security permissions to change it's state. + EXECUTE_ASSERT(::SetEvent(m_hUnblockOutputPinEvent)); + + // Cancel the block operation if it's still pending. + if(NULL != m_hNotifyCallerPinBlockedEvent) { + // This event should not fail because AsynchronousBlockOutputPin() successfully + // duplicated this handle and we have the appropriate security permissions. + EXECUTE_ASSERT(::SetEvent(m_hNotifyCallerPinBlockedEvent)); + EXECUTE_ASSERT(::CloseHandle(m_hNotifyCallerPinBlockedEvent)); + } + + m_BlockState = NOT_BLOCKED; + m_dwBlockCallerThreadID = 0; + m_hNotifyCallerPinBlockedEvent = NULL; + + return S_OK; +} + +HRESULT CDynamicOutputPin::StartUsingOutputPin(void) +{ + // The caller should not hold m_BlockStateLock. If the caller does, + // a deadlock could occur. + ASSERT(CritCheckOut(&m_BlockStateLock)); + + CAutoLock alBlockStateLock(&m_BlockStateLock); + + #ifdef DEBUG + AssertValid(); + #endif // DEBUG + + // Are we in the middle of a block operation? + while(BLOCKED == m_BlockState) { + m_BlockStateLock.Unlock(); + + // If this ASSERT fires, a deadlock could occur. The caller should make sure + // that this thread never acquires the Block State lock more than once. + ASSERT(CritCheckOut( &m_BlockStateLock )); + + // WaitForMultipleObjects() returns WAIT_OBJECT_0 if the unblock event + // is fired. It returns WAIT_OBJECT_0 + 1 if the stop event if fired. + // See the Windows SDK documentation for more information on + // WaitForMultipleObjects(). + const DWORD UNBLOCK = WAIT_OBJECT_0; + const DWORD STOP = WAIT_OBJECT_0 + 1; + + HANDLE ahWaitEvents[] = { m_hUnblockOutputPinEvent, m_hStopEvent }; + DWORD dwNumWaitEvents = sizeof(ahWaitEvents)/sizeof(HANDLE); + + DWORD dwReturnValue = ::WaitForMultipleObjects( dwNumWaitEvents, ahWaitEvents, FALSE, INFINITE ); + + m_BlockStateLock.Lock(); + + #ifdef DEBUG + AssertValid(); + #endif // DEBUG + + switch( dwReturnValue ) { + case UNBLOCK: + break; + + case STOP: + return VFW_E_STATE_CHANGED; + + case WAIT_FAILED: + return AmGetLastErrorToHResult(); + + default: + DbgBreak( "An Unexpected case occured in CDynamicOutputPin::StartUsingOutputPin()." ); + return E_UNEXPECTED; + } + } + + m_dwNumOutstandingOutputPinUsers++; + + #ifdef DEBUG + AssertValid(); + #endif // DEBUG + + return S_OK; +} + +void CDynamicOutputPin::StopUsingOutputPin(void) +{ + CAutoLock alBlockStateLock(&m_BlockStateLock); + + #ifdef DEBUG + AssertValid(); + #endif // DEBUG + + m_dwNumOutstandingOutputPinUsers--; + + if((m_dwNumOutstandingOutputPinUsers == 0) && (NOT_BLOCKED != m_BlockState)) { + BlockOutputPin(); + } + + #ifdef DEBUG + AssertValid(); + #endif // DEBUG +} + +bool CDynamicOutputPin::StreamingThreadUsingOutputPin(void) +{ + CAutoLock alBlockStateLock(&m_BlockStateLock); + + return (m_dwNumOutstandingOutputPinUsers > 0); +} + +void CDynamicOutputPin::SetConfigInfo(IGraphConfig *pGraphConfig, HANDLE hStopEvent) +{ + // This pointer is not addrefed because filters are not allowed to + // hold references to the filter graph manager. See the documentation for + // IBaseFilter::JoinFilterGraph() in the Direct Show SDK for more information. + m_pGraphConfig = pGraphConfig; + + m_hStopEvent = hStopEvent; +} + +HRESULT CDynamicOutputPin::Active(void) +{ + // Make sure the user initialized the object by calling SetConfigInfo(). + if((NULL == m_hStopEvent) || (NULL == m_pGraphConfig)) { + DbgBreak( ERROR: CDynamicOutputPin::Active() failed because m_pGraphConfig and m_hStopEvent were not initialized. Call SetConfigInfo() to initialize them. ); + return E_FAIL; + } + + // If this ASSERT fires, the user may have passed an invalid event handle to SetConfigInfo(). + // The ASSERT can also fire if the event if destroyed and then Active() is called. An event + // handle is invalid if 1) the event does not exist or the user does not have the security + // permissions to use the event. + EXECUTE_ASSERT(ResetEvent(m_hStopEvent)); + + return CBaseOutputPin::Active(); +} + +HRESULT CDynamicOutputPin::Inactive(void) +{ + // If this ASSERT fires, the user may have passed an invalid event handle to SetConfigInfo(). + // The ASSERT can also fire if the event if destroyed and then Active() is called. An event + // handle is invalid if 1) the event does not exist or the user does not have the security + // permissions to use the event. + EXECUTE_ASSERT(SetEvent(m_hStopEvent)); + + return CBaseOutputPin::Inactive(); +} + +HRESULT CDynamicOutputPin::DeliverBeginFlush(void) +{ + // If this ASSERT fires, the user may have passed an invalid event handle to SetConfigInfo(). + // The ASSERT can also fire if the event if destroyed and then DeliverBeginFlush() is called. + // An event handle is invalid if 1) the event does not exist or the user does not have the security + // permissions to use the event. + EXECUTE_ASSERT(SetEvent(m_hStopEvent)); + + return CBaseOutputPin::DeliverBeginFlush(); +} + +HRESULT CDynamicOutputPin::DeliverEndFlush(void) +{ + // If this ASSERT fires, the user may have passed an invalid event handle to SetConfigInfo(). + // The ASSERT can also fire if the event if destroyed and then DeliverBeginFlush() is called. + // An event handle is invalid if 1) the event does not exist or the user does not have the security + // permissions to use the event. + EXECUTE_ASSERT(ResetEvent(m_hStopEvent)); + + return CBaseOutputPin::DeliverEndFlush(); +} + + +// ChangeOutputFormat() either dynamicly changes the connection's format type or it dynamicly +// reconnects the output pin. +HRESULT CDynamicOutputPin::ChangeOutputFormat + ( + const AM_MEDIA_TYPE *pmt, + REFERENCE_TIME tSegmentStart, + REFERENCE_TIME tSegmentStop, + double dSegmentRate + ) +{ + // The caller should call StartUsingOutputPin() before calling this + // method. + ASSERT(StreamingThreadUsingOutputPin()); + + // Callers should always pass a valid media type to ChangeOutputFormat() . + ASSERT(NULL != pmt); + + CMediaType cmt(*pmt); + HRESULT hr = ChangeMediaType(&cmt); + if (FAILED(hr)) { + return hr; + } + + hr = DeliverNewSegment(tSegmentStart, tSegmentStop, dSegmentRate); + if( FAILED( hr ) ) { + return hr; + } + + return S_OK; +} + +HRESULT CDynamicOutputPin::ChangeMediaType(const CMediaType *pmt) +{ + // The caller should call StartUsingOutputPin() before calling this + // method. + ASSERT(StreamingThreadUsingOutputPin()); + + // This function assumes the filter graph is running. + ASSERT(!IsStopped()); + + if(!IsConnected()) { + return VFW_E_NOT_CONNECTED; + } + + /* First check if the downstream pin will accept a dynamic + format change + */ + QzCComPtr pConnection; + + m_Connected->QueryInterface(IID_IPinConnection, (void **)&pConnection); + if(pConnection != NULL) { + + if(S_OK == pConnection->DynamicQueryAccept(pmt)) { + + HRESULT hr = ChangeMediaTypeHelper(pmt); + if(FAILED(hr)) { + return hr; + } + + return S_OK; + } + } + + /* Can't do the dynamic connection */ + return DynamicReconnect(pmt); +} + +HRESULT CDynamicOutputPin::ChangeMediaTypeHelper(const CMediaType *pmt) +{ + // The caller should call StartUsingOutputPin() before calling this + // method. + ASSERT(StreamingThreadUsingOutputPin()); + + HRESULT hr = m_Connected->ReceiveConnection(this, pmt); + if(FAILED(hr)) { + return hr; + } + + hr = SetMediaType(pmt); + if(FAILED(hr)) { + return hr; + } + + // Does this pin use the local memory transport? + if(NULL != m_pInputPin) { + // This function assumes that m_pInputPin and m_Connected are + // two different interfaces to the same object. + ASSERT(::IsEqualObject(m_Connected, m_pInputPin)); + + ALLOCATOR_PROPERTIES apInputPinRequirements; + apInputPinRequirements.cbAlign = 0; + apInputPinRequirements.cbBuffer = 0; + apInputPinRequirements.cbPrefix = 0; + apInputPinRequirements.cBuffers = 0; + + m_pInputPin->GetAllocatorRequirements(&apInputPinRequirements); + + // A zero allignment does not make any sense. + if(0 == apInputPinRequirements.cbAlign) { + apInputPinRequirements.cbAlign = 1; + } + + hr = m_pAllocator->Decommit(); + if(FAILED(hr)) { + return hr; + } + + hr = DecideBufferSize(m_pAllocator, &apInputPinRequirements); + if(FAILED(hr)) { + return hr; + } + + hr = m_pAllocator->Commit(); + if(FAILED(hr)) { + return hr; + } + + hr = m_pInputPin->NotifyAllocator(m_pAllocator, m_bPinUsesReadOnlyAllocator); + if(FAILED(hr)) { + return hr; + } + } + + return S_OK; +} + +// this method has to be called from the thread that is pushing data, +// and it's the caller's responsibility to make sure that the thread +// has no outstand samples because they cannot be delivered after a +// reconnect +// +HRESULT CDynamicOutputPin::DynamicReconnect( const CMediaType* pmt ) +{ + // The caller should call StartUsingOutputPin() before calling this + // method. + ASSERT(StreamingThreadUsingOutputPin()); + + if((m_pGraphConfig == NULL) || (NULL == m_hStopEvent)) { + return E_FAIL; + } + + HRESULT hr = m_pGraphConfig->Reconnect( + this, + NULL, + pmt, + NULL, + m_hStopEvent, + AM_GRAPH_CONFIG_RECONNECT_CACHE_REMOVED_FILTERS ); + + return hr; +} + +HRESULT CDynamicOutputPin::CompleteConnect(IPin *pReceivePin) +{ + HRESULT hr = CBaseOutputPin::CompleteConnect(pReceivePin); + if(SUCCEEDED(hr)) { + if(!IsStopped() && m_pAllocator) { + hr = m_pAllocator->Commit(); + ASSERT(hr != VFW_E_ALREADY_COMMITTED); + } + } + + return hr; +} + +#ifdef DEBUG +void CDynamicOutputPin::AssertValid(void) +{ + // Make sure the object was correctly initialized. + + // This ASSERT only fires if the object failed to initialize + // and the user ignored the constructor's return code (phr). + ASSERT(NULL != m_hUnblockOutputPinEvent); + + // If either of these ASSERTs fire, the user did not correctly call + // SetConfigInfo(). + ASSERT(NULL != m_hStopEvent); + ASSERT(NULL != m_pGraphConfig); + + // Make sure the block state is consistent. + + CAutoLock alBlockStateLock(&m_BlockStateLock); + + // BLOCK_STATE variables only have three legal values: PENDING, BLOCKED and NOT_BLOCKED. + ASSERT((NOT_BLOCKED == m_BlockState) || (PENDING == m_BlockState) || (BLOCKED == m_BlockState)); + + // m_hNotifyCallerPinBlockedEvent is only needed when a block operation cannot complete + // immediately. + ASSERT(((NULL == m_hNotifyCallerPinBlockedEvent) && (PENDING != m_BlockState)) || + ((NULL != m_hNotifyCallerPinBlockedEvent) && (PENDING == m_BlockState)) ); + + // m_dwBlockCallerThreadID should always be 0 if the pin is not blocked and + // the user is not trying to block the pin. + ASSERT((0 == m_dwBlockCallerThreadID) || (NOT_BLOCKED != m_BlockState)); + + // If this ASSERT fires, the streaming thread is using the output pin and the + // output pin is blocked. + ASSERT(((0 != m_dwNumOutstandingOutputPinUsers) && (BLOCKED != m_BlockState)) || + ((0 == m_dwNumOutstandingOutputPinUsers) && (NOT_BLOCKED != m_BlockState)) || + ((0 == m_dwNumOutstandingOutputPinUsers) && (NOT_BLOCKED == m_BlockState)) ); +} +#endif // DEBUG + +HRESULT CDynamicOutputPin::WaitEvent(HANDLE hEvent) +{ + const DWORD EVENT_SIGNALED = WAIT_OBJECT_0; + + DWORD dwReturnValue = ::WaitForSingleObject(hEvent, INFINITE); + + switch( dwReturnValue ) { + case EVENT_SIGNALED: + return S_OK; + + case WAIT_FAILED: + return AmGetLastErrorToHResult(); + + default: + DbgBreak( "An Unexpected case occured in CDynamicOutputPin::WaitEvent()." ); + return E_UNEXPECTED; + } +} + +//===================================================================== +//===================================================================== +// Implements CBaseAllocator +//===================================================================== +//===================================================================== + + +/* Constructor overrides the default settings for the free list to request + that it be alertable (ie the list can be cast to a handle which can be + passed to WaitForSingleObject). Both of the allocator lists also ask for + object locking, the all list matches the object default settings but I + have included them here just so it is obvious what kind of list it is */ + +CBaseAllocator::CBaseAllocator(__in_opt LPCTSTR pName, + __inout_opt LPUNKNOWN pUnk, + __inout HRESULT *phr, + BOOL bEvent, + BOOL fEnableReleaseCallback + ) : + CUnknown(pName, pUnk), + m_lAllocated(0), + m_bChanged(FALSE), + m_bCommitted(FALSE), + m_bDecommitInProgress(FALSE), + m_lSize(0), + m_lCount(0), + m_lAlignment(0), + m_lPrefix(0), + m_hSem(NULL), + m_lWaiting(0), + m_fEnableReleaseCallback(fEnableReleaseCallback), + m_pNotify(NULL) +{ +#ifdef DXMPERF + PERFLOG_CTOR( pName ? pName : L"CBaseAllocator", (IMemAllocator *) this ); +#endif // DXMPERF + + if (bEvent) { + m_hSem = CreateSemaphore(NULL, 0, 0x7FFFFFFF, NULL); + if (m_hSem == NULL) { + *phr = E_OUTOFMEMORY; + return; + } + } +} + +#ifdef UNICODE +CBaseAllocator::CBaseAllocator(__in_opt LPCSTR pName, + __inout_opt LPUNKNOWN pUnk, + __inout HRESULT *phr, + BOOL bEvent, + BOOL fEnableReleaseCallback) : + CUnknown(pName, pUnk), + m_lAllocated(0), + m_bChanged(FALSE), + m_bCommitted(FALSE), + m_bDecommitInProgress(FALSE), + m_lSize(0), + m_lCount(0), + m_lAlignment(0), + m_lPrefix(0), + m_hSem(NULL), + m_lWaiting(0), + m_fEnableReleaseCallback(fEnableReleaseCallback), + m_pNotify(NULL) +{ +#ifdef DXMPERF + PERFLOG_CTOR( L"CBaseAllocator", (IMemAllocator *) this ); +#endif // DXMPERF + + if (bEvent) { + m_hSem = CreateSemaphore(NULL, 0, 0x7FFFFFFF, NULL); + if (m_hSem == NULL) { + *phr = E_OUTOFMEMORY; + return; + } + } +} +#endif + +/* Destructor */ + +CBaseAllocator::~CBaseAllocator() +{ + // we can't call Decommit here since that would mean a call to a + // pure virtual in destructor. + // We must assume that the derived class has gone into decommit state in + // its destructor. +#ifdef DXMPERF + PERFLOG_DTOR( L"CBaseAllocator", (IMemAllocator *) this ); +#endif // DXMPERF + + ASSERT(!m_bCommitted); + if (m_hSem != NULL) { + EXECUTE_ASSERT(CloseHandle(m_hSem)); + } + if (m_pNotify) { + m_pNotify->Release(); + } +} + + +/* Override this to publicise our interfaces */ + +STDMETHODIMP +CBaseAllocator::NonDelegatingQueryInterface(REFIID riid, __deref_out void **ppv) +{ + /* Do we know about this interface */ + + if (riid == IID_IMemAllocator || + riid == IID_IMemAllocatorCallbackTemp && m_fEnableReleaseCallback) { + return GetInterface((IMemAllocatorCallbackTemp *) this, ppv); + } else { + return CUnknown::NonDelegatingQueryInterface(riid, ppv); + } +} + + +/* This sets the size and count of the required samples. The memory isn't + actually allocated until Commit() is called, if memory has already been + allocated then assuming no samples are outstanding the user may call us + to change the buffering, the memory will be released in Commit() */ + +STDMETHODIMP +CBaseAllocator::SetProperties( + __in ALLOCATOR_PROPERTIES* pRequest, + __out ALLOCATOR_PROPERTIES* pActual) +{ + CheckPointer(pRequest, E_POINTER); + CheckPointer(pActual, E_POINTER); + ValidateReadWritePtr(pActual, sizeof(ALLOCATOR_PROPERTIES)); + CAutoLock cObjectLock(this); + + ZeroMemory(pActual, sizeof(ALLOCATOR_PROPERTIES)); + + ASSERT(pRequest->cbBuffer > 0); + + /* Check the alignment requested */ + if (pRequest->cbAlign != 1) { + DbgLog((LOG_ERROR, 2, TEXT("Alignment requested was 0x%x, not 1"), + pRequest->cbAlign)); + return VFW_E_BADALIGN; + } + + /* Can't do this if already committed, there is an argument that says we + should not reject the SetProperties call if there are buffers still + active. However this is called by the source filter, which is the same + person who is holding the samples. Therefore it is not unreasonable + for them to free all their samples before changing the requirements */ + + if (m_bCommitted) { + return VFW_E_ALREADY_COMMITTED; + } + + /* Must be no outstanding buffers */ + + if (m_lAllocated != m_lFree.GetCount()) { + return VFW_E_BUFFERS_OUTSTANDING; + } + + /* There isn't any real need to check the parameters as they + will just be rejected when the user finally calls Commit */ + + pActual->cbBuffer = m_lSize = pRequest->cbBuffer; + pActual->cBuffers = m_lCount = pRequest->cBuffers; + pActual->cbAlign = m_lAlignment = pRequest->cbAlign; + pActual->cbPrefix = m_lPrefix = pRequest->cbPrefix; + + m_bChanged = TRUE; + return NOERROR; +} + +STDMETHODIMP +CBaseAllocator::GetProperties( + __out ALLOCATOR_PROPERTIES * pActual) +{ + CheckPointer(pActual,E_POINTER); + ValidateReadWritePtr(pActual,sizeof(ALLOCATOR_PROPERTIES)); + + CAutoLock cObjectLock(this); + pActual->cbBuffer = m_lSize; + pActual->cBuffers = m_lCount; + pActual->cbAlign = m_lAlignment; + pActual->cbPrefix = m_lPrefix; + return NOERROR; +} + +// get container for a sample. Blocking, synchronous call to get the +// next free buffer (as represented by an IMediaSample interface). +// on return, the time etc properties will be invalid, but the buffer +// pointer and size will be correct. + +HRESULT CBaseAllocator::GetBuffer(__deref_out IMediaSample **ppBuffer, + __in_opt REFERENCE_TIME *pStartTime, + __in_opt REFERENCE_TIME *pEndTime, + DWORD dwFlags + ) +{ + UNREFERENCED_PARAMETER(pStartTime); + UNREFERENCED_PARAMETER(pEndTime); + UNREFERENCED_PARAMETER(dwFlags); + CMediaSample *pSample; + + *ppBuffer = NULL; + for (;;) + { + { // scope for lock + CAutoLock cObjectLock(this); + + /* Check we are committed */ + if (!m_bCommitted) { + return VFW_E_NOT_COMMITTED; + } + pSample = (CMediaSample *) m_lFree.RemoveHead(); + if (pSample == NULL) { + SetWaiting(); + } + } + + /* If we didn't get a sample then wait for the list to signal */ + + if (pSample) { + break; + } + if (dwFlags & AM_GBF_NOWAIT) { + return VFW_E_TIMEOUT; + } + ASSERT(m_hSem != NULL); + WaitForSingleObject(m_hSem, INFINITE); + } + + /* Addref the buffer up to one. On release + back to zero instead of being deleted, it will requeue itself by + calling the ReleaseBuffer member function. NOTE the owner of a + media sample must always be derived from CBaseAllocator */ + + + ASSERT(pSample->m_cRef == 0); + pSample->m_cRef = 1; + *ppBuffer = pSample; + +#ifdef DXMPERF + PERFLOG_GETBUFFER( (IMemAllocator *) this, pSample ); +#endif // DXMPERF + + return NOERROR; +} + + +/* Final release of a CMediaSample will call this */ + +STDMETHODIMP +CBaseAllocator::ReleaseBuffer(IMediaSample * pSample) +{ + CheckPointer(pSample,E_POINTER); + ValidateReadPtr(pSample,sizeof(IMediaSample)); + +#ifdef DXMPERF + PERFLOG_RELBUFFER( (IMemAllocator *) this, pSample ); +#endif // DXMPERF + + + BOOL bRelease = FALSE; + { + CAutoLock cal(this); + + /* Put back on the free list */ + + m_lFree.Add((CMediaSample *)pSample); + if (m_lWaiting != 0) { + NotifySample(); + } + + // if there is a pending Decommit, then we need to complete it by + // calling Free() when the last buffer is placed on the free list + + LONG l1 = m_lFree.GetCount(); + if (m_bDecommitInProgress && (l1 == m_lAllocated)) { + Free(); + m_bDecommitInProgress = FALSE; + bRelease = TRUE; + } + } + + if (m_pNotify) { + + ASSERT(m_fEnableReleaseCallback); + + // + // Note that this is not synchronized with setting up a notification + // method. + // + m_pNotify->NotifyRelease(); + } + + /* For each buffer there is one AddRef, made in GetBuffer and released + here. This may cause the allocator and all samples to be deleted */ + + if (bRelease) { + Release(); + } + return NOERROR; +} + +STDMETHODIMP +CBaseAllocator::SetNotify( + IMemAllocatorNotifyCallbackTemp* pNotify + ) +{ + ASSERT(m_fEnableReleaseCallback); + CAutoLock lck(this); + if (pNotify) { + pNotify->AddRef(); + } + if (m_pNotify) { + m_pNotify->Release(); + } + m_pNotify = pNotify; + return S_OK; +} + +STDMETHODIMP +CBaseAllocator::GetFreeCount( + __out LONG* plBuffersFree + ) +{ + ASSERT(m_fEnableReleaseCallback); + CAutoLock cObjectLock(this); + *plBuffersFree = m_lCount - m_lAllocated + m_lFree.GetCount(); + return NOERROR; +} + +void +CBaseAllocator::NotifySample() +{ + if (m_lWaiting != 0) { + ASSERT(m_hSem != NULL); + ReleaseSemaphore(m_hSem, m_lWaiting, 0); + m_lWaiting = 0; + } +} + +STDMETHODIMP +CBaseAllocator::Commit() +{ + /* Check we are not decommitted */ + CAutoLock cObjectLock(this); + + // cannot need to alloc or re-alloc if we are committed + if (m_bCommitted) { + return NOERROR; + } + + /* Allow GetBuffer calls */ + + m_bCommitted = TRUE; + + // is there a pending decommit ? if so, just cancel it + if (m_bDecommitInProgress) { + m_bDecommitInProgress = FALSE; + + // don't call Alloc at this point. He cannot allow SetProperties + // between Decommit and the last free, so the buffer size cannot have + // changed. And because some of the buffers are not free yet, he + // cannot re-alloc anyway. + return NOERROR; + } + + DbgLog((LOG_MEMORY, 1, TEXT("Allocating: %ldx%ld"), m_lCount, m_lSize)); + + // actually need to allocate the samples + HRESULT hr = Alloc(); + if (FAILED(hr)) { + m_bCommitted = FALSE; + return hr; + } + AddRef(); + return NOERROR; +} + + +STDMETHODIMP +CBaseAllocator::Decommit() +{ + BOOL bRelease = FALSE; + { + /* Check we are not already decommitted */ + CAutoLock cObjectLock(this); + if (m_bCommitted == FALSE) { + if (m_bDecommitInProgress == FALSE) { + return NOERROR; + } + } + + /* No more GetBuffer calls will succeed */ + m_bCommitted = FALSE; + + // are any buffers outstanding? + if (m_lFree.GetCount() < m_lAllocated) { + // please complete the decommit when last buffer is freed + m_bDecommitInProgress = TRUE; + } else { + m_bDecommitInProgress = FALSE; + + // need to complete the decommit here as there are no + // outstanding buffers + + Free(); + bRelease = TRUE; + } + + // Tell anyone waiting that they can go now so we can + // reject their call +#pragma warning(push) +#ifndef _PREFAST_ +#pragma warning(disable:4068) +#endif +#pragma prefast(suppress:__WARNING_DEREF_NULL_PTR, "Suppress warning related to Free() invalidating 'this' which is no applicable to CBaseAllocator::Free()") + NotifySample(); + +#pragma warning(pop) + } + + if (bRelease) { + Release(); + } + return NOERROR; +} + + +/* Base definition of allocation which checks we are ok to go ahead and do + the full allocation. We return S_FALSE if the requirements are the same */ + +HRESULT +CBaseAllocator::Alloc(void) +{ + /* Error if he hasn't set the size yet */ + if (m_lCount <= 0 || m_lSize <= 0 || m_lAlignment <= 0) { + return VFW_E_SIZENOTSET; + } + + /* should never get here while buffers outstanding */ + ASSERT(m_lFree.GetCount() == m_lAllocated); + + /* If the requirements haven't changed then don't reallocate */ + if (m_bChanged == FALSE) { + return S_FALSE; + } + + return NOERROR; +} + +/* Implement CBaseAllocator::CSampleList::Remove(pSample) + Removes pSample from the list +*/ +void +CBaseAllocator::CSampleList::Remove(__inout CMediaSample * pSample) +{ + CMediaSample **pSearch; + for (pSearch = &m_List; + *pSearch != NULL; + pSearch = &(CBaseAllocator::NextSample(*pSearch))) { + if (*pSearch == pSample) { + *pSearch = CBaseAllocator::NextSample(pSample); + CBaseAllocator::NextSample(pSample) = NULL; + m_nOnList--; + return; + } + } + DbgBreak("Couldn't find sample in list"); +} + +//===================================================================== +//===================================================================== +// Implements CMemAllocator +//===================================================================== +//===================================================================== + + +/* This goes in the factory template table to create new instances */ +CUnknown *CMemAllocator::CreateInstance(__inout_opt LPUNKNOWN pUnk, __inout HRESULT *phr) +{ + CUnknown *pUnkRet = new CMemAllocator(NAME("CMemAllocator"), pUnk, phr); + return pUnkRet; +} + +CMemAllocator::CMemAllocator( + __in_opt LPCTSTR pName, + __inout_opt LPUNKNOWN pUnk, + __inout HRESULT *phr) + : CBaseAllocator(pName, pUnk, phr, TRUE, TRUE), + m_pBuffer(NULL) +{ +} + +#ifdef UNICODE +CMemAllocator::CMemAllocator( + __in_opt LPCSTR pName, + __inout_opt LPUNKNOWN pUnk, + __inout HRESULT *phr) + : CBaseAllocator(pName, pUnk, phr, TRUE, TRUE), + m_pBuffer(NULL) +{ +} +#endif + +/* This sets the size and count of the required samples. The memory isn't + actually allocated until Commit() is called, if memory has already been + allocated then assuming no samples are outstanding the user may call us + to change the buffering, the memory will be released in Commit() */ +STDMETHODIMP +CMemAllocator::SetProperties( + __in ALLOCATOR_PROPERTIES* pRequest, + __out ALLOCATOR_PROPERTIES* pActual) +{ + CheckPointer(pActual,E_POINTER); + ValidateReadWritePtr(pActual,sizeof(ALLOCATOR_PROPERTIES)); + CAutoLock cObjectLock(this); + + ZeroMemory(pActual, sizeof(ALLOCATOR_PROPERTIES)); + + ASSERT(pRequest->cbBuffer > 0); + + SYSTEM_INFO SysInfo; + GetSystemInfo(&SysInfo); + + /* Check the alignment request is a power of 2 */ + if ((-pRequest->cbAlign & pRequest->cbAlign) != pRequest->cbAlign) { + DbgLog((LOG_ERROR, 1, TEXT("Alignment requested 0x%x not a power of 2!"), + pRequest->cbAlign)); + } + /* Check the alignment requested */ + if (pRequest->cbAlign == 0 || + (SysInfo.dwAllocationGranularity & (pRequest->cbAlign - 1)) != 0) { + DbgLog((LOG_ERROR, 1, TEXT("Invalid alignment 0x%x requested - granularity = 0x%x"), + pRequest->cbAlign, SysInfo.dwAllocationGranularity)); + return VFW_E_BADALIGN; + } + + /* Can't do this if already committed, there is an argument that says we + should not reject the SetProperties call if there are buffers still + active. However this is called by the source filter, which is the same + person who is holding the samples. Therefore it is not unreasonable + for them to free all their samples before changing the requirements */ + + if (m_bCommitted == TRUE) { + return VFW_E_ALREADY_COMMITTED; + } + + /* Must be no outstanding buffers */ + + if (m_lFree.GetCount() < m_lAllocated) { + return VFW_E_BUFFERS_OUTSTANDING; + } + + /* There isn't any real need to check the parameters as they + will just be rejected when the user finally calls Commit */ + + // round length up to alignment - remember that prefix is included in + // the alignment + LONG lSize = pRequest->cbBuffer + pRequest->cbPrefix; + LONG lRemainder = lSize % pRequest->cbAlign; + if (lRemainder != 0) { + lSize = lSize - lRemainder + pRequest->cbAlign; + } + pActual->cbBuffer = m_lSize = (lSize - pRequest->cbPrefix); + + pActual->cBuffers = m_lCount = pRequest->cBuffers; + pActual->cbAlign = m_lAlignment = pRequest->cbAlign; + pActual->cbPrefix = m_lPrefix = pRequest->cbPrefix; + + m_bChanged = TRUE; + return NOERROR; +} + +// override this to allocate our resources when Commit is called. +// +// note that our resources may be already allocated when this is called, +// since we don't free them on Decommit. We will only be called when in +// decommit state with all buffers free. +// +// object locked by caller +HRESULT +CMemAllocator::Alloc(void) +{ + CAutoLock lck(this); + + /* Check he has called SetProperties */ + HRESULT hr = CBaseAllocator::Alloc(); + if (FAILED(hr)) { + return hr; + } + + /* If the requirements haven't changed then don't reallocate */ + if (hr == S_FALSE) { + ASSERT(m_pBuffer); + return NOERROR; + } + ASSERT(hr == S_OK); // we use this fact in the loop below + + /* Free the old resources */ + if (m_pBuffer) { + ReallyFree(); + } + + /* Make sure we've got reasonable values */ + if ( m_lSize < 0 || m_lPrefix < 0 || m_lCount < 0 ) { + return E_OUTOFMEMORY; + } + + /* Compute the aligned size */ + LONG lAlignedSize = m_lSize + m_lPrefix; + + /* Check overflow */ + if (lAlignedSize < m_lSize) { + return E_OUTOFMEMORY; + } + + if (m_lAlignment > 1) { + LONG lRemainder = lAlignedSize % m_lAlignment; + if (lRemainder != 0) { + LONG lNewSize = lAlignedSize + m_lAlignment - lRemainder; + if (lNewSize < lAlignedSize) { + return E_OUTOFMEMORY; + } + lAlignedSize = lNewSize; + } + } + + /* Create the contiguous memory block for the samples + making sure it's properly aligned (64K should be enough!) + */ + ASSERT(lAlignedSize % m_lAlignment == 0); + + LONGLONG lToAllocate = m_lCount * (LONGLONG)lAlignedSize; + + /* Check overflow */ + if (lToAllocate > MAXLONG) { + return E_OUTOFMEMORY; + } + + m_pBuffer = (PBYTE)VirtualAlloc(NULL, + (LONG)lToAllocate, + MEM_COMMIT, + PAGE_READWRITE); + + if (m_pBuffer == NULL) { + return E_OUTOFMEMORY; + } + + LPBYTE pNext = m_pBuffer; + CMediaSample *pSample; + + ASSERT(m_lAllocated == 0); + + // Create the new samples - we have allocated m_lSize bytes for each sample + // plus m_lPrefix bytes per sample as a prefix. We set the pointer to + // the memory after the prefix - so that GetPointer() will return a pointer + // to m_lSize bytes. + for (; m_lAllocated < m_lCount; m_lAllocated++, pNext += lAlignedSize) { + + + pSample = new CMediaSample( + NAME("Default memory media sample"), + this, + &hr, + pNext + m_lPrefix, // GetPointer() value + m_lSize); // not including prefix + + ASSERT(SUCCEEDED(hr)); + if (pSample == NULL) { + return E_OUTOFMEMORY; + } + + // This CANNOT fail + m_lFree.Add(pSample); + } + + m_bChanged = FALSE; + return NOERROR; +} + + +// override this to free up any resources we have allocated. +// called from the base class on Decommit when all buffers have been +// returned to the free list. +// +// caller has already locked the object. + +// in our case, we keep the memory until we are deleted, so +// we do nothing here. The memory is deleted in the destructor by +// calling ReallyFree() +void +CMemAllocator::Free(void) +{ + return; +} + + +// called from the destructor (and from Alloc if changing size/count) to +// actually free up the memory +void +CMemAllocator::ReallyFree(void) +{ + /* Should never be deleting this unless all buffers are freed */ + + ASSERT(m_lAllocated == m_lFree.GetCount()); + + /* Free up all the CMediaSamples */ + + CMediaSample *pSample; + for (;;) { + pSample = m_lFree.RemoveHead(); + if (pSample != NULL) { + delete pSample; + } else { + break; + } + } + + m_lAllocated = 0; + + // free the block of buffer memory + if (m_pBuffer) { + EXECUTE_ASSERT(VirtualFree(m_pBuffer, 0, MEM_RELEASE)); + m_pBuffer = NULL; + } +} + + +/* Destructor frees our memory resources */ + +CMemAllocator::~CMemAllocator() +{ + Decommit(); + ReallyFree(); +} + +// ------------------------------------------------------------------------ +// filter registration through IFilterMapper. used if IFilterMapper is +// not found (Quartz 1.0 install) + +STDAPI +AMovieSetupRegisterFilter( const AMOVIESETUP_FILTER * const psetupdata + , IFilterMapper * pIFM + , BOOL bRegister ) +{ + DbgLog((LOG_TRACE, 3, TEXT("= AMovieSetupRegisterFilter"))); + + // check we've got data + // + if( NULL == psetupdata ) return S_FALSE; + + + // unregister filter + // (as pins are subkeys of filter's CLSID key + // they do not need to be removed separately). + // + DbgLog((LOG_TRACE, 3, TEXT("= = unregister filter"))); + HRESULT hr = pIFM->UnregisterFilter( *(psetupdata->clsID) ); + + + if( bRegister ) + { + // register filter + // + DbgLog((LOG_TRACE, 3, TEXT("= = register filter"))); + hr = pIFM->RegisterFilter( *(psetupdata->clsID) + , psetupdata->strName + , psetupdata->dwMerit ); + if( SUCCEEDED(hr) ) + { + // all its pins + // + DbgLog((LOG_TRACE, 3, TEXT("= = register filter pins"))); + for( UINT m1=0; m1 < psetupdata->nPins; m1++ ) + { + hr = pIFM->RegisterPin( *(psetupdata->clsID) + , psetupdata->lpPin[m1].strName + , psetupdata->lpPin[m1].bRendered + , psetupdata->lpPin[m1].bOutput + , psetupdata->lpPin[m1].bZero + , psetupdata->lpPin[m1].bMany + , *(psetupdata->lpPin[m1].clsConnectsToFilter) + , psetupdata->lpPin[m1].strConnectsToPin ); + + if( SUCCEEDED(hr) ) + { + // and each pin's media types + // + DbgLog((LOG_TRACE, 3, TEXT("= = register filter pin types"))); + for( UINT m2=0; m2 < psetupdata->lpPin[m1].nMediaTypes; m2++ ) + { + hr = pIFM->RegisterPinType( *(psetupdata->clsID) + , psetupdata->lpPin[m1].strName + , *(psetupdata->lpPin[m1].lpMediaType[m2].clsMajorType) + , *(psetupdata->lpPin[m1].lpMediaType[m2].clsMinorType) ); + if( FAILED(hr) ) break; + } + if( FAILED(hr) ) break; + } + if( FAILED(hr) ) break; + } + } + } + + // handle one acceptable "error" - that + // of filter not being registered! + // (couldn't find a suitable #define'd + // name for the error!) + // + if( 0x80070002 == hr) + return NOERROR; + else + return hr; +} + +// Remove warnings about unreferenced inline functions +#pragma warning(disable:4514) + diff --git a/src/baseclasses/amfilter.h b/src/baseclasses/amfilter.h new file mode 100644 index 0000000..f0930af --- /dev/null +++ b/src/baseclasses/amfilter.h @@ -0,0 +1,1360 @@ +//------------------------------------------------------------------------------ +// File: AMFilter.h +// +// Desc: DirectShow base classes - efines class hierarchy for streams +// architecture. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#ifndef __FILTER__ +#define __FILTER__ + +/* The following classes are declared in this header: */ + +class CBaseFilter; // IBaseFilter,IMediaFilter support +class CBasePin; // Abstract base class for IPin interface +class CEnumPins; // Enumerate input and output pins +class CEnumMediaTypes; // Enumerate the pin's preferred formats +class CBaseOutputPin; // Adds data provider member functions +class CMediaSample; // Basic transport unit for IMemInputPin +class CBaseAllocator; // General list guff for most allocators +class CMemAllocator; // Implements memory buffer allocation + + +//===================================================================== +//===================================================================== +// +// QueryFilterInfo and QueryPinInfo AddRef the interface pointers +// they return. You can use the macro below to release the interface. +// +//===================================================================== +//===================================================================== + +#define QueryFilterInfoReleaseGraph(fi) if ((fi).pGraph) (fi).pGraph->Release(); + +#define QueryPinInfoReleaseFilter(pi) if ((pi).pFilter) (pi).pFilter->Release(); + + +//===================================================================== +//===================================================================== +// Defines CBaseFilter +// +// An abstract class providing basic IBaseFilter support for pin +// enumeration and filter information reading. +// +// We cannot derive from CBaseMediaFilter since methods in IMediaFilter +// are also in IBaseFilter and would be ambiguous. Since much of the code +// assumes that they derive from a class that has m_State and other state +// directly available, we duplicate code from CBaseMediaFilter rather than +// having a member variable. +// +// Derive your filter from this, or from a derived object such as +// CTransformFilter. +//===================================================================== +//===================================================================== + + +class AM_NOVTABLE CBaseFilter : public CUnknown, // Handles an IUnknown + public IBaseFilter, // The Filter Interface + public IAMovieSetup // For un/registration +{ + +friend class CBasePin; + +protected: + FILTER_STATE m_State; // current state: running, paused + IReferenceClock *m_pClock; // this graph's ref clock + CRefTime m_tStart; // offset from stream time to reference time + CLSID m_clsid; // This filters clsid + // used for serialization + CCritSec *m_pLock; // Object we use for locking + + WCHAR *m_pName; // Full filter name + IFilterGraph *m_pGraph; // Graph we belong to + IMediaEventSink *m_pSink; // Called with notify events + LONG m_PinVersion; // Current pin version + +public: + + CBaseFilter( + __in_opt LPCTSTR pName, // Object description + __inout_opt LPUNKNOWN pUnk, // IUnknown of delegating object + __in CCritSec *pLock, // Object who maintains lock + REFCLSID clsid); // The clsid to be used to serialize this filter + + CBaseFilter( + __in_opt LPCTSTR pName, // Object description + __in_opt LPUNKNOWN pUnk, // IUnknown of delegating object + __in CCritSec *pLock, // Object who maintains lock + REFCLSID clsid, // The clsid to be used to serialize this filter + __inout HRESULT *phr); // General OLE return code +#ifdef UNICODE + CBaseFilter( + __in_opt LPCSTR pName, // Object description + __in_opt LPUNKNOWN pUnk, // IUnknown of delegating object + __in CCritSec *pLock, // Object who maintains lock + REFCLSID clsid); // The clsid to be used to serialize this filter + + CBaseFilter( + __in_opt LPCSTR pName, // Object description + __in_opt LPUNKNOWN pUnk, // IUnknown of delegating object + __in CCritSec *pLock, // Object who maintains lock + REFCLSID clsid, // The clsid to be used to serialize this filter + __inout HRESULT *phr); // General OLE return code +#endif + ~CBaseFilter(); + + DECLARE_IUNKNOWN + + // override this to say what interfaces we support where + STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, __deref_out void ** ppv); +#ifdef DEBUG + STDMETHODIMP_(ULONG) NonDelegatingRelease(); +#endif + + // + // --- IPersist method --- + // + + STDMETHODIMP GetClassID(__out CLSID *pClsID); + + // --- IMediaFilter methods --- + + STDMETHODIMP GetState(DWORD dwMSecs, __out FILTER_STATE *State); + + STDMETHODIMP SetSyncSource(__in_opt IReferenceClock *pClock); + + STDMETHODIMP GetSyncSource(__deref_out_opt IReferenceClock **pClock); + + + // override Stop and Pause so we can activate the pins. + // Note that Run will call Pause first if activation needed. + // Override these if you want to activate your filter rather than + // your pins. + STDMETHODIMP Stop(); + STDMETHODIMP Pause(); + + // the start parameter is the difference to be added to the + // sample's stream time to get the reference time for + // its presentation + STDMETHODIMP Run(REFERENCE_TIME tStart); + + // --- helper methods --- + + // return the current stream time - ie find out what + // stream time should be appearing now + virtual HRESULT StreamTime(CRefTime& rtStream); + + // Is the filter currently active? + BOOL IsActive() { + CAutoLock cObjectLock(m_pLock); + return ((m_State == State_Paused) || (m_State == State_Running)); + }; + + // Is this filter stopped (without locking) + BOOL IsStopped() { + return (m_State == State_Stopped); + }; + + // + // --- IBaseFilter methods --- + // + + // pin enumerator + STDMETHODIMP EnumPins( + __deref_out IEnumPins ** ppEnum); + + + // default behaviour of FindPin assumes pin ids are their names + STDMETHODIMP FindPin( + LPCWSTR Id, + __deref_out IPin ** ppPin + ); + + STDMETHODIMP QueryFilterInfo( + __out FILTER_INFO * pInfo); + + STDMETHODIMP JoinFilterGraph( + __inout_opt IFilterGraph * pGraph, + __in_opt LPCWSTR pName); + + // return a Vendor information string. Optional - may return E_NOTIMPL. + // memory returned should be freed using CoTaskMemFree + // default implementation returns E_NOTIMPL + STDMETHODIMP QueryVendorInfo( + __deref_out LPWSTR* pVendorInfo + ); + + // --- helper methods --- + + // send an event notification to the filter graph if we know about it. + // returns S_OK if delivered, S_FALSE if the filter graph does not sink + // events, or an error otherwise. + HRESULT NotifyEvent( + long EventCode, + LONG_PTR EventParam1, + LONG_PTR EventParam2); + + // return the filter graph we belong to + __out_opt IFilterGraph *GetFilterGraph() { + return m_pGraph; + } + + // Request reconnect + // pPin is the pin to reconnect + // pmt is the type to reconnect with - can be NULL + // Calls ReconnectEx on the filter graph + HRESULT ReconnectPin(IPin *pPin, __in_opt AM_MEDIA_TYPE const *pmt); + + // find out the current pin version (used by enumerators) + virtual LONG GetPinVersion(); + void IncrementPinVersion(); + + // you need to supply these to access the pins from the enumerator + // and for default Stop and Pause/Run activation. + virtual int GetPinCount() PURE; + virtual CBasePin *GetPin(int n) PURE; + + // --- IAMovieSetup methods --- + + STDMETHODIMP Register(); // ask filter to register itself + STDMETHODIMP Unregister(); // and unregister itself + + // --- setup helper methods --- + // (override to return filters setup data) + + virtual __out_opt LPAMOVIESETUP_FILTER GetSetupData(){ return NULL; } + +}; + + +//===================================================================== +//===================================================================== +// Defines CBasePin +// +// Abstract class that supports the basics of IPin +//===================================================================== +//===================================================================== + +class AM_NOVTABLE CBasePin : public CUnknown, public IPin, public IQualityControl +{ + +protected: + + WCHAR * m_pName; // This pin's name + IPin *m_Connected; // Pin we have connected to + PIN_DIRECTION m_dir; // Direction of this pin + CCritSec *m_pLock; // Object we use for locking + bool m_bRunTimeError; // Run time error generated + bool m_bCanReconnectWhenActive; // OK to reconnect when active + bool m_bTryMyTypesFirst; // When connecting enumerate + // this pin's types first + CBaseFilter *m_pFilter; // Filter we were created by + IQualityControl *m_pQSink; // Target for Quality messages + LONG m_TypeVersion; // Holds current type version + CMediaType m_mt; // Media type of connection + + CRefTime m_tStart; // time from NewSegment call + CRefTime m_tStop; // time from NewSegment + double m_dRate; // rate from NewSegment + +#ifdef DEBUG + LONG m_cRef; // Ref count tracing +#endif + + // displays pin connection information + +#ifdef DEBUG + void DisplayPinInfo(IPin *pReceivePin); + void DisplayTypeInfo(IPin *pPin, const CMediaType *pmt); +#else + void DisplayPinInfo(IPin *pReceivePin) {}; + void DisplayTypeInfo(IPin *pPin, const CMediaType *pmt) {}; +#endif + + // used to agree a media type for a pin connection + + // given a specific media type, attempt a connection (includes + // checking that the type is acceptable to this pin) + HRESULT + AttemptConnection( + IPin* pReceivePin, // connect to this pin + const CMediaType* pmt // using this type + ); + + // try all the media types in this enumerator - for each that + // we accept, try to connect using ReceiveConnection. + HRESULT TryMediaTypes( + IPin *pReceivePin, // connect to this pin + __in_opt const CMediaType *pmt, // proposed type from Connect + IEnumMediaTypes *pEnum); // try this enumerator + + // establish a connection with a suitable mediatype. Needs to + // propose a media type if the pmt pointer is null or partially + // specified - use TryMediaTypes on both our and then the other pin's + // enumerator until we find one that works. + HRESULT AgreeMediaType( + IPin *pReceivePin, // connect to this pin + const CMediaType *pmt); // proposed type from Connect + +public: + + CBasePin( + __in_opt LPCTSTR pObjectName, // Object description + __in CBaseFilter *pFilter, // Owning filter who knows about pins + __in CCritSec *pLock, // Object who implements the lock + __inout HRESULT *phr, // General OLE return code + __in_opt LPCWSTR pName, // Pin name for us + PIN_DIRECTION dir); // Either PINDIR_INPUT or PINDIR_OUTPUT +#ifdef UNICODE + CBasePin( + __in_opt LPCSTR pObjectName, // Object description + __in CBaseFilter *pFilter, // Owning filter who knows about pins + __in CCritSec *pLock, // Object who implements the lock + __inout HRESULT *phr, // General OLE return code + __in_opt LPCWSTR pName, // Pin name for us + PIN_DIRECTION dir); // Either PINDIR_INPUT or PINDIR_OUTPUT +#endif + virtual ~CBasePin(); + + DECLARE_IUNKNOWN + + STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, __deref_out void ** ppv); + STDMETHODIMP_(ULONG) NonDelegatingRelease(); + STDMETHODIMP_(ULONG) NonDelegatingAddRef(); + + // --- IPin methods --- + + // take lead role in establishing a connection. Media type pointer + // may be null, or may point to partially-specified mediatype + // (subtype or format type may be GUID_NULL). + STDMETHODIMP Connect( + IPin * pReceivePin, + __in_opt const AM_MEDIA_TYPE *pmt // optional media type + ); + + // (passive) accept a connection from another pin + STDMETHODIMP ReceiveConnection( + IPin * pConnector, // this is the initiating connecting pin + const AM_MEDIA_TYPE *pmt // this is the media type we will exchange + ); + + STDMETHODIMP Disconnect(); + + STDMETHODIMP ConnectedTo(__deref_out IPin **pPin); + + STDMETHODIMP ConnectionMediaType(__out AM_MEDIA_TYPE *pmt); + + STDMETHODIMP QueryPinInfo( + __out PIN_INFO * pInfo + ); + + STDMETHODIMP QueryDirection( + __out PIN_DIRECTION * pPinDir + ); + + STDMETHODIMP QueryId( + __deref_out LPWSTR * Id + ); + + // does the pin support this media type + STDMETHODIMP QueryAccept( + const AM_MEDIA_TYPE *pmt + ); + + // return an enumerator for this pins preferred media types + STDMETHODIMP EnumMediaTypes( + __deref_out IEnumMediaTypes **ppEnum + ); + + // return an array of IPin* - the pins that this pin internally connects to + // All pins put in the array must be AddReffed (but no others) + // Errors: "Can't say" - FAIL, not enough slots - return S_FALSE + // Default: return E_NOTIMPL + // The filter graph will interpret NOT_IMPL as any input pin connects to + // all visible output pins and vice versa. + // apPin can be NULL if nPin==0 (not otherwise). + STDMETHODIMP QueryInternalConnections( + __out_ecount_part(*nPin,*nPin) IPin* *apPin, // array of IPin* + __inout ULONG *nPin // on input, the number of slots + // on output the number of pins + ) { return E_NOTIMPL; } + + // Called when no more data will be sent + STDMETHODIMP EndOfStream(void); + + // Begin/EndFlush still PURE + + // NewSegment notifies of the start/stop/rate applying to the data + // about to be received. Default implementation records data and + // returns S_OK. + // Override this to pass downstream. + STDMETHODIMP NewSegment( + REFERENCE_TIME tStart, + REFERENCE_TIME tStop, + double dRate); + + //================================================================================ + // IQualityControl methods + //================================================================================ + + STDMETHODIMP Notify(IBaseFilter * pSender, Quality q); + + STDMETHODIMP SetSink(IQualityControl * piqc); + + // --- helper methods --- + + // Returns true if the pin is connected. false otherwise. + BOOL IsConnected(void) {return (m_Connected != NULL); }; + // Return the pin this is connected to (if any) + IPin * GetConnected() { return m_Connected; }; + + // Check if our filter is currently stopped + BOOL IsStopped() { + return (m_pFilter->m_State == State_Stopped); + }; + + // find out the current type version (used by enumerators) + virtual LONG GetMediaTypeVersion(); + void IncrementTypeVersion(); + + // switch the pin to active (paused or running) mode + // not an error to call this if already active + virtual HRESULT Active(void); + + // switch the pin to inactive state - may already be inactive + virtual HRESULT Inactive(void); + + // Notify of Run() from filter + virtual HRESULT Run(REFERENCE_TIME tStart); + + // check if the pin can support this specific proposed type and format + virtual HRESULT CheckMediaType(const CMediaType *) PURE; + + // set the connection to use this format (previously agreed) + virtual HRESULT SetMediaType(const CMediaType *); + + // check that the connection is ok before verifying it + // can be overridden eg to check what interfaces will be supported. + virtual HRESULT CheckConnect(IPin *); + + // Set and release resources required for a connection + virtual HRESULT BreakConnect(); + virtual HRESULT CompleteConnect(IPin *pReceivePin); + + // returns the preferred formats for a pin + virtual HRESULT GetMediaType(int iPosition, __inout CMediaType *pMediaType); + + // access to NewSegment values + REFERENCE_TIME CurrentStopTime() { + return m_tStop; + } + REFERENCE_TIME CurrentStartTime() { + return m_tStart; + } + double CurrentRate() { + return m_dRate; + } + + // Access name + LPWSTR Name() { return m_pName; }; + + // Can reconnectwhen active? + void SetReconnectWhenActive(bool bCanReconnect) + { + m_bCanReconnectWhenActive = bCanReconnect; + } + + bool CanReconnectWhenActive() + { + return m_bCanReconnectWhenActive; + } + +protected: + STDMETHODIMP DisconnectInternal(); +}; + + +//===================================================================== +//===================================================================== +// Defines CEnumPins +// +// Pin enumerator class that works by calling CBaseFilter. This interface +// is provided by CBaseFilter::EnumPins and calls GetPinCount() and +// GetPin() to enumerate existing pins. Needs to be a separate object so +// that it can be cloned (creating an existing object at the same +// position in the enumeration) +// +//===================================================================== +//===================================================================== + +class CEnumPins : public IEnumPins // The interface we support +{ + int m_Position; // Current ordinal position + int m_PinCount; // Number of pins available + CBaseFilter *m_pFilter; // The filter who owns us + LONG m_Version; // Pin version information + LONG m_cRef; + + typedef CGenericList CPinList; + + CPinList m_PinCache; // These pointers have not been AddRef'ed and + // so they should not be dereferenced. They are + // merely kept to ID which pins have been enumerated. + +#ifdef DEBUG + DWORD m_dwCookie; +#endif + + /* If while we are retrieving a pin for example from the filter an error + occurs we assume that our internal state is stale with respect to the + filter (someone may have deleted all the pins). We can check before + starting whether or not the operation is likely to fail by asking the + filter what it's current version number is. If the filter has not + overriden the GetPinVersion method then this will always match */ + + BOOL AreWeOutOfSync() { + return (m_pFilter->GetPinVersion() == m_Version ? FALSE : TRUE); + }; + + /* This method performs the same operations as Reset, except is does not clear + the cache of pins already enumerated. */ + + STDMETHODIMP Refresh(); + +public: + + CEnumPins( + __in CBaseFilter *pFilter, + __in_opt CEnumPins *pEnumPins); + + virtual ~CEnumPins(); + + // IUnknown + STDMETHODIMP QueryInterface(REFIID riid, __deref_out void **ppv); + STDMETHODIMP_(ULONG) AddRef(); + STDMETHODIMP_(ULONG) Release(); + + // IEnumPins + STDMETHODIMP Next( + ULONG cPins, // place this many pins... + __out_ecount(cPins) IPin ** ppPins, // ...in this array of IPin* + __out_opt ULONG * pcFetched // actual count passed returned here + ); + + STDMETHODIMP Skip(ULONG cPins); + STDMETHODIMP Reset(); + STDMETHODIMP Clone(__deref_out IEnumPins **ppEnum); + + +}; + + +//===================================================================== +//===================================================================== +// Defines CEnumMediaTypes +// +// Enumerates the preferred formats for input and output pins +//===================================================================== +//===================================================================== + +class CEnumMediaTypes : public IEnumMediaTypes // The interface we support +{ + int m_Position; // Current ordinal position + CBasePin *m_pPin; // The pin who owns us + LONG m_Version; // Media type version value + LONG m_cRef; +#ifdef DEBUG + DWORD m_dwCookie; +#endif + + /* The media types a filter supports can be quite dynamic so we add to + the general IEnumXXXX interface the ability to be signaled when they + change via an event handle the connected filter supplies. Until the + Reset method is called after the state changes all further calls to + the enumerator (except Reset) will return E_UNEXPECTED error code */ + + BOOL AreWeOutOfSync() { + return (m_pPin->GetMediaTypeVersion() == m_Version ? FALSE : TRUE); + }; + +public: + + CEnumMediaTypes( + __in CBasePin *pPin, + __in_opt CEnumMediaTypes *pEnumMediaTypes); + + virtual ~CEnumMediaTypes(); + + // IUnknown + STDMETHODIMP QueryInterface(REFIID riid, __deref_out void **ppv); + STDMETHODIMP_(ULONG) AddRef(); + STDMETHODIMP_(ULONG) Release(); + + // IEnumMediaTypes + STDMETHODIMP Next( + ULONG cMediaTypes, // place this many pins... + __out_ecount(cMediaTypes) AM_MEDIA_TYPE ** ppMediaTypes, // ...in this array + __out_opt ULONG * pcFetched // actual count passed + ); + + STDMETHODIMP Skip(ULONG cMediaTypes); + STDMETHODIMP Reset(); + STDMETHODIMP Clone(__deref_out IEnumMediaTypes **ppEnum); +}; + + + +//===================================================================== +//===================================================================== +// Defines CBaseOutputPin +// +// class derived from CBasePin that can pass buffers to a connected pin +// that supports IMemInputPin. Supports IPin. +// +// Derive your output pin from this. +// +//===================================================================== +//===================================================================== + +class AM_NOVTABLE CBaseOutputPin : public CBasePin +{ + +protected: + + IMemAllocator *m_pAllocator; + IMemInputPin *m_pInputPin; // interface on the downstreaminput pin + // set up in CheckConnect when we connect. + +public: + + CBaseOutputPin( + __in_opt LPCTSTR pObjectName, + __in CBaseFilter *pFilter, + __in CCritSec *pLock, + __inout HRESULT *phr, + __in_opt LPCWSTR pName); +#ifdef UNICODE + CBaseOutputPin( + __in_opt LPCSTR pObjectName, + __in CBaseFilter *pFilter, + __in CCritSec *pLock, + __inout HRESULT *phr, + __in_opt LPCWSTR pName); +#endif + // override CompleteConnect() so we can negotiate an allocator + virtual HRESULT CompleteConnect(IPin *pReceivePin); + + // negotiate the allocator and its buffer size/count and other properties + // Calls DecideBufferSize to set properties + virtual HRESULT DecideAllocator(IMemInputPin * pPin, __deref_out IMemAllocator ** pAlloc); + + // override this to set the buffer size and count. Return an error + // if the size/count is not to your liking. + // The allocator properties passed in are those requested by the + // input pin - use eg the alignment and prefix members if you have + // no preference on these. + virtual HRESULT DecideBufferSize( + IMemAllocator * pAlloc, + __inout ALLOCATOR_PROPERTIES * ppropInputRequest + ) PURE; + + // returns an empty sample buffer from the allocator + virtual HRESULT GetDeliveryBuffer(__deref_out IMediaSample ** ppSample, + __in_opt REFERENCE_TIME * pStartTime, + __in_opt REFERENCE_TIME * pEndTime, + DWORD dwFlags); + + // deliver a filled-in sample to the connected input pin + // note - you need to release it after calling this. The receiving + // pin will addref the sample if it needs to hold it beyond the + // call. + virtual HRESULT Deliver(IMediaSample *); + + // override this to control the connection + virtual HRESULT InitAllocator(__deref_out IMemAllocator **ppAlloc); + HRESULT CheckConnect(IPin *pPin); + HRESULT BreakConnect(); + + // override to call Commit and Decommit + HRESULT Active(void); + HRESULT Inactive(void); + + // we have a default handling of EndOfStream which is to return + // an error, since this should be called on input pins only + STDMETHODIMP EndOfStream(void); + + // called from elsewhere in our filter to pass EOS downstream to + // our connected input pin + virtual HRESULT DeliverEndOfStream(void); + + // same for Begin/EndFlush - we handle Begin/EndFlush since it + // is an error on an output pin, and we have Deliver methods to + // call the methods on the connected pin + STDMETHODIMP BeginFlush(void); + STDMETHODIMP EndFlush(void); + virtual HRESULT DeliverBeginFlush(void); + virtual HRESULT DeliverEndFlush(void); + + // deliver NewSegment to connected pin - you will need to + // override this if you queue any data in your output pin. + virtual HRESULT DeliverNewSegment( + REFERENCE_TIME tStart, + REFERENCE_TIME tStop, + double dRate); + + //================================================================================ + // IQualityControl methods + //================================================================================ + + // All inherited from CBasePin and not overridden here. + // STDMETHODIMP Notify(IBaseFilter * pSender, Quality q); + // STDMETHODIMP SetSink(IQualityControl * piqc); +}; + + +/////////////////////////////////////////////////////////////////////////// +// CDynamicOutputPin +// + +class CDynamicOutputPin : public CBaseOutputPin, + public IPinFlowControl +{ +public: +#ifdef UNICODE + CDynamicOutputPin( + __in_opt LPCSTR pObjectName, + __in CBaseFilter *pFilter, + __in CCritSec *pLock, + __inout HRESULT *phr, + __in_opt LPCWSTR pName); +#endif + + CDynamicOutputPin( + __in_opt LPCTSTR pObjectName, + __in CBaseFilter *pFilter, + __in CCritSec *pLock, + __inout HRESULT *phr, + __in_opt LPCWSTR pName); + + ~CDynamicOutputPin(); + + // IUnknown Methods + DECLARE_IUNKNOWN + STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, __deref_out void **ppv); + + // IPin Methods + STDMETHODIMP Disconnect(void); + + // IPinFlowControl Methods + STDMETHODIMP Block(DWORD dwBlockFlags, HANDLE hEvent); + + // Set graph config info + void SetConfigInfo(IGraphConfig *pGraphConfig, HANDLE hStopEvent); + + #ifdef DEBUG + virtual HRESULT Deliver(IMediaSample *pSample); + virtual HRESULT DeliverEndOfStream(void); + virtual HRESULT DeliverNewSegment(REFERENCE_TIME tStart, REFERENCE_TIME tStop, double dRate); + #endif // DEBUG + + HRESULT DeliverBeginFlush(void); + HRESULT DeliverEndFlush(void); + + HRESULT Inactive(void); + HRESULT Active(void); + virtual HRESULT CompleteConnect(IPin *pReceivePin); + + virtual HRESULT StartUsingOutputPin(void); + virtual void StopUsingOutputPin(void); + virtual bool StreamingThreadUsingOutputPin(void); + + HRESULT ChangeOutputFormat + ( + const AM_MEDIA_TYPE *pmt, + REFERENCE_TIME tSegmentStart, + REFERENCE_TIME tSegmentStop, + double dSegmentRate + ); + HRESULT ChangeMediaType(const CMediaType *pmt); + HRESULT DynamicReconnect(const CMediaType *pmt); + +protected: + HRESULT SynchronousBlockOutputPin(void); + HRESULT AsynchronousBlockOutputPin(HANDLE hNotifyCallerPinBlockedEvent); + HRESULT UnblockOutputPin(void); + + void BlockOutputPin(void); + void ResetBlockState(void); + + static HRESULT WaitEvent(HANDLE hEvent); + + enum BLOCK_STATE + { + NOT_BLOCKED, + PENDING, + BLOCKED + }; + + // This lock should be held when the following class members are + // being used: m_hNotifyCallerPinBlockedEvent, m_BlockState, + // m_dwBlockCallerThreadID and m_dwNumOutstandingOutputPinUsers. + CCritSec m_BlockStateLock; + + // This event should be signaled when the output pin is + // not blocked. This is a manual reset event. For more + // information on events, see the documentation for + // CreateEvent() in the Windows SDK. + HANDLE m_hUnblockOutputPinEvent; + + // This event will be signaled when block operation succeedes or + // when the user cancels the block operation. The block operation + // can be canceled by calling IPinFlowControl2::Block( 0, NULL ) + // while the block operation is pending. + HANDLE m_hNotifyCallerPinBlockedEvent; + + // The state of the current block operation. + BLOCK_STATE m_BlockState; + + // The ID of the thread which last called IPinFlowControl::Block(). + // For more information on thread IDs, see the documentation for + // GetCurrentThreadID() in the Windows SDK. + DWORD m_dwBlockCallerThreadID; + + // The number of times StartUsingOutputPin() has been sucessfully + // called and a corresponding call to StopUsingOutputPin() has not + // been made. When this variable is greater than 0, the streaming + // thread is calling IPin::NewSegment(), IPin::EndOfStream(), + // IMemInputPin::Receive() or IMemInputPin::ReceiveMultiple(). The + // streaming thread could also be calling: DynamicReconnect(), + // ChangeMediaType() or ChangeOutputFormat(). The output pin cannot + // be blocked while the output pin is being used. + DWORD m_dwNumOutstandingOutputPinUsers; + + // This event should be set when the IMediaFilter::Stop() is called. + // This is a manual reset event. It is also set when the output pin + // delivers a flush to the connected input pin. + HANDLE m_hStopEvent; + IGraphConfig* m_pGraphConfig; + + // TRUE if the output pin's allocator's samples are read only. + // Otherwise FALSE. For more information, see the documentation + // for IMemInputPin::NotifyAllocator(). + BOOL m_bPinUsesReadOnlyAllocator; + +private: + HRESULT Initialize(void); + HRESULT ChangeMediaTypeHelper(const CMediaType *pmt); + + #ifdef DEBUG + void AssertValid(void); + #endif // DEBUG +}; + +class CAutoUsingOutputPin +{ +public: + CAutoUsingOutputPin( __in CDynamicOutputPin* pOutputPin, __inout HRESULT* phr ); + ~CAutoUsingOutputPin(); + +private: + CDynamicOutputPin* m_pOutputPin; +}; + +inline CAutoUsingOutputPin::CAutoUsingOutputPin( __in CDynamicOutputPin* pOutputPin, __inout HRESULT* phr ) : + m_pOutputPin(NULL) +{ + // The caller should always pass in valid pointers. + ASSERT( NULL != pOutputPin ); + ASSERT( NULL != phr ); + + // Make sure the user initialized phr. + ASSERT( S_OK == *phr ); + + HRESULT hr = pOutputPin->StartUsingOutputPin(); + if( FAILED( hr ) ) + { + *phr = hr; + return; + } + + m_pOutputPin = pOutputPin; +} + +inline CAutoUsingOutputPin::~CAutoUsingOutputPin() +{ + if( NULL != m_pOutputPin ) + { + m_pOutputPin->StopUsingOutputPin(); + } +} + +#ifdef DEBUG + +inline HRESULT CDynamicOutputPin::Deliver(IMediaSample *pSample) +{ + // The caller should call StartUsingOutputPin() before calling this + // method. + ASSERT(StreamingThreadUsingOutputPin()); + + return CBaseOutputPin::Deliver(pSample); +} + +inline HRESULT CDynamicOutputPin::DeliverEndOfStream(void) +{ + // The caller should call StartUsingOutputPin() before calling this + // method. + ASSERT( StreamingThreadUsingOutputPin() ); + + return CBaseOutputPin::DeliverEndOfStream(); +} + +inline HRESULT CDynamicOutputPin::DeliverNewSegment(REFERENCE_TIME tStart, REFERENCE_TIME tStop, double dRate) +{ + // The caller should call StartUsingOutputPin() before calling this + // method. + ASSERT(StreamingThreadUsingOutputPin()); + + return CBaseOutputPin::DeliverNewSegment(tStart, tStop, dRate); +} + +#endif // DEBUG + +//===================================================================== +//===================================================================== +// Memory allocators +// +// the shared memory transport between pins requires the input pin +// to provide a memory allocator that can provide sample objects. A +// sample object supports the IMediaSample interface. +// +// CBaseAllocator handles the management of free and busy samples. It +// allocates CMediaSample objects. CBaseAllocator is an abstract class: +// in particular it has no method of initializing the list of free +// samples. CMemAllocator is derived from CBaseAllocator and initializes +// the list of samples using memory from the standard IMalloc interface. +// +// If you want your buffers to live in some special area of memory, +// derive your allocator object from CBaseAllocator. If you derive your +// IMemInputPin interface object from CBaseMemInputPin, you will get +// CMemAllocator-based allocation etc for free and will just need to +// supply the Receive handling, and media type / format negotiation. +//===================================================================== +//===================================================================== + + +//===================================================================== +//===================================================================== +// Defines CMediaSample +// +// an object of this class supports IMediaSample and represents a buffer +// for media data with some associated properties. Releasing it returns +// it to a freelist managed by a CBaseAllocator derived object. +//===================================================================== +//===================================================================== + +class CMediaSample : public IMediaSample2 // The interface we support +{ + +protected: + + friend class CBaseAllocator; + + /* Values for dwFlags - these are used for backward compatiblity + only now - use AM_SAMPLE_xxx + */ + enum { Sample_SyncPoint = 0x01, /* Is this a sync point */ + Sample_Preroll = 0x02, /* Is this a preroll sample */ + Sample_Discontinuity = 0x04, /* Set if start of new segment */ + Sample_TypeChanged = 0x08, /* Has the type changed */ + Sample_TimeValid = 0x10, /* Set if time is valid */ + Sample_MediaTimeValid = 0x20, /* Is the media time valid */ + Sample_TimeDiscontinuity = 0x40, /* Time discontinuity */ + Sample_StopValid = 0x100, /* Stop time valid */ + Sample_ValidFlags = 0x1FF + }; + + /* Properties, the media sample class can be a container for a format + change in which case we take a copy of a type through the SetMediaType + interface function and then return it when GetMediaType is called. As + we do no internal processing on it we leave it as a pointer */ + + DWORD m_dwFlags; /* Flags for this sample */ + /* Type specific flags are packed + into the top word + */ + DWORD m_dwTypeSpecificFlags; /* Media type specific flags */ + __field_ecount_opt(m_cbBuffer) LPBYTE m_pBuffer; /* Pointer to the complete buffer */ + LONG m_lActual; /* Length of data in this sample */ + LONG m_cbBuffer; /* Size of the buffer */ + CBaseAllocator *m_pAllocator; /* The allocator who owns us */ + CMediaSample *m_pNext; /* Chaining in free list */ + REFERENCE_TIME m_Start; /* Start sample time */ + REFERENCE_TIME m_End; /* End sample time */ + LONGLONG m_MediaStart; /* Real media start position */ + LONG m_MediaEnd; /* A difference to get the end */ + AM_MEDIA_TYPE *m_pMediaType; /* Media type change data */ + DWORD m_dwStreamId; /* Stream id */ +public: + LONG m_cRef; /* Reference count */ + + +public: + + CMediaSample( + __in_opt LPCTSTR pName, + __in_opt CBaseAllocator *pAllocator, + __inout_opt HRESULT *phr, + __in_bcount_opt(length) LPBYTE pBuffer = NULL, + LONG length = 0); +#ifdef UNICODE + CMediaSample( + __in_opt LPCSTR pName, + __in_opt CBaseAllocator *pAllocator, + __inout_opt HRESULT *phr, + __in_bcount_opt(length) LPBYTE pBuffer = NULL, + LONG length = 0); +#endif + + virtual ~CMediaSample(); + + /* Note the media sample does not delegate to its owner */ + + STDMETHODIMP QueryInterface(REFIID riid, __deref_out void **ppv); + STDMETHODIMP_(ULONG) AddRef(); + STDMETHODIMP_(ULONG) Release(); + + // set the buffer pointer and length. Used by allocators that + // want variable sized pointers or pointers into already-read data. + // This is only available through a CMediaSample* not an IMediaSample* + // and so cannot be changed by clients. + HRESULT SetPointer(__in_bcount(cBytes) BYTE * ptr, LONG cBytes); + + // Get me a read/write pointer to this buffer's memory. + STDMETHODIMP GetPointer(__deref_out BYTE ** ppBuffer); + + STDMETHODIMP_(LONG) GetSize(void); + + // get the stream time at which this sample should start and finish. + STDMETHODIMP GetTime( + __out REFERENCE_TIME * pTimeStart, // put time here + __out REFERENCE_TIME * pTimeEnd + ); + + // Set the stream time at which this sample should start and finish. + STDMETHODIMP SetTime( + __in_opt REFERENCE_TIME * pTimeStart, // put time here + __in_opt REFERENCE_TIME * pTimeEnd + ); + STDMETHODIMP IsSyncPoint(void); + STDMETHODIMP SetSyncPoint(BOOL bIsSyncPoint); + STDMETHODIMP IsPreroll(void); + STDMETHODIMP SetPreroll(BOOL bIsPreroll); + + STDMETHODIMP_(LONG) GetActualDataLength(void); + STDMETHODIMP SetActualDataLength(LONG lActual); + + // these allow for limited format changes in band + + STDMETHODIMP GetMediaType(__deref_out AM_MEDIA_TYPE **ppMediaType); + STDMETHODIMP SetMediaType(__in_opt AM_MEDIA_TYPE *pMediaType); + + // returns S_OK if there is a discontinuity in the data (this same is + // not a continuation of the previous stream of data + // - there has been a seek). + STDMETHODIMP IsDiscontinuity(void); + // set the discontinuity property - TRUE if this sample is not a + // continuation, but a new sample after a seek. + STDMETHODIMP SetDiscontinuity(BOOL bDiscontinuity); + + // get the media times for this sample + STDMETHODIMP GetMediaTime( + __out LONGLONG * pTimeStart, + __out LONGLONG * pTimeEnd + ); + + // Set the media times for this sample + STDMETHODIMP SetMediaTime( + __in_opt LONGLONG * pTimeStart, + __in_opt LONGLONG * pTimeEnd + ); + + // Set and get properties (IMediaSample2) + STDMETHODIMP GetProperties( + DWORD cbProperties, + __out_bcount(cbProperties) BYTE * pbProperties + ); + + STDMETHODIMP SetProperties( + DWORD cbProperties, + __in_bcount(cbProperties) const BYTE * pbProperties + ); +}; + + +//===================================================================== +//===================================================================== +// Defines CBaseAllocator +// +// Abstract base class that manages a list of media samples +// +// This class provides support for getting buffers from the free list, +// including handling of commit and (asynchronous) decommit. +// +// Derive from this class and override the Alloc and Free functions to +// allocate your CMediaSample (or derived) objects and add them to the +// free list, preparing them as necessary. +//===================================================================== +//===================================================================== + +class AM_NOVTABLE CBaseAllocator : public CUnknown,// A non delegating IUnknown + public IMemAllocatorCallbackTemp, // The interface we support + public CCritSec // Provides object locking +{ + class CSampleList; + friend class CSampleList; + + /* Trick to get at protected member in CMediaSample */ + static CMediaSample * &NextSample(__in CMediaSample *pSample) + { + return pSample->m_pNext; + }; + + /* Mini list class for the free list */ + class CSampleList + { + public: + CSampleList() : m_List(NULL), m_nOnList(0) {}; +#ifdef DEBUG + ~CSampleList() + { + ASSERT(m_nOnList == 0); + }; +#endif + CMediaSample *Head() const { return m_List; }; + CMediaSample *Next(__in CMediaSample *pSample) const { return CBaseAllocator::NextSample(pSample); }; + int GetCount() const { return m_nOnList; }; + void Add(__inout CMediaSample *pSample) + { + ASSERT(pSample != NULL); + CBaseAllocator::NextSample(pSample) = m_List; + m_List = pSample; + m_nOnList++; + }; + CMediaSample *RemoveHead() + { + CMediaSample *pSample = m_List; + if (pSample != NULL) { + m_List = CBaseAllocator::NextSample(m_List); + m_nOnList--; + } + return pSample; + }; + void Remove(__inout CMediaSample *pSample); + + public: + CMediaSample *m_List; + int m_nOnList; + }; +protected: + + CSampleList m_lFree; // Free list + + /* Note to overriders of CBaseAllocator. + + We use a lazy signalling mechanism for waiting for samples. + This means we don't call the OS if no waits occur. + + In order to implement this: + + 1. When a new sample is added to m_lFree call NotifySample() which + calls ReleaseSemaphore on m_hSem with a count of m_lWaiting and + sets m_lWaiting to 0. + This must all be done holding the allocator's critical section. + + 2. When waiting for a sample call SetWaiting() which increments + m_lWaiting BEFORE leaving the allocator's critical section. + + 3. Actually wait by calling WaitForSingleObject(m_hSem, INFINITE) + having left the allocator's critical section. The effect of + this is to remove 1 from the semaphore's count. You MUST call + this once having incremented m_lWaiting. + + The following are then true when the critical section is not held : + (let nWaiting = number about to wait or waiting) + + (1) if (m_lFree.GetCount() != 0) then (m_lWaiting == 0) + (2) m_lWaiting + Semaphore count == nWaiting + + We would deadlock if + nWaiting != 0 && + m_lFree.GetCount() != 0 && + Semaphore count == 0 + + But from (1) if m_lFree.GetCount() != 0 then m_lWaiting == 0 so + from (2) Semaphore count == nWaiting (which is non-0) so the + deadlock can't happen. + */ + + HANDLE m_hSem; // For signalling + long m_lWaiting; // Waiting for a free element + long m_lCount; // how many buffers we have agreed to provide + long m_lAllocated; // how many buffers are currently allocated + long m_lSize; // agreed size of each buffer + long m_lAlignment; // agreed alignment + long m_lPrefix; // agreed prefix (preceeds GetPointer() value) + BOOL m_bChanged; // Have the buffer requirements changed + + // if true, we are decommitted and can't allocate memory + BOOL m_bCommitted; + // if true, the decommit has happened, but we haven't called Free yet + // as there are still outstanding buffers + BOOL m_bDecommitInProgress; + + // Notification interface + IMemAllocatorNotifyCallbackTemp *m_pNotify; + + BOOL m_fEnableReleaseCallback; + + // called to decommit the memory when the last buffer is freed + // pure virtual - need to override this + virtual void Free(void) PURE; + + // override to allocate the memory when commit called + virtual HRESULT Alloc(void); + +public: + + CBaseAllocator( + __in_opt LPCTSTR , __inout_opt LPUNKNOWN, __inout HRESULT *, + BOOL bEvent = TRUE, BOOL fEnableReleaseCallback = FALSE); +#ifdef UNICODE + CBaseAllocator( + __in_opt LPCSTR , __inout_opt LPUNKNOWN, __inout HRESULT *, + BOOL bEvent = TRUE, BOOL fEnableReleaseCallback = FALSE); +#endif + virtual ~CBaseAllocator(); + + DECLARE_IUNKNOWN + + // override this to publicise our interfaces + STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, __deref_out void **ppv); + + STDMETHODIMP SetProperties( + __in ALLOCATOR_PROPERTIES* pRequest, + __out ALLOCATOR_PROPERTIES* pActual); + + // return the properties actually being used on this allocator + STDMETHODIMP GetProperties( + __out ALLOCATOR_PROPERTIES* pProps); + + // override Commit to allocate memory. We handle the GetBuffer + //state changes + STDMETHODIMP Commit(); + + // override this to handle the memory freeing. We handle any outstanding + // GetBuffer calls + STDMETHODIMP Decommit(); + + // get container for a sample. Blocking, synchronous call to get the + // next free buffer (as represented by an IMediaSample interface). + // on return, the time etc properties will be invalid, but the buffer + // pointer and size will be correct. The two time parameters are + // optional and either may be NULL, they may alternatively be set to + // the start and end times the sample will have attached to it + // bPrevFramesSkipped is not used (used only by the video renderer's + // allocator where it affects quality management in direct draw). + + STDMETHODIMP GetBuffer(__deref_out IMediaSample **ppBuffer, + __in_opt REFERENCE_TIME * pStartTime, + __in_opt REFERENCE_TIME * pEndTime, + DWORD dwFlags); + + // final release of a CMediaSample will call this + STDMETHODIMP ReleaseBuffer(IMediaSample *pBuffer); + // obsolete:: virtual void PutOnFreeList(CMediaSample * pSample); + + STDMETHODIMP SetNotify(IMemAllocatorNotifyCallbackTemp *pNotify); + + STDMETHODIMP GetFreeCount(__out LONG *plBuffersFree); + + // Notify that a sample is available + void NotifySample(); + + // Notify that we're waiting for a sample + void SetWaiting() { m_lWaiting++; }; +}; + + +//===================================================================== +//===================================================================== +// Defines CMemAllocator +// +// this is an allocator based on CBaseAllocator that allocates sample +// buffers in main memory (from 'new'). You must call SetProperties +// before calling Commit. +// +// we don't free the memory when going into Decommit state. The simplest +// way to implement this without complicating CBaseAllocator is to +// have a Free() function, called to go into decommit state, that does +// nothing and a ReallyFree function called from our destructor that +// actually frees the memory. +//===================================================================== +//===================================================================== + +// Make me one from quartz.dll +STDAPI CreateMemoryAllocator(__deref_out IMemAllocator **ppAllocator); + +class CMemAllocator : public CBaseAllocator +{ + +protected: + + LPBYTE m_pBuffer; // combined memory for all buffers + + // override to free the memory when decommit completes + // - we actually do nothing, and save the memory until deletion. + void Free(void); + + // called from the destructor (and from Alloc if changing size/count) to + // actually free up the memory + void ReallyFree(void); + + // overriden to allocate the memory when commit called + HRESULT Alloc(void); + +public: + /* This goes in the factory template table to create new instances */ + static CUnknown *CreateInstance(__inout_opt LPUNKNOWN, __inout HRESULT *); + + STDMETHODIMP SetProperties( + __in ALLOCATOR_PROPERTIES* pRequest, + __out ALLOCATOR_PROPERTIES* pActual); + + CMemAllocator(__in_opt LPCTSTR , __inout_opt LPUNKNOWN, __inout HRESULT *); +#ifdef UNICODE + CMemAllocator(__in_opt LPCSTR , __inout_opt LPUNKNOWN, __inout HRESULT *); +#endif + ~CMemAllocator(); +}; + +// helper used by IAMovieSetup implementation +STDAPI +AMovieSetupRegisterFilter( const AMOVIESETUP_FILTER * const psetupdata + , IFilterMapper * pIFM + , BOOL bRegister ); + + +/////////////////////////////////////////////////////////////////////////// +// ------------------------------------------------------------------------ +// ------------------------------------------------------------------------ +// ------------------------------------------------------------------------ +// ------------------------------------------------------------------------ +/////////////////////////////////////////////////////////////////////////// + +#endif /* __FILTER__ */ + + + diff --git a/src/baseclasses/amvideo.cpp b/src/baseclasses/amvideo.cpp new file mode 100644 index 0000000..5c2755d --- /dev/null +++ b/src/baseclasses/amvideo.cpp @@ -0,0 +1,275 @@ +//------------------------------------------------------------------------------ +// File: AMVideo.cpp +// +// Desc: DirectShow base classes - implements helper functions for +// bitmap formats. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#include +#include + +// These are bit field masks for true colour devices + +const DWORD bits555[] = {0x007C00,0x0003E0,0x00001F}; +const DWORD bits565[] = {0x00F800,0x0007E0,0x00001F}; +const DWORD bits888[] = {0xFF0000,0x00FF00,0x0000FF}; + +// This maps bitmap subtypes into a bits per pixel value and also a +// name. unicode and ansi versions are stored because we have to +// return a pointer to a static string. +const struct { + const GUID *pSubtype; + WORD BitCount; + CHAR *pName; + WCHAR *wszName; +} BitCountMap[] = { &MEDIASUBTYPE_RGB1, 1, "RGB Monochrome", L"RGB Monochrome", + &MEDIASUBTYPE_RGB4, 4, "RGB VGA", L"RGB VGA", + &MEDIASUBTYPE_RGB8, 8, "RGB 8", L"RGB 8", + &MEDIASUBTYPE_RGB565, 16, "RGB 565 (16 bit)", L"RGB 565 (16 bit)", + &MEDIASUBTYPE_RGB555, 16, "RGB 555 (16 bit)", L"RGB 555 (16 bit)", + &MEDIASUBTYPE_RGB24, 24, "RGB 24", L"RGB 24", + &MEDIASUBTYPE_RGB32, 32, "RGB 32", L"RGB 32", + &MEDIASUBTYPE_ARGB32, 32, "ARGB 32", L"ARGB 32", + &MEDIASUBTYPE_Overlay, 0, "Overlay", L"Overlay", + &GUID_NULL, 0, "UNKNOWN", L"UNKNOWN" +}; + +// Return the size of the bitmap as defined by this header + +STDAPI_(DWORD) GetBitmapSize(const BITMAPINFOHEADER *pHeader) +{ + return DIBSIZE(*pHeader); +} + + +// This is called if the header has a 16 bit colour depth and needs to work +// out the detailed type from the bit fields (either RGB 565 or RGB 555) + +STDAPI_(const GUID) GetTrueColorType(const BITMAPINFOHEADER *pbmiHeader) +{ + BITMAPINFO *pbmInfo = (BITMAPINFO *) pbmiHeader; + ASSERT(pbmiHeader->biBitCount == 16); + + // If its BI_RGB then it's RGB 555 by default + + if (pbmiHeader->biCompression == BI_RGB) { + return MEDIASUBTYPE_RGB555; + } + + // Compare the bit fields with RGB 555 + + DWORD *pMask = (DWORD *) pbmInfo->bmiColors; + if (pMask[0] == bits555[0]) { + if (pMask[1] == bits555[1]) { + if (pMask[2] == bits555[2]) { + return MEDIASUBTYPE_RGB555; + } + } + } + + // Compare the bit fields with RGB 565 + + pMask = (DWORD *) pbmInfo->bmiColors; + if (pMask[0] == bits565[0]) { + if (pMask[1] == bits565[1]) { + if (pMask[2] == bits565[2]) { + return MEDIASUBTYPE_RGB565; + } + } + } + return GUID_NULL; +} + + +// Given a BITMAPINFOHEADER structure this returns the GUID sub type that is +// used to describe it in format negotiations. For example a video codec fills +// in the format block with a VIDEOINFO structure, it also fills in the major +// type with MEDIATYPE_VIDEO and the subtype with a GUID that matches the bit +// count, for example if it is an eight bit image then MEDIASUBTYPE_RGB8 + +STDAPI_(const GUID) GetBitmapSubtype(const BITMAPINFOHEADER *pbmiHeader) +{ + ASSERT(pbmiHeader); + + // If it's not RGB then create a GUID from the compression type + + if (pbmiHeader->biCompression != BI_RGB) { + if (pbmiHeader->biCompression != BI_BITFIELDS) { + FOURCCMap FourCCMap(pbmiHeader->biCompression); + return (const GUID) FourCCMap; + } + } + + // Map the RGB DIB bit depth to a image GUID + + switch(pbmiHeader->biBitCount) { + case 1 : return MEDIASUBTYPE_RGB1; + case 4 : return MEDIASUBTYPE_RGB4; + case 8 : return MEDIASUBTYPE_RGB8; + case 16 : return GetTrueColorType(pbmiHeader); + case 24 : return MEDIASUBTYPE_RGB24; + case 32 : return MEDIASUBTYPE_RGB32; + } + return GUID_NULL; +} + + +// Given a video bitmap subtype we return the number of bits per pixel it uses +// We return a WORD bit count as thats what the BITMAPINFOHEADER uses. If the +// GUID subtype is not found in the table we return an invalid USHRT_MAX + +STDAPI_(WORD) GetBitCount(const GUID *pSubtype) +{ + ASSERT(pSubtype); + const GUID *pMediaSubtype; + INT iPosition = 0; + + // Scan the mapping list seeing if the source GUID matches any known + // bitmap subtypes, the list is terminated by a GUID_NULL entry + + while (TRUE) { + pMediaSubtype = BitCountMap[iPosition].pSubtype; + if (IsEqualGUID(*pMediaSubtype,GUID_NULL)) { + return USHRT_MAX; + } + if (IsEqualGUID(*pMediaSubtype,*pSubtype)) { + return BitCountMap[iPosition].BitCount; + } + iPosition++; + } +} + + +// Given a bitmap subtype we return a description name that can be used for +// debug purposes. In a retail build this function still returns the names +// If the subtype isn't found in the lookup table we return string UNKNOWN + +int LocateSubtype(const GUID *pSubtype) +{ + ASSERT(pSubtype); + const GUID *pMediaSubtype; + INT iPosition = 0; + + // Scan the mapping list seeing if the source GUID matches any known + // bitmap subtypes, the list is terminated by a GUID_NULL entry + + while (TRUE) { + pMediaSubtype = BitCountMap[iPosition].pSubtype; + if (IsEqualGUID(*pMediaSubtype,*pSubtype) || + IsEqualGUID(*pMediaSubtype,GUID_NULL) + ) + { + break; + } + + iPosition++; + } + + return iPosition; +} + + + +STDAPI_(WCHAR *) GetSubtypeNameW(const GUID *pSubtype) +{ + return BitCountMap[LocateSubtype(pSubtype)].wszName; +} + +STDAPI_(CHAR *) GetSubtypeNameA(const GUID *pSubtype) +{ + return BitCountMap[LocateSubtype(pSubtype)].pName; +} + +#ifndef GetSubtypeName +#error wxutil.h should have defined GetSubtypeName +#endif +#undef GetSubtypeName + +// this is here for people that linked to it directly; most people +// would use the header file that picks the A or W version. +STDAPI_(CHAR *) GetSubtypeName(const GUID *pSubtype) +{ + return GetSubtypeNameA(pSubtype); +} + + +// The mechanism for describing a bitmap format is with the BITMAPINFOHEADER +// This is really messy to deal with because it invariably has fields that +// follow it holding bit fields, palettes and the rest. This function gives +// the number of bytes required to hold a VIDEOINFO that represents it. This +// count includes the prefix information (like the rcSource rectangle) the +// BITMAPINFOHEADER field, and any other colour information on the end. +// +// WARNING If you want to copy a BITMAPINFOHEADER into a VIDEOINFO always make +// sure that you use the HEADER macro because the BITMAPINFOHEADER field isn't +// right at the start of the VIDEOINFO (there are a number of other fields), +// +// CopyMemory(HEADER(pVideoInfo),pbmi,sizeof(BITMAPINFOHEADER)); +// + +STDAPI_(LONG) GetBitmapFormatSize(const BITMAPINFOHEADER *pHeader) +{ + // Everyone has this to start with this + LONG Size = SIZE_PREHEADER + pHeader->biSize; + + ASSERT(pHeader->biSize >= sizeof(BITMAPINFOHEADER)); + + // Does this format use a palette, if the number of colours actually used + // is zero then it is set to the maximum that are allowed for that colour + // depth (an example is 256 for eight bits). Truecolour formats may also + // pass a palette with them in which case the used count is non zero + + // This would scare me. + ASSERT(pHeader->biBitCount <= iPALETTE || pHeader->biClrUsed == 0); + + if (pHeader->biBitCount <= iPALETTE || pHeader->biClrUsed) { + LONG Entries = (DWORD) 1 << pHeader->biBitCount; + if (pHeader->biClrUsed) { + Entries = pHeader->biClrUsed; + } + Size += Entries * sizeof(RGBQUAD); + } + + // Truecolour formats may have a BI_BITFIELDS specifier for compression + // type which means that room for three DWORDs should be allocated that + // specify where in each pixel the RGB colour components may be found + + if (pHeader->biCompression == BI_BITFIELDS) { + Size += SIZE_MASKS; + } + + // A BITMAPINFO for a palettised image may also contain a palette map that + // provides the information to map from a source palette to a destination + // palette during a BitBlt for example, because this information is only + // ever processed during drawing you don't normally store the palette map + // nor have any way of knowing if it is present in the data structure + + return Size; +} + + +// Returns TRUE if the VIDEOINFO contains a palette + +STDAPI_(BOOL) ContainsPalette(const VIDEOINFOHEADER *pVideoInfo) +{ + if (PALETTISED(pVideoInfo) == FALSE) { + if (pVideoInfo->bmiHeader.biClrUsed == 0) { + return FALSE; + } + } + return TRUE; +} + + +// Return a pointer to the first entry in a palette + +STDAPI_(const RGBQUAD *) GetBitmapPalette(const VIDEOINFOHEADER *pVideoInfo) +{ + if (pVideoInfo->bmiHeader.biCompression == BI_BITFIELDS) { + return TRUECOLOR(pVideoInfo)->bmiColors; + } + return COLORS(pVideoInfo); +} diff --git a/src/baseclasses/checkbmi.h b/src/baseclasses/checkbmi.h new file mode 100644 index 0000000..9761dae --- /dev/null +++ b/src/baseclasses/checkbmi.h @@ -0,0 +1,120 @@ +// Copyright (c) 1992 - 1997 Microsoft Corporation. All Rights Reserved. + +#ifndef _CHECKBMI_H_ +#define _CHECKBMI_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +// Helper +__inline BOOL MultiplyCheckOverflow(DWORD a, DWORD b, __deref_out_range(==, a * b) DWORD *pab) { + *pab = a * b; + if ((a == 0) || (((*pab) / a) == b)) { + return TRUE; + } + return FALSE; +} + + +// Checks if the fields in a BITMAPINFOHEADER won't generate +// overlows and buffer overruns +// This is not a complete check and does not guarantee code using this structure will be secure +// from attack +// Bugs this is guarding against: +// 1. Total structure size calculation overflowing +// 2. biClrUsed > 256 for 8-bit palettized content +// 3. Total bitmap size in bytes overflowing +// 4. biSize < size of the base structure leading to accessessing random memory +// 5. Total structure size exceeding know size of data +// + +__success(return != 0) __inline BOOL ValidateBitmapInfoHeader( + const BITMAPINFOHEADER *pbmi, // pointer to structure to check + __out_range(>=, sizeof(BITMAPINFOHEADER)) DWORD cbSize // size of memory block containing structure +) +{ + DWORD dwWidthInBytes; + DWORD dwBpp; + DWORD dwWidthInBits; + DWORD dwHeight; + DWORD dwSizeImage; + DWORD dwClrUsed; + + // Reject bad parameters - do the size check first to avoid reading bad memory + if (cbSize < sizeof(BITMAPINFOHEADER) || + pbmi->biSize < sizeof(BITMAPINFOHEADER) || + pbmi->biSize > 4096) { + return FALSE; + } + + // Reject 0 size + if (pbmi->biWidth == 0 || pbmi->biHeight == 0) { + return FALSE; + } + + // Use bpp of 200 for validating against further overflows if not set for compressed format + dwBpp = 200; + + if (pbmi->biBitCount > dwBpp) { + return FALSE; + } + + // Strictly speaking abs can overflow so cast explicitly to DWORD + dwHeight = (DWORD)abs(pbmi->biHeight); + + if (!MultiplyCheckOverflow(dwBpp, (DWORD)pbmi->biWidth, &dwWidthInBits)) { + return FALSE; + } + + // Compute correct width in bytes - rounding up to 4 bytes + dwWidthInBytes = (dwWidthInBits / 8 + 3) & ~3; + + if (!MultiplyCheckOverflow(dwWidthInBytes, dwHeight, &dwSizeImage)) { + return FALSE; + } + + // Fail if total size is 0 - this catches indivual quantities being 0 + // Also don't allow huge values > 1GB which might cause arithmetic + // errors for users + if (dwSizeImage > 0x40000000 || + pbmi->biSizeImage > 0x40000000) { + return FALSE; + } + + // Fail if biClrUsed looks bad + if (pbmi->biClrUsed > 256) { + return FALSE; + } + + if (pbmi->biClrUsed == 0 && pbmi->biBitCount <= 8 && pbmi->biBitCount > 0) { + dwClrUsed = (1 << pbmi->biBitCount); + } else { + dwClrUsed = pbmi->biClrUsed; + } + + // Check total size + if (cbSize < pbmi->biSize + dwClrUsed * sizeof(RGBQUAD) + + (pbmi->biCompression == BI_BITFIELDS ? 3 * sizeof(DWORD) : 0)) { + return FALSE; + } + + // If it is RGB validate biSizeImage - lots of code assumes the size is correct + if (pbmi->biCompression == BI_RGB || pbmi->biCompression == BI_BITFIELDS) { + if (pbmi->biSizeImage != 0) { + DWORD dwBits = (DWORD)pbmi->biWidth * (DWORD)pbmi->biBitCount; + DWORD dwWidthInBytes = ((DWORD)((dwBits+31) & (~31)) / 8); + DWORD dwTotalSize = (DWORD)abs(pbmi->biHeight) * dwWidthInBytes; + if (dwTotalSize > pbmi->biSizeImage) { + return FALSE; + } + } + } + return TRUE; +} + +#ifdef __cplusplus +} +#endif + +#endif // _CHECKBMI_H_ diff --git a/src/baseclasses/combase.cpp b/src/baseclasses/combase.cpp new file mode 100644 index 0000000..9374b17 --- /dev/null +++ b/src/baseclasses/combase.cpp @@ -0,0 +1,265 @@ +//------------------------------------------------------------------------------ +// File: ComBase.cpp +// +// Desc: DirectShow base classes - implements class hierarchy for creating +// COM objects. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#include +#pragma warning( disable : 4514 ) // Disable warnings re unused inline functions + + +/* Define the static member variable */ + +LONG CBaseObject::m_cObjects = 0; + + +/* Constructor */ + +CBaseObject::CBaseObject(__in_opt LPCTSTR pName) +{ + /* Increment the number of active objects */ + InterlockedIncrement(&m_cObjects); + +#ifdef DEBUG + +#ifdef UNICODE + m_dwCookie = DbgRegisterObjectCreation(0, pName); +#else + m_dwCookie = DbgRegisterObjectCreation(pName, 0); +#endif + +#endif +} + +#ifdef UNICODE +CBaseObject::CBaseObject(const char *pName) +{ + /* Increment the number of active objects */ + InterlockedIncrement(&m_cObjects); + +#ifdef DEBUG + m_dwCookie = DbgRegisterObjectCreation(pName, 0); +#endif +} +#endif + +HINSTANCE hlibOLEAut32; + +/* Destructor */ + +CBaseObject::~CBaseObject() +{ + /* Decrement the number of objects active */ + if (InterlockedDecrement(&m_cObjects) == 0) { + if (hlibOLEAut32) { + FreeLibrary(hlibOLEAut32); + + hlibOLEAut32 = 0; + } + }; + + +#ifdef DEBUG + DbgRegisterObjectDestruction(m_dwCookie); +#endif +} + +static const TCHAR szOle32Aut[] = TEXT("OleAut32.dll"); + +HINSTANCE LoadOLEAut32() +{ + if (hlibOLEAut32 == 0) { + + hlibOLEAut32 = LoadLibrary(szOle32Aut); + } + + return hlibOLEAut32; +} + + +/* Constructor */ + +// We know we use "this" in the initialization list, we also know we don't modify *phr. +#pragma warning( disable : 4355 4100 ) +CUnknown::CUnknown(__in_opt LPCTSTR pName, __in_opt LPUNKNOWN pUnk) +: CBaseObject(pName) +/* Start the object with a reference count of zero - when the */ +/* object is queried for it's first interface this may be */ +/* incremented depending on whether or not this object is */ +/* currently being aggregated upon */ +, m_cRef(0) +/* Set our pointer to our IUnknown interface. */ +/* If we have an outer, use its, otherwise use ours. */ +/* This pointer effectivly points to the owner of */ +/* this object and can be accessed by the GetOwner() method. */ +, m_pUnknown( pUnk != 0 ? pUnk : reinterpret_cast( static_cast(this) ) ) + /* Why the double cast? Well, the inner cast is a type-safe cast */ + /* to pointer to a type from which we inherit. The second is */ + /* type-unsafe but works because INonDelegatingUnknown "behaves */ + /* like" IUnknown. (Only the names on the methods change.) */ +{ + // Everything we need to do has been done in the initializer list +} + +// This does the same as above except it has a useless HRESULT argument +// use the previous constructor, this is just left for compatibility... +CUnknown::CUnknown(__in_opt LPCTSTR pName, __in_opt LPUNKNOWN pUnk, __inout_opt HRESULT *phr) : + CBaseObject(pName), + m_cRef(0), + m_pUnknown( pUnk != 0 ? pUnk : reinterpret_cast( static_cast(this) ) ) +{ +} + +#ifdef UNICODE +CUnknown::CUnknown(__in_opt LPCSTR pName, __in_opt LPUNKNOWN pUnk) +: CBaseObject(pName), m_cRef(0), + m_pUnknown( pUnk != 0 ? pUnk : reinterpret_cast( static_cast(this) ) ) +{ } + +CUnknown::CUnknown(__in_opt LPCSTR pName, __in_opt LPUNKNOWN pUnk, __inout_opt HRESULT *phr) : + CBaseObject(pName), m_cRef(0), + m_pUnknown( pUnk != 0 ? pUnk : reinterpret_cast( static_cast(this) ) ) +{ } + +#endif + +#pragma warning( default : 4355 4100 ) + + +/* QueryInterface */ + +STDMETHODIMP CUnknown::NonDelegatingQueryInterface(REFIID riid, __deref_out void ** ppv) +{ + CheckPointer(ppv,E_POINTER); + ValidateReadWritePtr(ppv,sizeof(PVOID)); + + /* We know only about IUnknown */ + + if (riid == IID_IUnknown) { + GetInterface((LPUNKNOWN) (PNDUNKNOWN) this, ppv); + return NOERROR; + } else { + *ppv = NULL; + return E_NOINTERFACE; + } +} + +/* We have to ensure that we DON'T use a max macro, since these will typically */ +/* lead to one of the parameters being evaluated twice. Since we are worried */ +/* about concurrency, we can't afford to access the m_cRef twice since we can't */ +/* afford to run the risk that its value having changed between accesses. */ + +template inline static T ourmax( const T & a, const T & b ) +{ + return a > b ? a : b; +} + +/* AddRef */ + +STDMETHODIMP_(ULONG) CUnknown::NonDelegatingAddRef() +{ + LONG lRef = InterlockedIncrement( &m_cRef ); + ASSERT(lRef > 0); + DbgLog((LOG_MEMORY,3,TEXT(" Obj %d ref++ = %d"), + m_dwCookie, m_cRef)); + return ourmax(ULONG(m_cRef), 1ul); +} + + +/* Release */ + +STDMETHODIMP_(ULONG) CUnknown::NonDelegatingRelease() +{ + /* If the reference count drops to zero delete ourselves */ + + LONG lRef = InterlockedDecrement( &m_cRef ); + ASSERT(lRef >= 0); + + DbgLog((LOG_MEMORY,3,TEXT(" Object %d ref-- = %d"), + m_dwCookie, m_cRef)); + if (lRef == 0) { + + // COM rules say we must protect against re-entrancy. + // If we are an aggregator and we hold our own interfaces + // on the aggregatee, the QI for these interfaces will + // addref ourselves. So after doing the QI we must release + // a ref count on ourselves. Then, before releasing the + // private interface, we must addref ourselves. When we do + // this from the destructor here it will result in the ref + // count going to 1 and then back to 0 causing us to + // re-enter the destructor. Hence we add an extra refcount here + // once we know we will delete the object. + // for an example aggregator see filgraph\distrib.cpp. + + m_cRef++; + + delete this; + return ULONG(0); + } else { + // Don't touch m_cRef again even in this leg as the object + // may have just been released on another thread too + return ourmax(ULONG(lRef), 1ul); + } +} + + +/* Return an interface pointer to a requesting client + performing a thread safe AddRef as necessary */ + +STDAPI GetInterface(LPUNKNOWN pUnk, __out void **ppv) +{ + CheckPointer(ppv, E_POINTER); + *ppv = pUnk; + pUnk->AddRef(); + return NOERROR; +} + + +/* Compares two interfaces and returns TRUE if they are on the same object */ + +BOOL WINAPI IsEqualObject(IUnknown *pFirst, IUnknown *pSecond) +{ + /* Different objects can't have the same interface pointer for + any interface + */ + if (pFirst == pSecond) { + return TRUE; + } + /* OK - do it the hard way - check if they have the same + IUnknown pointers - a single object can only have one of these + */ + LPUNKNOWN pUnknown1; // Retrieve the IUnknown interface + LPUNKNOWN pUnknown2; // Retrieve the other IUnknown interface + HRESULT hr; // General OLE return code + + ASSERT(pFirst); + ASSERT(pSecond); + + /* See if the IUnknown pointers match */ + + hr = pFirst->QueryInterface(IID_IUnknown,(void **) &pUnknown1); + if (FAILED(hr)) { + return FALSE; + } + ASSERT(pUnknown1); + + /* Release the extra interface we hold */ + + pUnknown1->Release(); + + hr = pSecond->QueryInterface(IID_IUnknown,(void **) &pUnknown2); + if (FAILED(hr)) { + return FALSE; + } + ASSERT(pUnknown2); + + /* Release the extra interface we hold */ + + pUnknown2->Release(); + return (pUnknown1 == pUnknown2); +} + diff --git a/src/baseclasses/combase.h b/src/baseclasses/combase.h new file mode 100644 index 0000000..e1354f7 --- /dev/null +++ b/src/baseclasses/combase.h @@ -0,0 +1,300 @@ +//------------------------------------------------------------------------------ +// File: ComBase.h +// +// Desc: DirectShow base classes - defines a class hierarchy for creating +// COM objects. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +/* + +a. Derive your COM object from CUnknown + +b. Make a static CreateInstance function that takes an LPUNKNOWN, an HRESULT * + and a TCHAR *. The LPUNKNOWN defines the object to delegate IUnknown calls + to. The HRESULT * allows error codes to be passed around constructors and + the TCHAR * is a descriptive name that can be printed on the debugger. + + It is important that constructors only change the HRESULT * if they have + to set an ERROR code, if it was successful then leave it alone or you may + overwrite an error code from an object previously created. + + When you call a constructor the descriptive name should be in static store + as we do not copy the string. To stop large amounts of memory being used + in retail builds by all these static strings use the NAME macro, + + CMyFilter = new CImplFilter(NAME("My filter"),pUnknown,phr); + if (FAILED(hr)) { + return hr; + } + + In retail builds NAME(_x_) compiles to NULL, the base CBaseObject class + knows not to do anything with objects that don't have a name. + +c. Have a constructor for your object that passes the LPUNKNOWN, HRESULT * and + TCHAR * to the CUnknown constructor. You can set the HRESULT if you have an + error, or just simply pass it through to the constructor. + + The object creation will fail in the class factory if the HRESULT indicates + an error (ie FAILED(HRESULT) == TRUE) + +d. Create a FactoryTemplate with your object's class id and CreateInstance + function. + +Then (for each interface) either + +Multiple inheritance + +1. Also derive it from ISomeInterface +2. Include DECLARE_IUNKNOWN in your class definition to declare + implementations of QueryInterface, AddRef and Release that + call the outer unknown +3. Override NonDelegatingQueryInterface to expose ISomeInterface by + code something like + + if (riid == IID_ISomeInterface) { + return GetInterface((ISomeInterface *) this, ppv); + } else { + return CUnknown::NonDelegatingQueryInterface(riid, ppv); + } + +4. Declare and implement the member functions of ISomeInterface. + +or: Nested interfaces + +1. Declare a class derived from CUnknown +2. Include DECLARE_IUNKNOWN in your class definition +3. Override NonDelegatingQueryInterface to expose ISomeInterface by + code something like + + if (riid == IID_ISomeInterface) { + return GetInterface((ISomeInterface *) this, ppv); + } else { + return CUnknown::NonDelegatingQueryInterface(riid, ppv); + } + +4. Implement the member functions of ISomeInterface. Use GetOwner() to + access the COM object class. + +And in your COM object class: + +5. Make the nested class a friend of the COM object class, and declare + an instance of the nested class as a member of the COM object class. + + NOTE that because you must always pass the outer unknown and an hResult + to the CUnknown constructor you cannot use a default constructor, in + other words you will have to make the member variable a pointer to the + class and make a NEW call in your constructor to actually create it. + +6. override the NonDelegatingQueryInterface with code like this: + + if (riid == IID_ISomeInterface) { + return m_pImplFilter-> + NonDelegatingQueryInterface(IID_ISomeInterface, ppv); + } else { + return CUnknown::NonDelegatingQueryInterface(riid, ppv); + } + +You can have mixed classes which support some interfaces via multiple +inheritance and some via nested classes + +*/ + +#ifndef __COMBASE__ +#define __COMBASE__ + +// Filter Setup data structures no defined in axextend.idl + +typedef REGPINTYPES +AMOVIESETUP_MEDIATYPE, * PAMOVIESETUP_MEDIATYPE, * FAR LPAMOVIESETUP_MEDIATYPE; + +typedef REGFILTERPINS +AMOVIESETUP_PIN, * PAMOVIESETUP_PIN, * FAR LPAMOVIESETUP_PIN; + +typedef struct _AMOVIESETUP_FILTER +{ + const CLSID * clsID; + const WCHAR * strName; + DWORD dwMerit; + UINT nPins; + const AMOVIESETUP_PIN * lpPin; +} +AMOVIESETUP_FILTER, * PAMOVIESETUP_FILTER, * FAR LPAMOVIESETUP_FILTER; + +/* The DLLENTRY module initialises the module handle on loading */ + +extern HINSTANCE g_hInst; + +/* Version of IUnknown that is renamed to allow a class to support both + non delegating and delegating IUnknowns in the same COM object */ + +#ifndef INONDELEGATINGUNKNOWN_DEFINED +DECLARE_INTERFACE(INonDelegatingUnknown) +{ + STDMETHOD(NonDelegatingQueryInterface) (THIS_ REFIID, LPVOID *) PURE; + STDMETHOD_(ULONG, NonDelegatingAddRef)(THIS) PURE; + STDMETHOD_(ULONG, NonDelegatingRelease)(THIS) PURE; +}; +#define INONDELEGATINGUNKNOWN_DEFINED +#endif + +typedef INonDelegatingUnknown *PNDUNKNOWN; + + +/* This is the base object class that supports active object counting. As + part of the debug facilities we trace every time a C++ object is created + or destroyed. The name of the object has to be passed up through the class + derivation list during construction as you cannot call virtual functions + in the constructor. The downside of all this is that every single object + constructor has to take an object name parameter that describes it */ + +class CBaseObject +{ + +private: + + // Disable the copy constructor and assignment by default so you will get + // compiler errors instead of unexpected behaviour if you pass objects + // by value or assign objects. + CBaseObject(const CBaseObject& objectSrc); // no implementation + void operator=(const CBaseObject& objectSrc); // no implementation + +private: + static LONG m_cObjects; /* Total number of objects active */ + +protected: +#ifdef DEBUG + DWORD m_dwCookie; /* Cookie identifying this object */ +#endif + + +public: + + /* These increment and decrement the number of active objects */ + + CBaseObject(__in_opt LPCTSTR pName); +#ifdef UNICODE + CBaseObject(__in_opt LPCSTR pName); +#endif + ~CBaseObject(); + + /* Call this to find if there are any CUnknown derived objects active */ + + static LONG ObjectsActive() { + return m_cObjects; + }; +}; + + +/* An object that supports one or more COM interfaces will be based on + this class. It supports counting of total objects for DLLCanUnloadNow + support, and an implementation of the core non delegating IUnknown */ + +class AM_NOVTABLE CUnknown : public INonDelegatingUnknown, + public CBaseObject +{ +private: + const LPUNKNOWN m_pUnknown; /* Owner of this object */ + +protected: /* So we can override NonDelegatingRelease() */ + volatile LONG m_cRef; /* Number of reference counts */ + +public: + + CUnknown(__in_opt LPCTSTR pName, __in_opt LPUNKNOWN pUnk); + virtual ~CUnknown() {}; + + // This is redundant, just use the other constructor + // as we never touch the HRESULT in this anyway + CUnknown(__in_opt LPCTSTR Name, __in_opt LPUNKNOWN pUnk, __inout_opt HRESULT *phr); +#ifdef UNICODE + CUnknown(__in_opt LPCSTR pName, __in_opt LPUNKNOWN pUnk); + CUnknown(__in_opt LPCSTR pName, __in_opt LPUNKNOWN pUnk,__inout_opt HRESULT *phr); +#endif + + /* Return the owner of this object */ + + LPUNKNOWN GetOwner() const { + return m_pUnknown; + }; + + /* Called from the class factory to create a new instance, it is + pure virtual so it must be overriden in your derived class */ + + /* static CUnknown *CreateInstance(LPUNKNOWN, HRESULT *) */ + + /* Non delegating unknown implementation */ + + STDMETHODIMP NonDelegatingQueryInterface(REFIID, __deref_out void **); + STDMETHODIMP_(ULONG) NonDelegatingAddRef(); + STDMETHODIMP_(ULONG) NonDelegatingRelease(); +}; + +/* Return an interface pointer to a requesting client + performing a thread safe AddRef as necessary */ + +STDAPI GetInterface(LPUNKNOWN pUnk, __out void **ppv); + +/* A function that can create a new COM object */ + +typedef CUnknown *(CALLBACK *LPFNNewCOMObject)(__in_opt LPUNKNOWN pUnkOuter, __inout_opt HRESULT *phr); + +/* A function (can be NULL) which is called from the DLL entrypoint + routine for each factory template: + + bLoading - TRUE on DLL load, FALSE on DLL unload + rclsid - the m_ClsID of the entry +*/ +typedef void (CALLBACK *LPFNInitRoutine)(BOOL bLoading, const CLSID *rclsid); + +/* Create one of these per object class in an array so that + the default class factory code can create new instances */ + +class CFactoryTemplate { + +public: + + const WCHAR * m_Name; + const CLSID * m_ClsID; + LPFNNewCOMObject m_lpfnNew; + LPFNInitRoutine m_lpfnInit; + const AMOVIESETUP_FILTER * m_pAMovieSetup_Filter; + + BOOL IsClassID(REFCLSID rclsid) const { + return (IsEqualCLSID(*m_ClsID,rclsid)); + }; + + CUnknown *CreateInstance(__inout_opt LPUNKNOWN pUnk, __inout_opt HRESULT *phr) const { + CheckPointer(phr,NULL); + return m_lpfnNew(pUnk, phr); + }; +}; + + +/* You must override the (pure virtual) NonDelegatingQueryInterface to return + interface pointers (using GetInterface) to the interfaces your derived + class supports (the default implementation only supports IUnknown) */ + +#define DECLARE_IUNKNOWN \ + STDMETHODIMP QueryInterface(REFIID riid, __deref_out void **ppv) { \ + return GetOwner()->QueryInterface(riid,ppv); \ + }; \ + STDMETHODIMP_(ULONG) AddRef() { \ + return GetOwner()->AddRef(); \ + }; \ + STDMETHODIMP_(ULONG) Release() { \ + return GetOwner()->Release(); \ + }; + + + +HINSTANCE LoadOLEAut32(); + + +#endif /* __COMBASE__ */ + + + + diff --git a/src/baseclasses/dllentry.cpp b/src/baseclasses/dllentry.cpp new file mode 100644 index 0000000..134414e --- /dev/null +++ b/src/baseclasses/dllentry.cpp @@ -0,0 +1,351 @@ +//------------------------------------------------------------------------------ +// File: DlleEntry.cpp +// +// Desc: DirectShow base classes - implements classes used to support dll +// entry points for COM objects. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#include +#include + +#ifdef DEBUG +#ifdef UNICODE +#ifndef _UNICODE +#define _UNICODE +#endif // _UNICODE +#endif // UNICODE + +#include +#endif // DEBUG +#include + +extern CFactoryTemplate g_Templates[]; +extern int g_cTemplates; + +HINSTANCE g_hInst; + +// +// an instance of this is created by the DLLGetClassObject entrypoint +// it uses the CFactoryTemplate object it is given to support the +// IClassFactory interface + +class CClassFactory : public IClassFactory, public CBaseObject +{ + +private: + const CFactoryTemplate *const m_pTemplate; + + ULONG m_cRef; + + static int m_cLocked; +public: + CClassFactory(const CFactoryTemplate *); + + // IUnknown + STDMETHODIMP QueryInterface(REFIID riid, __deref_out void ** ppv); + STDMETHODIMP_(ULONG)AddRef(); + STDMETHODIMP_(ULONG)Release(); + + // IClassFactory + STDMETHODIMP CreateInstance(LPUNKNOWN pUnkOuter, REFIID riid, __deref_out void **pv); + STDMETHODIMP LockServer(BOOL fLock); + + // allow DLLGetClassObject to know about global server lock status + static BOOL IsLocked() { + return (m_cLocked > 0); + }; +}; + +// process-wide dll locked state +int CClassFactory::m_cLocked = 0; + +CClassFactory::CClassFactory(const CFactoryTemplate *pTemplate) +: CBaseObject(NAME("Class Factory")) +, m_cRef(0) +, m_pTemplate(pTemplate) +{ +} + + +STDMETHODIMP +CClassFactory::QueryInterface(REFIID riid,__deref_out void **ppv) +{ + CheckPointer(ppv,E_POINTER) + ValidateReadWritePtr(ppv,sizeof(PVOID)); + *ppv = NULL; + + // any interface on this object is the object pointer. + if ((riid == IID_IUnknown) || (riid == IID_IClassFactory)) { + *ppv = (LPVOID) this; + // AddRef returned interface pointer + ((LPUNKNOWN) *ppv)->AddRef(); + return NOERROR; + } + + return ResultFromScode(E_NOINTERFACE); +} + + +STDMETHODIMP_(ULONG) +CClassFactory::AddRef() +{ + return ++m_cRef; +} + +STDMETHODIMP_(ULONG) +CClassFactory::Release() +{ + LONG lRef = InterlockedDecrement((volatile LONG *)&m_cRef); + if (lRef == 0) { + delete this; + return 0; + } else { + return lRef; + } +} + +STDMETHODIMP +CClassFactory::CreateInstance( + LPUNKNOWN pUnkOuter, + REFIID riid, + __deref_out void **pv) +{ + CheckPointer(pv,E_POINTER) + ValidateReadWritePtr(pv,sizeof(void *)); + *pv = NULL; + + /* Enforce the normal OLE rules regarding interfaces and delegation */ + + if (pUnkOuter != NULL) { + if (IsEqualIID(riid,IID_IUnknown) == FALSE) { + *pv = NULL; + return ResultFromScode(E_NOINTERFACE); + } + } + + /* Create the new object through the derived class's create function */ + + HRESULT hr = NOERROR; + CUnknown *pObj = m_pTemplate->CreateInstance(pUnkOuter, &hr); + + if (pObj == NULL) { + *pv = NULL; + if (SUCCEEDED(hr)) { + hr = E_OUTOFMEMORY; + } + return hr; + } + + /* Delete the object if we got a construction error */ + + if (FAILED(hr)) { + delete pObj; + *pv = NULL; + return hr; + } + + /* Get a reference counted interface on the object */ + + /* We wrap the non-delegating QI with NDAddRef & NDRelease. */ + /* This protects any outer object from being prematurely */ + /* released by an inner object that may have to be created */ + /* in order to supply the requested interface. */ + pObj->NonDelegatingAddRef(); + hr = pObj->NonDelegatingQueryInterface(riid, pv); + pObj->NonDelegatingRelease(); + /* Note that if NonDelegatingQueryInterface fails, it will */ + /* not increment the ref count, so the NonDelegatingRelease */ + /* will drop the ref back to zero and the object will "self-*/ + /* destruct". Hence we don't need additional tidy-up code */ + /* to cope with NonDelegatingQueryInterface failing. */ + + if (SUCCEEDED(hr)) { + ASSERT(*pv); + } + + return hr; +} + +STDMETHODIMP +CClassFactory::LockServer(BOOL fLock) +{ + if (fLock) { + m_cLocked++; + } else { + m_cLocked--; + } + return NOERROR; +} + + +// --- COM entrypoints ----------------------------------------- + +//called by COM to get the class factory object for a given class +__control_entrypoint(DllExport) STDAPI +DllGetClassObject( + __in REFCLSID rClsID, + __in REFIID riid, + __deref_out void **pv) +{ + *pv = NULL; + if (!(riid == IID_IUnknown) && !(riid == IID_IClassFactory)) { + return E_NOINTERFACE; + } + + // traverse the array of templates looking for one with this + // class id + for (int i = 0; i < g_cTemplates; i++) { + const CFactoryTemplate * pT = &g_Templates[i]; + if (pT->IsClassID(rClsID)) { + + // found a template - make a class factory based on this + // template + + *pv = (LPVOID) (LPUNKNOWN) new CClassFactory(pT); + if (*pv == NULL) { + return E_OUTOFMEMORY; + } + ((LPUNKNOWN)*pv)->AddRef(); + return NOERROR; + } + } + return CLASS_E_CLASSNOTAVAILABLE; +} + +// +// Call any initialization routines +// +void +DllInitClasses(BOOL bLoading) +{ + int i; + + // traverse the array of templates calling the init routine + // if they have one + for (i = 0; i < g_cTemplates; i++) { + const CFactoryTemplate * pT = &g_Templates[i]; + if (pT->m_lpfnInit != NULL) { + (*pT->m_lpfnInit)(bLoading, pT->m_ClsID); + } + } + +} + +// called by COM to determine if this dll can be unloaded +// return ok unless there are outstanding objects or a lock requested +// by IClassFactory::LockServer +// +// CClassFactory has a static function that can tell us about the locks, +// and CCOMObject has a static function that can tell us about the active +// object count +STDAPI +DllCanUnloadNow() +{ + DbgLog((LOG_MEMORY,2,TEXT("DLLCanUnloadNow called - IsLocked = %d, Active objects = %d"), + CClassFactory::IsLocked(), + CBaseObject::ObjectsActive())); + + if (CClassFactory::IsLocked() || CBaseObject::ObjectsActive()) { + return S_FALSE; + } else { + return S_OK; + } +} + + +// --- standard WIN32 entrypoints -------------------------------------- + + +extern "C" void __cdecl __security_init_cookie(void); +extern "C" BOOL WINAPI _DllEntryPoint(HINSTANCE, ULONG, __inout_opt LPVOID); +#pragma comment(linker, "/merge:.CRT=.rdata") + +extern "C" +DECLSPEC_NOINLINE +BOOL +WINAPI +DllEntryPoint( + HINSTANCE hInstance, + ULONG ulReason, + __inout_opt LPVOID pv + ) +{ + if ( ulReason == DLL_PROCESS_ATTACH ) { + // Must happen before any other code is executed. Thankfully - it's re-entrant + __security_init_cookie(); + } + return _DllEntryPoint(hInstance, ulReason, pv); +} + + +DECLSPEC_NOINLINE +BOOL +WINAPI +_DllEntryPoint( + HINSTANCE hInstance, + ULONG ulReason, + __inout_opt LPVOID pv + ) +{ +#ifdef DEBUG + extern bool g_fDbgInDllEntryPoint; + g_fDbgInDllEntryPoint = true; +#endif + + switch (ulReason) + { + + case DLL_PROCESS_ATTACH: + DisableThreadLibraryCalls(hInstance); + DbgInitialise(hInstance); + + g_hInst = hInstance; + DllInitClasses(TRUE); + break; + + case DLL_PROCESS_DETACH: + DllInitClasses(FALSE); + +#ifdef DEBUG + if (CBaseObject::ObjectsActive()) { + DbgSetModuleLevel(LOG_MEMORY, 2); + TCHAR szInfo[512]; + extern TCHAR m_ModuleName[]; // Cut down module name + + TCHAR FullName[_MAX_PATH]; // Load the full path and module name + TCHAR *pName; // Searches from the end for a backslash + + GetModuleFileName(NULL,FullName,_MAX_PATH); + pName = _tcsrchr(FullName,'\\'); + if (pName == NULL) { + pName = FullName; + } else { + pName++; + } + + (void)StringCchPrintf(szInfo, NUMELMS(szInfo), TEXT("Executable: %s Pid %x Tid %x. "), + pName, GetCurrentProcessId(), GetCurrentThreadId()); + + (void)StringCchPrintf(szInfo+lstrlen(szInfo), NUMELMS(szInfo) - lstrlen(szInfo), TEXT("Module %s, %d objects left active!"), + m_ModuleName, CBaseObject::ObjectsActive()); + DbgAssert(szInfo, TEXT(__FILE__),__LINE__); + + // If running remotely wait for the Assert to be acknowledged + // before dumping out the object register + DbgDumpObjectRegister(); + } + DbgTerminate(); +#endif + break; + } + +#ifdef DEBUG + g_fDbgInDllEntryPoint = false; +#endif + return TRUE; +} + + diff --git a/src/baseclasses/dllsetup.cpp b/src/baseclasses/dllsetup.cpp new file mode 100644 index 0000000..7594e13 --- /dev/null +++ b/src/baseclasses/dllsetup.cpp @@ -0,0 +1,693 @@ +//------------------------------------------------------------------------------ +// File: DllSetup.cpp +// +// Desc: DirectShow base classes. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#include +#include + +//--------------------------------------------------------------------------- +// defines + +#define MAX_KEY_LEN 260 + + +//--------------------------------------------------------------------------- +// externally defined functions/variable + +extern int g_cTemplates; +extern CFactoryTemplate g_Templates[]; + +//--------------------------------------------------------------------------- +// +// EliminateSubKey +// +// Try to enumerate all keys under this one. +// if we find anything, delete it completely. +// Otherwise just delete it. +// +// note - this was pinched/duplicated from +// Filgraph\Mapper.cpp - so should it be in +// a lib somewhere? +// +//--------------------------------------------------------------------------- + +STDAPI +EliminateSubKey( HKEY hkey, LPCTSTR strSubKey ) +{ + HKEY hk; + if (0 == lstrlen(strSubKey) ) { + // defensive approach + return E_FAIL; + } + + LONG lreturn = RegOpenKeyEx( hkey + , strSubKey + , 0 + , MAXIMUM_ALLOWED + , &hk ); + + ASSERT( lreturn == ERROR_SUCCESS + || lreturn == ERROR_FILE_NOT_FOUND + || lreturn == ERROR_INVALID_HANDLE ); + + if( ERROR_SUCCESS == lreturn ) + { + // Keep on enumerating the first (zero-th) + // key and deleting that + + for( ; ; ) + { + TCHAR Buffer[MAX_KEY_LEN]; + DWORD dw = MAX_KEY_LEN; + FILETIME ft; + + lreturn = RegEnumKeyEx( hk + , 0 + , Buffer + , &dw + , NULL + , NULL + , NULL + , &ft); + + ASSERT( lreturn == ERROR_SUCCESS + || lreturn == ERROR_NO_MORE_ITEMS ); + + if( ERROR_SUCCESS == lreturn ) + { + EliminateSubKey(hk, Buffer); + } + else + { + break; + } + } + + RegCloseKey(hk); + RegDeleteKey(hkey, strSubKey); + } + + return NOERROR; +} + + +//--------------------------------------------------------------------------- +// +// AMovieSetupRegisterServer() +// +// registers specfied file "szFileName" as server for +// CLSID "clsServer". A description is also required. +// The ThreadingModel and ServerType are optional, as +// they default to InprocServer32 (i.e. dll) and Both. +// +//--------------------------------------------------------------------------- + +STDAPI +AMovieSetupRegisterServer( CLSID clsServer + , LPCWSTR szDescription + , LPCWSTR szFileName + , LPCWSTR szThreadingModel = L"Both" + , LPCWSTR szServerType = L"InprocServer32" ) +{ + // temp buffer + // + TCHAR achTemp[MAX_PATH]; + + // convert CLSID uuid to string and write + // out subkey as string - CLSID\{} + // + OLECHAR szCLSID[CHARS_IN_GUID]; + HRESULT hr = StringFromGUID2( clsServer + , szCLSID + , CHARS_IN_GUID ); + ASSERT( SUCCEEDED(hr) ); + + // create key + // + HKEY hkey; + (void)StringCchPrintf( achTemp, NUMELMS(achTemp), TEXT("CLSID\\%ls"), szCLSID ); + LONG lreturn = RegCreateKey( HKEY_CLASSES_ROOT + , (LPCTSTR)achTemp + , &hkey ); + if( ERROR_SUCCESS != lreturn ) + { + return AmHresultFromWin32(lreturn); + } + + // set description string + // + + (void)StringCchPrintf( achTemp, NUMELMS(achTemp), TEXT("%ls"), szDescription ); + lreturn = RegSetValue( hkey + , (LPCTSTR)NULL + , REG_SZ + , achTemp + , sizeof(achTemp) ); + if( ERROR_SUCCESS != lreturn ) + { + RegCloseKey( hkey ); + return AmHresultFromWin32(lreturn); + } + + // create CLSID\\{"CLSID"}\\"ServerType" key, + // using key to CLSID\\{"CLSID"} passed back by + // last call to RegCreateKey(). + // + HKEY hsubkey; + + (void)StringCchPrintf( achTemp, NUMELMS(achTemp), TEXT("%ls"), szServerType ); + lreturn = RegCreateKey( hkey + , achTemp + , &hsubkey ); + if( ERROR_SUCCESS != lreturn ) + { + RegCloseKey( hkey ); + return AmHresultFromWin32(lreturn); + } + + // set Server string + // + (void)StringCchPrintf( achTemp, NUMELMS(achTemp), TEXT("%ls"), szFileName ); + lreturn = RegSetValue( hsubkey + , (LPCTSTR)NULL + , REG_SZ + , (LPCTSTR)achTemp + , sizeof(TCHAR) * (lstrlen(achTemp)+1) ); + if( ERROR_SUCCESS != lreturn ) + { + RegCloseKey( hkey ); + RegCloseKey( hsubkey ); + return AmHresultFromWin32(lreturn); + } + + (void)StringCchPrintf( achTemp, NUMELMS(achTemp), TEXT("%ls"), szThreadingModel ); + lreturn = RegSetValueEx( hsubkey + , TEXT("ThreadingModel") + , 0L + , REG_SZ + , (CONST BYTE *)achTemp + , sizeof(TCHAR) * (lstrlen(achTemp)+1) ); + + // close hkeys + // + RegCloseKey( hkey ); + RegCloseKey( hsubkey ); + + // and return + // + return HRESULT_FROM_WIN32(lreturn); + +} + + +//--------------------------------------------------------------------------- +// +// AMovieSetupUnregisterServer() +// +// default ActiveMovie dll setup function +// - to use must be called from an exported +// function named DllRegisterServer() +// +//--------------------------------------------------------------------------- + +STDAPI +AMovieSetupUnregisterServer( CLSID clsServer ) +{ + // convert CLSID uuid to string and write + // out subkey CLSID\{} + // + OLECHAR szCLSID[CHARS_IN_GUID]; + HRESULT hr = StringFromGUID2( clsServer + , szCLSID + , CHARS_IN_GUID ); + ASSERT( SUCCEEDED(hr) ); + + TCHAR achBuffer[MAX_KEY_LEN]; + (void)StringCchPrintf( achBuffer, NUMELMS(achBuffer), TEXT("CLSID\\%ls"), szCLSID ); + + // delete subkey + // + + hr = EliminateSubKey( HKEY_CLASSES_ROOT, achBuffer ); + ASSERT( SUCCEEDED(hr) ); + + // return + // + return NOERROR; +} + + +//--------------------------------------------------------------------------- +// +// AMovieSetupRegisterFilter through IFilterMapper2 +// +//--------------------------------------------------------------------------- + +STDAPI +AMovieSetupRegisterFilter2( const AMOVIESETUP_FILTER * const psetupdata + , IFilterMapper2 * pIFM2 + , BOOL bRegister ) +{ + DbgLog((LOG_TRACE, 3, TEXT("= AMovieSetupRegisterFilter"))); + + // check we've got data + // + if( NULL == psetupdata ) return S_FALSE; + + + // unregister filter + // (as pins are subkeys of filter's CLSID key + // they do not need to be removed separately). + // + DbgLog((LOG_TRACE, 3, TEXT("= = unregister filter"))); + HRESULT hr = pIFM2->UnregisterFilter( + 0, // default category + 0, // default instance name + *psetupdata->clsID ); + + + if( bRegister ) + { + REGFILTER2 rf2; + rf2.dwVersion = 1; + rf2.dwMerit = psetupdata->dwMerit; + rf2.cPins = psetupdata->nPins; + rf2.rgPins = psetupdata->lpPin; + + // register filter + // + DbgLog((LOG_TRACE, 3, TEXT("= = register filter"))); + hr = pIFM2->RegisterFilter(*psetupdata->clsID + , psetupdata->strName + , 0 // moniker + , 0 // category + , NULL // instance + , &rf2); + } + + // handle one acceptable "error" - that + // of filter not being registered! + // (couldn't find a suitable #define'd + // name for the error!) + // + if( 0x80070002 == hr) + return NOERROR; + else + return hr; +} + + +//--------------------------------------------------------------------------- +// +// RegisterAllServers() +// +//--------------------------------------------------------------------------- + +STDAPI +RegisterAllServers( LPCWSTR szFileName, BOOL bRegister ) +{ + HRESULT hr = NOERROR; + + for( int i = 0; i < g_cTemplates; i++ ) + { + // get i'th template + // + const CFactoryTemplate *pT = &g_Templates[i]; + + DbgLog((LOG_TRACE, 2, TEXT("- - register %ls"), + (LPCWSTR)pT->m_Name )); + + // register CLSID and InprocServer32 + // + if( bRegister ) + { + hr = AMovieSetupRegisterServer( *(pT->m_ClsID) + , (LPCWSTR)pT->m_Name + , szFileName ); + } + else + { + hr = AMovieSetupUnregisterServer( *(pT->m_ClsID) ); + } + + // check final error for this pass + // and break loop if we failed + // + if( FAILED(hr) ) + break; + } + + return hr; +} + + +//--------------------------------------------------------------------------- +// +// AMovieDllRegisterServer2() +// +// default ActiveMovie dll setup function +// - to use must be called from an exported +// function named DllRegisterServer() +// +// this function is table driven using the +// static members of the CFactoryTemplate +// class defined in the dll. +// +// it registers the Dll as the InprocServer32 +// and then calls the IAMovieSetup.Register +// method. +// +//--------------------------------------------------------------------------- + +STDAPI +AMovieDllRegisterServer2( BOOL bRegister ) +{ + HRESULT hr = NOERROR; + + DbgLog((LOG_TRACE, 2, TEXT("AMovieDllRegisterServer2()"))); + + // get file name (where g_hInst is the + // instance handle of the filter dll) + // + WCHAR achFileName[MAX_PATH]; + + // WIN95 doesn't support GetModuleFileNameW + // + { + char achTemp[MAX_PATH]; + + DbgLog((LOG_TRACE, 2, TEXT("- get module file name"))); + + // g_hInst handle is set in our dll entry point. Make sure + // DllEntryPoint in dllentry.cpp is called + ASSERT(g_hInst != 0); + + if( 0 == GetModuleFileNameA( g_hInst + , achTemp + , sizeof(achTemp) ) ) + { + // we've failed! + DWORD dwerr = GetLastError(); + return AmHresultFromWin32(dwerr); + } + + MultiByteToWideChar( CP_ACP + , 0L + , achTemp + , lstrlenA(achTemp) + 1 + , achFileName + , NUMELMS(achFileName) ); + } + + // + // first registering, register all OLE servers + // + if( bRegister ) + { + DbgLog((LOG_TRACE, 2, TEXT("- register OLE Servers"))); + hr = RegisterAllServers( achFileName, TRUE ); + } + + // + // next, register/unregister all filters + // + + if( SUCCEEDED(hr) ) + { + // init is ref counted so call just in case + // we're being called cold. + // + DbgLog((LOG_TRACE, 2, TEXT("- CoInitialize"))); + hr = CoInitialize( (LPVOID)NULL ); + ASSERT( SUCCEEDED(hr) ); + + // get hold of IFilterMapper2 + // + DbgLog((LOG_TRACE, 2, TEXT("- obtain IFilterMapper2"))); + IFilterMapper2 *pIFM2 = 0; + IFilterMapper *pIFM = 0; + hr = CoCreateInstance( CLSID_FilterMapper2 + , NULL + , CLSCTX_INPROC_SERVER + , IID_IFilterMapper2 + , (void **)&pIFM2 ); + if(FAILED(hr)) + { + DbgLog((LOG_TRACE, 2, TEXT("- trying IFilterMapper instead"))); + + hr = CoCreateInstance( + CLSID_FilterMapper, + NULL, + CLSCTX_INPROC_SERVER, + IID_IFilterMapper, + (void **)&pIFM); + } + if( SUCCEEDED(hr) ) + { + // scan through array of CFactoryTemplates + // registering servers and filters. + // + DbgLog((LOG_TRACE, 2, TEXT("- register Filters"))); + for( int i = 0; i < g_cTemplates; i++ ) + { + // get i'th template + // + const CFactoryTemplate *pT = &g_Templates[i]; + + if( NULL != pT->m_pAMovieSetup_Filter ) + { + DbgLog((LOG_TRACE, 2, TEXT("- - register %ls"), (LPCWSTR)pT->m_Name )); + + if(pIFM2) + { + hr = AMovieSetupRegisterFilter2( pT->m_pAMovieSetup_Filter, pIFM2, bRegister ); + } + else + { + hr = AMovieSetupRegisterFilter( pT->m_pAMovieSetup_Filter, pIFM, bRegister ); + } + } + + // check final error for this pass + // and break loop if we failed + // + if( FAILED(hr) ) + break; + } + + // release interface + // + if(pIFM2) + pIFM2->Release(); + else + pIFM->Release(); + + } + + // and clear up + // + CoFreeUnusedLibraries(); + CoUninitialize(); + } + + // + // if unregistering, unregister all OLE servers + // + if( SUCCEEDED(hr) && !bRegister ) + { + DbgLog((LOG_TRACE, 2, TEXT("- register OLE Servers"))); + hr = RegisterAllServers( achFileName, FALSE ); + } + + DbgLog((LOG_TRACE, 2, TEXT("- return %0x"), hr)); + return hr; +} + + +//--------------------------------------------------------------------------- +// +// AMovieDllRegisterServer() +// +// default ActiveMovie dll setup function +// - to use must be called from an exported +// function named DllRegisterServer() +// +// this function is table driven using the +// static members of the CFactoryTemplate +// class defined in the dll. +// +// it registers the Dll as the InprocServer32 +// and then calls the IAMovieSetup.Register +// method. +// +//--------------------------------------------------------------------------- + + +STDAPI +AMovieDllRegisterServer( void ) +{ + HRESULT hr = NOERROR; + + // get file name (where g_hInst is the + // instance handle of the filter dll) + // + WCHAR achFileName[MAX_PATH]; + + { + // WIN95 doesn't support GetModuleFileNameW + // + char achTemp[MAX_PATH]; + + if( 0 == GetModuleFileNameA( g_hInst + , achTemp + , sizeof(achTemp) ) ) + { + // we've failed! + DWORD dwerr = GetLastError(); + return AmHresultFromWin32(dwerr); + } + + MultiByteToWideChar( CP_ACP + , 0L + , achTemp + , lstrlenA(achTemp) + 1 + , achFileName + , NUMELMS(achFileName) ); + } + + // scan through array of CFactoryTemplates + // registering servers and filters. + // + for( int i = 0; i < g_cTemplates; i++ ) + { + // get i'th template + // + const CFactoryTemplate *pT = &g_Templates[i]; + + // register CLSID and InprocServer32 + // + hr = AMovieSetupRegisterServer( *(pT->m_ClsID) + , (LPCWSTR)pT->m_Name + , achFileName ); + + // instantiate all servers and get hold of + // IAMovieSetup, if implemented, and call + // IAMovieSetup.Register() method + // + if( SUCCEEDED(hr) && (NULL != pT->m_lpfnNew) ) + { + // instantiate object + // + PAMOVIESETUP psetup; + hr = CoCreateInstance( *(pT->m_ClsID) + , 0 + , CLSCTX_INPROC_SERVER + , IID_IAMovieSetup + , reinterpret_cast(&psetup) ); + if( SUCCEEDED(hr) ) + { + hr = psetup->Unregister(); + if( SUCCEEDED(hr) ) + hr = psetup->Register(); + psetup->Release(); + } + else + { + if( (E_NOINTERFACE == hr ) + || (VFW_E_NEED_OWNER == hr ) ) + hr = NOERROR; + } + } + + // check final error for this pass + // and break loop if we failed + // + if( FAILED(hr) ) + break; + + } // end-for + + return hr; +} + + +//--------------------------------------------------------------------------- +// +// AMovieDllUnregisterServer() +// +// default ActiveMovie dll uninstall function +// - to use must be called from an exported +// function named DllRegisterServer() +// +// this function is table driven using the +// static members of the CFactoryTemplate +// class defined in the dll. +// +// it calls the IAMovieSetup.Unregister +// method and then unregisters the Dll +// as the InprocServer32 +// +//--------------------------------------------------------------------------- + +STDAPI +AMovieDllUnregisterServer() +{ + // initialize return code + // + HRESULT hr = NOERROR; + + // scan through CFactory template and unregister + // all OLE servers and filters. + // + for( int i = g_cTemplates; i--; ) + { + // get i'th template + // + const CFactoryTemplate *pT = &g_Templates[i]; + + // check method exists + // + if( NULL != pT->m_lpfnNew ) + { + // instantiate object + // + PAMOVIESETUP psetup; + hr = CoCreateInstance( *(pT->m_ClsID) + , 0 + , CLSCTX_INPROC_SERVER + , IID_IAMovieSetup + , reinterpret_cast(&psetup) ); + if( SUCCEEDED(hr) ) + { + hr = psetup->Unregister(); + psetup->Release(); + } + else + { + if( (E_NOINTERFACE == hr ) + || (VFW_E_NEED_OWNER == hr ) ) + hr = NOERROR; + } + } + + // unregister CLSID and InprocServer32 + // + if( SUCCEEDED(hr) ) + { + hr = AMovieSetupUnregisterServer( *(pT->m_ClsID) ); + } + + // check final error for this pass + // and break loop if we failed + // + if( FAILED(hr) ) + break; + } + + return hr; +} diff --git a/src/baseclasses/dllsetup.h b/src/baseclasses/dllsetup.h new file mode 100644 index 0000000..aaac2ec --- /dev/null +++ b/src/baseclasses/dllsetup.h @@ -0,0 +1,46 @@ +//------------------------------------------------------------------------------ +// File: DllSetup.h +// +// Desc: DirectShow base classes. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +// To be self registering, OLE servers must +// export functions named DllRegisterServer +// and DllUnregisterServer. To allow use of +// custom and default implementations the +// defaults are named AMovieDllRegisterServer +// and AMovieDllUnregisterServer. +// +// To the use the default implementation you +// must provide stub functions. +// +// i.e. STDAPI DllRegisterServer() +// { +// return AMovieDllRegisterServer(); +// } +// +// STDAPI DllUnregisterServer() +// { +// return AMovieDllUnregisterServer(); +// } +// +// +// AMovieDllRegisterServer calls IAMovieSetup.Register(), and +// AMovieDllUnregisterServer calls IAMovieSetup.Unregister(). + +STDAPI AMovieDllRegisterServer2( BOOL ); +STDAPI AMovieDllRegisterServer(); +STDAPI AMovieDllUnregisterServer(); + +// helper functions +STDAPI EliminateSubKey( HKEY, LPCTSTR ); + + +STDAPI +AMovieSetupRegisterFilter2( const AMOVIESETUP_FILTER * const psetupdata + , IFilterMapper2 * pIFM2 + , BOOL bRegister ); + diff --git a/src/baseclasses/fourcc.h b/src/baseclasses/fourcc.h new file mode 100644 index 0000000..f4f71e9 --- /dev/null +++ b/src/baseclasses/fourcc.h @@ -0,0 +1,101 @@ +//------------------------------------------------------------------------------ +// File: FourCC.h +// +// Desc: DirectShow base classes. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +// FOURCCMap +// +// provides a mapping between old-style multimedia format DWORDs +// and new-style GUIDs. +// +// A range of 4 billion GUIDs has been allocated to ensure that this +// mapping can be done straightforwardly one-to-one in both directions. +// +// January 95 + + +#ifndef __FOURCC__ +#define __FOURCC__ + + +// Multimedia format types are marked with DWORDs built from four 8-bit +// chars and known as FOURCCs. New multimedia AM_MEDIA_TYPE definitions include +// a subtype GUID. In order to simplify the mapping, GUIDs in the range: +// XXXXXXXX-0000-0010-8000-00AA00389B71 +// are reserved for FOURCCs. + +class FOURCCMap : public GUID +{ + +public: + FOURCCMap(); + FOURCCMap(DWORD Fourcc); + FOURCCMap(const GUID *); + + + DWORD GetFOURCC(void); + void SetFOURCC(DWORD fourcc); + void SetFOURCC(const GUID *); + +private: + void InitGUID(); +}; + +#define GUID_Data2 0 +#define GUID_Data3 0x10 +#define GUID_Data4_1 0xaa000080 +#define GUID_Data4_2 0x719b3800 + +inline void +FOURCCMap::InitGUID() { + Data2 = GUID_Data2; + Data3 = GUID_Data3; + ((DWORD *)Data4)[0] = GUID_Data4_1; + ((DWORD *)Data4)[1] = GUID_Data4_2; +} + +inline +FOURCCMap::FOURCCMap() { + InitGUID(); + SetFOURCC( DWORD(0)); +} + +inline +FOURCCMap::FOURCCMap(DWORD fourcc) +{ + InitGUID(); + SetFOURCC(fourcc); +} + +inline +FOURCCMap::FOURCCMap(const GUID * pGuid) +{ + InitGUID(); + SetFOURCC(pGuid); +} + +inline void +FOURCCMap::SetFOURCC(const GUID * pGuid) +{ + FOURCCMap * p = (FOURCCMap*) pGuid; + SetFOURCC(p->GetFOURCC()); +} + +inline void +FOURCCMap::SetFOURCC(DWORD fourcc) +{ + Data1 = fourcc; +} + +inline DWORD +FOURCCMap::GetFOURCC(void) +{ + return Data1; +} + +#endif /* __FOURCC__ */ + diff --git a/src/baseclasses/measure.h b/src/baseclasses/measure.h new file mode 100644 index 0000000..0babc86 --- /dev/null +++ b/src/baseclasses/measure.h @@ -0,0 +1,222 @@ +//------------------------------------------------------------------------------ +// File: Measure.h +// +// Desc: DirectShow base classes. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +/* + The idea is to pepper the source code with interesting measurements and + have the last few thousand of these recorded in a circular buffer that + can be post-processed to give interesting numbers. + + WHAT THE LOG LOOKS LIKE: + + Time (sec) Type Delta Incident_Name + 0.055,41 NOTE -. Incident Nine - Another note + 0.055,42 NOTE 0.000,01 Incident Nine - Another note + 0.055,44 NOTE 0.000,02 Incident Nine - Another note + 0.055,45 STOP -. Incident Eight - Also random + 0.055,47 START -. Incident Seven - Random + 0.055,49 NOTE 0.000,05 Incident Nine - Another note + ------- ---------------- + 0.125,60 STOP 0.000,03 Msr_Stop + 0.125,62 START -. Msr_Start + 0.125,63 START -. Incident Two - Start/Stop + 0.125,65 STOP 0.000,03 Msr_Start + 0.125,66 START -. Msr_Stop + 0.125,68 STOP 0.000,05 Incident Two - Start/Stop + 0.125,70 STOP 0.000,04 Msr_Stop + 0.125,72 START -. Msr_Start + 0.125,73 START -. Incident Two - Start/Stop + 0.125,75 STOP 0.000,03 Msr_Start + 0.125,77 START -. Msr_Stop + 0.125,78 STOP 0.000,05 Incident Two - Start/Stop + 0.125,80 STOP 0.000,03 Msr_Stop + 0.125,81 NOTE -. Incident Three - single Note + 0.125,83 START -. Incident Four - Start, no stop + 0.125,85 START -. Incident Five - Single Start/Stop + 0.125,87 STOP 0.000,02 Incident Five - Single Start/Stop + +Number Average StdDev Smallest Largest Incident_Name + 10 0.000,58 0.000,10 0.000,55 0.000,85 Incident One - Note + 50 0.000,05 0.000,00 0.000,05 0.000,05 Incident Two - Start/Stop + 1 -. -. -. -. Incident Three - single Note + 0 -. -. -. -. Incident Four - Start, no stop + 1 0.000,02 -. 0.000,02 0.000,02 Incident Five - Single Start/Stop + 0 -. -. -. -. Incident Six - zero occurrences + 100 0.000,25 0.000,12 0.000,02 0.000,62 Incident Seven - Random + 100 0.000,79 0.000,48 0.000,02 0.001,92 Incident Eight - Also random + 5895 0.000,01 0.000,01 0.000,01 0.000,56 Incident Nine - Another note + 10 0.000,03 0.000,00 0.000,03 0.000,04 Msr_Note + 50 0.000,03 0.000,00 0.000,03 0.000,04 Msr_Start + 50 0.000,04 0.000,03 0.000,03 0.000,31 Msr_Stop + + WHAT IT MEANS: + The log shows what happened and when. Each line shows the time at which + something happened (see WHAT YOU CODE below) what it was that happened + and (if approporate) the time since the corresponding previous event + (that's the delta column). + + The statistics show how many times each event occurred, what the average + delta time was, also the standard deviation, largest and smalles delta. + + WHAT YOU CODE: + + Before anything else executes: - register your ids + + int id1 = Msr_Register("Incident One - Note"); + int id2 = Msr_Register("Incident Two - Start/Stop"); + int id3 = Msr_Register("Incident Three - single Note"); + etc. + + At interesting moments: + + // To measure a repetitive event - e.g. end of bitblt to screen + Msr_Note(Id9); // e.g. "video frame hiting the screen NOW!" + + or + + // To measure an elapsed time e.g. time taken to decode an MPEG B-frame + Msr_Start(Id2); // e.g. "Starting to decode MPEG B-frame" + . . . + MsrStop(Id2); // "Finished MPEG decode" + + At the end: + + HANDLE hFile; + hFile = CreateFile("Perf.log", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL); + Msr_Dump(hFile); // This writes the log out to the file + CloseHandle(hFile); + + or + + Msr_Dump(NULL); // This writes it to DbgLog((LOG_TRACE,0, ... )); + // but if you are writing it out to the debugger + // then the times are probably all garbage because + // the debugger can make things run awfully slow. + + A given id should be used either for start / stop or Note calls. If Notes + are mixed in with Starts and Stops their statistics will be gibberish. + + If you code the calls in upper case i.e. MSR_START(idMunge); then you get + macros which will turn into nothing unless PERF is defined. + + You can reset the statistical counts for a given id by calling Reset(Id). + They are reset by default at the start. + It logs Reset as a special incident, so you can see it in the log. + + The log is a circular buffer in storage (to try to minimise disk I/O). + It overwrites the oldest entries once full. The statistics include ALL + incidents since the last Reset, whether still visible in the log or not. +*/ + +#ifndef __MEASURE__ +#define __MEASURE__ + +#ifdef PERF +#define MSR_INIT() Msr_Init() +#define MSR_TERMINATE() Msr_Terminate() +#define MSR_REGISTER(a) Msr_Register(a) +#define MSR_RESET(a) Msr_Reset(a) +#define MSR_CONTROL(a) Msr_Control(a) +#define MSR_START(a) Msr_Start(a) +#define MSR_STOP(a) Msr_Stop(a) +#define MSR_NOTE(a) Msr_Note(a) +#define MSR_INTEGER(a,b) Msr_Integer(a,b) +#define MSR_DUMP(a) Msr_Dump(a) +#define MSR_DUMPSTATS(a) Msr_DumpStats(a) +#else +#define MSR_INIT() ((void)0) +#define MSR_TERMINATE() ((void)0) +#define MSR_REGISTER(a) 0 +#define MSR_RESET(a) ((void)0) +#define MSR_CONTROL(a) ((void)0) +#define MSR_START(a) ((void)0) +#define MSR_STOP(a) ((void)0) +#define MSR_NOTE(a) ((void)0) +#define MSR_INTEGER(a,b) ((void)0) +#define MSR_DUMP(a) ((void)0) +#define MSR_DUMPSTATS(a) ((void)0) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// This must be called first - (called by the DllEntry) + +void WINAPI Msr_Init(void); + + +// Call this last to clean up (or just let it fall off the end - who cares?) + +void WINAPI Msr_Terminate(void); + + +// Call this to get an Id for an "incident" that you can pass to Start, Stop or Note +// everything that's logged is called an "incident". + +int WINAPI Msr_Register(__in LPTSTR Incident); + + +// Reset the statistical counts for an incident + +void WINAPI Msr_Reset(int Id); + + +// Reset all the counts for all incidents +#define MSR_RESET_ALL 0 +#define MSR_PAUSE 1 +#define MSR_RUN 2 + +void WINAPI Msr_Control(int iAction); + + +// log the start of an operation + +void WINAPI Msr_Start(int Id); + + +// log the end of an operation + +void WINAPI Msr_Stop(int Id); + + +// log a one-off or repetitive operation + +void WINAPI Msr_Note(int Id); + + +// log an integer (on which we can see statistics later) +void WINAPI Msr_Integer(int Id, int n); + + +// print out all the vaialable log (it may have wrapped) and then the statistics. +// When the log wraps you lose log but the statistics are still complete. +// hFIle==NULL => use DbgLog +// otherwise hFile must have come from CreateFile or OpenFile. + +void WINAPI Msr_Dump(HANDLE hFile); + + +// just dump the statistics - never mind the log + +void WINAPI Msr_DumpStats(HANDLE hFile); + +// Type definitions in case you want to declare a pointer to the dump functions +// (makes it a trifle easier to do dynamic linking +// i.e. LoadModule, GetProcAddress and call that) + +// Typedefs so can declare MSR_DUMPPROC *MsrDumpStats; or whatever +typedef void WINAPI MSR_DUMPPROC(HANDLE hFile); +typedef void WINAPI MSR_CONTROLPROC(int iAction); + + +#ifdef __cplusplus +} +#endif + +#endif // __MEASURE__ diff --git a/src/baseclasses/mtype.cpp b/src/baseclasses/mtype.cpp new file mode 100644 index 0000000..fa0e39e --- /dev/null +++ b/src/baseclasses/mtype.cpp @@ -0,0 +1,478 @@ +//------------------------------------------------------------------------------ +// File: MType.cpp +// +// Desc: DirectShow base classes - implements a class that holds and +// manages media type information. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +// helper class that derived pin objects can use to compare media +// types etc. Has same data members as the struct AM_MEDIA_TYPE defined +// in the streams IDL file, but also has (non-virtual) functions + +#include +#include + +CMediaType::~CMediaType(){ + FreeMediaType(*this); +} + + +CMediaType::CMediaType() +{ + InitMediaType(); +} + + +CMediaType::CMediaType(const GUID * type) +{ + InitMediaType(); + majortype = *type; +} + + +// copy constructor does a deep copy of the format block + +CMediaType::CMediaType(const AM_MEDIA_TYPE& rt, __out_opt HRESULT* phr) +{ + HRESULT hr = CopyMediaType(this, &rt); + if (FAILED(hr) && (NULL != phr)) { + *phr = hr; + } +} + + +CMediaType::CMediaType(const CMediaType& rt, __out_opt HRESULT* phr) +{ + HRESULT hr = CopyMediaType(this, &rt); + if (FAILED(hr) && (NULL != phr)) { + *phr = hr; + } +} + + +// this class inherits publicly from AM_MEDIA_TYPE so the compiler could generate +// the following assignment operator itself, however it could introduce some +// memory conflicts and leaks in the process because the structure contains +// a dynamically allocated block (pbFormat) which it will not copy correctly + +CMediaType& +CMediaType::operator=(const AM_MEDIA_TYPE& rt) +{ + Set(rt); + return *this; +} + +CMediaType& +CMediaType::operator=(const CMediaType& rt) +{ + *this = (AM_MEDIA_TYPE &) rt; + return *this; +} + +BOOL +CMediaType::operator == (const CMediaType& rt) const +{ + // I don't believe we need to check sample size or + // temporal compression flags, since I think these must + // be represented in the type, subtype and format somehow. They + // are pulled out as separate flags so that people who don't understand + // the particular format representation can still see them, but + // they should duplicate information in the format block. + + return ((IsEqualGUID(majortype,rt.majortype) == TRUE) && + (IsEqualGUID(subtype,rt.subtype) == TRUE) && + (IsEqualGUID(formattype,rt.formattype) == TRUE) && + (cbFormat == rt.cbFormat) && + ( (cbFormat == 0) || + pbFormat != NULL && rt.pbFormat != NULL && + (memcmp(pbFormat, rt.pbFormat, cbFormat) == 0))); +} + + +BOOL +CMediaType::operator != (const CMediaType& rt) const +{ + /* Check to see if they are equal */ + + if (*this == rt) { + return FALSE; + } + return TRUE; +} + + +HRESULT +CMediaType::Set(const CMediaType& rt) +{ + return Set((AM_MEDIA_TYPE &) rt); +} + + +HRESULT +CMediaType::Set(const AM_MEDIA_TYPE& rt) +{ + if (&rt != this) { + FreeMediaType(*this); + HRESULT hr = CopyMediaType(this, &rt); + if (FAILED(hr)) { + return E_OUTOFMEMORY; + } + } + + return S_OK; +} + + +BOOL +CMediaType::IsValid() const +{ + return (!IsEqualGUID(majortype,GUID_NULL)); +} + + +void +CMediaType::SetType(const GUID* ptype) +{ + majortype = *ptype; +} + + +void +CMediaType::SetSubtype(const GUID* ptype) +{ + subtype = *ptype; +} + + +ULONG +CMediaType::GetSampleSize() const { + if (IsFixedSize()) { + return lSampleSize; + } else { + return 0; + } +} + + +void +CMediaType::SetSampleSize(ULONG sz) { + if (sz == 0) { + SetVariableSize(); + } else { + bFixedSizeSamples = TRUE; + lSampleSize = sz; + } +} + + +void +CMediaType::SetVariableSize() { + bFixedSizeSamples = FALSE; +} + + +void +CMediaType::SetTemporalCompression(BOOL bCompressed) { + bTemporalCompression = bCompressed; +} + +BOOL +CMediaType::SetFormat(__in_bcount(cb) BYTE * pformat, ULONG cb) +{ + if (NULL == AllocFormatBuffer(cb)) + return(FALSE); + + ASSERT(pbFormat); + memcpy(pbFormat, pformat, cb); + return(TRUE); +} + + +// set the type of the media type format block, this type defines what you +// will actually find in the format pointer. For example FORMAT_VideoInfo or +// FORMAT_WaveFormatEx. In the future this may be an interface pointer to a +// property set. Before sending out media types this should be filled in. + +void +CMediaType::SetFormatType(const GUID *pformattype) +{ + formattype = *pformattype; +} + + +// reset the format buffer + +void CMediaType::ResetFormatBuffer() +{ + if (cbFormat) { + CoTaskMemFree((PVOID)pbFormat); + } + cbFormat = 0; + pbFormat = NULL; +} + + +// allocate length bytes for the format and return a read/write pointer +// If we cannot allocate the new block of memory we return NULL leaving +// the original block of memory untouched (as does ReallocFormatBuffer) + +BYTE* +CMediaType::AllocFormatBuffer(ULONG length) +{ + ASSERT(length); + + // do the types have the same buffer size + + if (cbFormat == length) { + return pbFormat; + } + + // allocate the new format buffer + + BYTE *pNewFormat = (PBYTE)CoTaskMemAlloc(length); + if (pNewFormat == NULL) { + if (length <= cbFormat) return pbFormat; //reuse the old block anyway. + return NULL; + } + + // delete the old format + + if (cbFormat != 0) { + ASSERT(pbFormat); + CoTaskMemFree((PVOID)pbFormat); + } + + cbFormat = length; + pbFormat = pNewFormat; + return pbFormat; +} + + +// reallocate length bytes for the format and return a read/write pointer +// to it. We keep as much information as we can given the new buffer size +// if this fails the original format buffer is left untouched. The caller +// is responsible for ensuring the size of memory required is non zero + +BYTE* +CMediaType::ReallocFormatBuffer(ULONG length) +{ + ASSERT(length); + + // do the types have the same buffer size + + if (cbFormat == length) { + return pbFormat; + } + + // allocate the new format buffer + + BYTE *pNewFormat = (PBYTE)CoTaskMemAlloc(length); + if (pNewFormat == NULL) { + if (length <= cbFormat) return pbFormat; //reuse the old block anyway. + return NULL; + } + + // copy any previous format (or part of if new is smaller) + // delete the old format and replace with the new one + + if (cbFormat != 0) { + ASSERT(pbFormat); + memcpy(pNewFormat,pbFormat,min(length,cbFormat)); + CoTaskMemFree((PVOID)pbFormat); + } + + cbFormat = length; + pbFormat = pNewFormat; + return pNewFormat; +} + +// initialise a media type structure + +void CMediaType::InitMediaType() +{ + ZeroMemory((PVOID)this, sizeof(*this)); + lSampleSize = 1; + bFixedSizeSamples = TRUE; +} + + +// a partially specified media type can be passed to IPin::Connect +// as a constraint on the media type used in the connection. +// the type, subtype or format type can be null. +BOOL +CMediaType::IsPartiallySpecified(void) const +{ + if ((majortype == GUID_NULL) || + (formattype == GUID_NULL)) { + return TRUE; + } else { + return FALSE; + } +} + +BOOL +CMediaType::MatchesPartial(const CMediaType* ppartial) const +{ + if ((ppartial->majortype != GUID_NULL) && + (majortype != ppartial->majortype)) { + return FALSE; + } + if ((ppartial->subtype != GUID_NULL) && + (subtype != ppartial->subtype)) { + return FALSE; + } + + if (ppartial->formattype != GUID_NULL) { + // if the format block is specified then it must match exactly + if (formattype != ppartial->formattype) { + return FALSE; + } + if (cbFormat != ppartial->cbFormat) { + return FALSE; + } + if ((cbFormat != 0) && + (memcmp(pbFormat, ppartial->pbFormat, cbFormat) != 0)) { + return FALSE; + } + } + + return TRUE; + +} + + + +// general purpose function to delete a heap allocated AM_MEDIA_TYPE structure +// which is useful when calling IEnumMediaTypes::Next as the interface +// implementation allocates the structures which you must later delete +// the format block may also be a pointer to an interface to release + +void WINAPI DeleteMediaType(__inout_opt AM_MEDIA_TYPE *pmt) +{ + // allow NULL pointers for coding simplicity + + if (pmt == NULL) { + return; + } + + FreeMediaType(*pmt); + CoTaskMemFree((PVOID)pmt); +} + + +// this also comes in useful when using the IEnumMediaTypes interface so +// that you can copy a media type, you can do nearly the same by creating +// a CMediaType object but as soon as it goes out of scope the destructor +// will delete the memory it allocated (this takes a copy of the memory) + +AM_MEDIA_TYPE * WINAPI CreateMediaType(AM_MEDIA_TYPE const *pSrc) +{ + ASSERT(pSrc); + + // Allocate a block of memory for the media type + + AM_MEDIA_TYPE *pMediaType = + (AM_MEDIA_TYPE *)CoTaskMemAlloc(sizeof(AM_MEDIA_TYPE)); + + if (pMediaType == NULL) { + return NULL; + } + // Copy the variable length format block + + HRESULT hr = CopyMediaType(pMediaType,pSrc); + if (FAILED(hr)) { + CoTaskMemFree((PVOID)pMediaType); + return NULL; + } + + return pMediaType; +} + + +// Copy 1 media type to another + +HRESULT WINAPI CopyMediaType(__out AM_MEDIA_TYPE *pmtTarget, const AM_MEDIA_TYPE *pmtSource) +{ + // We'll leak if we copy onto one that already exists - there's one + // case we can check like that - copying to itself. + ASSERT(pmtSource != pmtTarget); + *pmtTarget = *pmtSource; + if (pmtSource->cbFormat != 0) { + ASSERT(pmtSource->pbFormat != NULL); + pmtTarget->pbFormat = (PBYTE)CoTaskMemAlloc(pmtSource->cbFormat); + if (pmtTarget->pbFormat == NULL) { + pmtTarget->cbFormat = 0; + return E_OUTOFMEMORY; + } else { + CopyMemory((PVOID)pmtTarget->pbFormat, (PVOID)pmtSource->pbFormat, + pmtTarget->cbFormat); + } + } + if (pmtTarget->pUnk != NULL) { + pmtTarget->pUnk->AddRef(); + } + + return S_OK; +} + +// Free an existing media type (ie free resources it holds) + +void WINAPI FreeMediaType(__inout AM_MEDIA_TYPE& mt) +{ + if (mt.cbFormat != 0) { + CoTaskMemFree((PVOID)mt.pbFormat); + + // Strictly unnecessary but tidier + mt.cbFormat = 0; + mt.pbFormat = NULL; + } + if (mt.pUnk != NULL) { + mt.pUnk->Release(); + mt.pUnk = NULL; + } +} + +// Initialize a media type from a WAVEFORMATEX + +STDAPI CreateAudioMediaType( + const WAVEFORMATEX *pwfx, + __out AM_MEDIA_TYPE *pmt, + BOOL bSetFormat +) +{ + pmt->majortype = MEDIATYPE_Audio; + if (pwfx->wFormatTag == WAVE_FORMAT_EXTENSIBLE) { + pmt->subtype = ((PWAVEFORMATEXTENSIBLE)pwfx)->SubFormat; + } else { + pmt->subtype = FOURCCMap(pwfx->wFormatTag); + } + pmt->formattype = FORMAT_WaveFormatEx; + pmt->bFixedSizeSamples = TRUE; + pmt->bTemporalCompression = FALSE; + pmt->lSampleSize = pwfx->nBlockAlign; + pmt->pUnk = NULL; + if (bSetFormat) { + if (pwfx->wFormatTag == WAVE_FORMAT_PCM) { + pmt->cbFormat = sizeof(WAVEFORMATEX); + } else { + pmt->cbFormat = sizeof(WAVEFORMATEX) + pwfx->cbSize; + } + pmt->pbFormat = (PBYTE)CoTaskMemAlloc(pmt->cbFormat); + if (pmt->pbFormat == NULL) { + return E_OUTOFMEMORY; + } + if (pwfx->wFormatTag == WAVE_FORMAT_PCM) { + CopyMemory(pmt->pbFormat, pwfx, sizeof(PCMWAVEFORMAT)); + ((WAVEFORMATEX *)pmt->pbFormat)->cbSize = 0; + } else { + CopyMemory(pmt->pbFormat, pwfx, pmt->cbFormat); + } + } + return S_OK; +} + +// eliminate very many spurious warnings from MS compiler +#pragma warning(disable:4514) diff --git a/src/baseclasses/mtype.h b/src/baseclasses/mtype.h new file mode 100644 index 0000000..9402f06 --- /dev/null +++ b/src/baseclasses/mtype.h @@ -0,0 +1,89 @@ +//------------------------------------------------------------------------------ +// File: MtType.h +// +// Desc: DirectShow base classes - defines a class that holds and manages +// media type information. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#ifndef __MTYPE__ +#define __MTYPE__ + +/* Helper class that derived pin objects can use to compare media + types etc. Has same data members as the struct AM_MEDIA_TYPE defined + in the streams IDL file, but also has (non-virtual) functions */ + +class CMediaType : public _AMMediaType { + +public: + + ~CMediaType(); + CMediaType(); + CMediaType(const GUID * majortype); + CMediaType(const AM_MEDIA_TYPE&, __out_opt HRESULT* phr = NULL); + CMediaType(const CMediaType&, __out_opt HRESULT* phr = NULL); + + CMediaType& operator=(const CMediaType&); + CMediaType& operator=(const AM_MEDIA_TYPE&); + + BOOL operator == (const CMediaType&) const; + BOOL operator != (const CMediaType&) const; + + HRESULT Set(const CMediaType& rt); + HRESULT Set(const AM_MEDIA_TYPE& rt); + + BOOL IsValid() const; + + const GUID *Type() const { return &majortype;} ; + void SetType(const GUID *); + const GUID *Subtype() const { return &subtype;} ; + void SetSubtype(const GUID *); + + BOOL IsFixedSize() const {return bFixedSizeSamples; }; + BOOL IsTemporalCompressed() const {return bTemporalCompression; }; + ULONG GetSampleSize() const; + + void SetSampleSize(ULONG sz); + void SetVariableSize(); + void SetTemporalCompression(BOOL bCompressed); + + // read/write pointer to format - can't change length without + // calling SetFormat, AllocFormatBuffer or ReallocFormatBuffer + + BYTE* Format() const {return pbFormat; }; + ULONG FormatLength() const { return cbFormat; }; + + void SetFormatType(const GUID *); + const GUID *FormatType() const {return &formattype; }; + BOOL SetFormat(__in_bcount(length) BYTE *pFormat, ULONG length); + void ResetFormatBuffer(); + BYTE* AllocFormatBuffer(ULONG length); + BYTE* ReallocFormatBuffer(ULONG length); + + void InitMediaType(); + + BOOL MatchesPartial(const CMediaType* ppartial) const; + BOOL IsPartiallySpecified(void) const; +}; + + +/* General purpose functions to copy and delete a task allocated AM_MEDIA_TYPE + structure which is useful when using the IEnumMediaFormats interface as + the implementation allocates the structures which you must later delete */ + +void WINAPI DeleteMediaType(__inout_opt AM_MEDIA_TYPE *pmt); +AM_MEDIA_TYPE * WINAPI CreateMediaType(AM_MEDIA_TYPE const *pSrc); +HRESULT WINAPI CopyMediaType(__out AM_MEDIA_TYPE *pmtTarget, const AM_MEDIA_TYPE *pmtSource); +void WINAPI FreeMediaType(__inout AM_MEDIA_TYPE& mt); + +// Initialize a media type from a WAVEFORMATEX + +STDAPI CreateAudioMediaType( + const WAVEFORMATEX *pwfx, + __out AM_MEDIA_TYPE *pmt, + BOOL bSetFormat); + +#endif /* __MTYPE__ */ + diff --git a/src/baseclasses/refclock.h b/src/baseclasses/refclock.h new file mode 100644 index 0000000..df822e0 --- /dev/null +++ b/src/baseclasses/refclock.h @@ -0,0 +1,184 @@ +//------------------------------------------------------------------------------ +// File: RefClock.h +// +// Desc: DirectShow base classes - defines the IReferenceClock interface. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#ifndef __BASEREFCLOCK__ +#define __BASEREFCLOCK__ + +#include + +const UINT RESOLUTION = 1; /* High resolution timer */ +const INT ADVISE_CACHE = 4; /* Default cache size */ +const LONGLONG MAX_TIME = 0x7FFFFFFFFFFFFFFF; /* Maximum LONGLONG value */ + +inline LONGLONG WINAPI ConvertToMilliseconds(const REFERENCE_TIME& RT) +{ + /* This converts an arbitrary value representing a reference time + into a MILLISECONDS value for use in subsequent system calls */ + + return (RT / (UNITS / MILLISECONDS)); +} + +/* This class hierarchy will support an IReferenceClock interface so + that an audio card (or other externally driven clock) can update the + system wide clock that everyone uses. + + The interface will be pretty thin with probably just one update method + This interface has not yet been defined. + */ + +/* This abstract base class implements the IReferenceClock + * interface. Classes that actually provide clock signals (from + * whatever source) have to be derived from this class. + * + * The abstract class provides implementations for: + * CUnknown support + * locking support (CCritSec) + * client advise code (creates a thread) + * + * Question: what can we do about quality? Change the timer + * resolution to lower the system load? Up the priority of the + * timer thread to force more responsive signals? + * + * During class construction we create a worker thread that is destroyed during + * destuction. This thread executes a series of WaitForSingleObject calls, + * waking up when a command is given to the thread or the next wake up point + * is reached. The wakeup points are determined by clients making Advise + * calls. + * + * Each advise call defines a point in time when they wish to be notified. A + * periodic advise is a series of these such events. We maintain a list of + * advise links and calculate when the nearest event notification is due for. + * We then call WaitForSingleObject with a timeout equal to this time. The + * handle we wait on is used by the class to signal that something has changed + * and that we must reschedule the next event. This typically happens when + * someone comes in and asks for an advise link while we are waiting for an + * event to timeout. + * + * While we are modifying the list of advise requests we + * are protected from interference through a critical section. Clients are NOT + * advised through callbacks. One shot clients have an event set, while + * periodic clients have a semaphore released for each event notification. A + * semaphore allows a client to be kept up to date with the number of events + * actually triggered and be assured that they can't miss multiple events being + * set. + * + * Keeping track of advises is taken care of by the CAMSchedule class. + */ + +class CBaseReferenceClock +: public CUnknown, public IReferenceClock, public CCritSec, public IReferenceClockTimerControl +{ +protected: + virtual ~CBaseReferenceClock(); // Don't let me be created on the stack! +public: + CBaseReferenceClock(__in_opt LPCTSTR pName, + __inout_opt LPUNKNOWN pUnk, + __inout HRESULT *phr, + __inout_opt CAMSchedule * pSched = 0 ); + + STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, __deref_out void ** ppv); + + DECLARE_IUNKNOWN + + /* IReferenceClock methods */ + // Derived classes must implement GetPrivateTime(). All our GetTime + // does is call GetPrivateTime and then check so that time does not + // go backwards. A return code of S_FALSE implies that the internal + // clock has gone backwards and GetTime time has halted until internal + // time has caught up. (Don't know if this will be much use to folk, + // but it seems odd not to use the return code for something useful.) + STDMETHODIMP GetTime(__out REFERENCE_TIME *pTime); + // When this is called, it sets m_rtLastGotTime to the time it returns. + + /* Provide standard mechanisms for scheduling events */ + + /* Ask for an async notification that a time has elapsed */ + STDMETHODIMP AdviseTime( + REFERENCE_TIME baseTime, // base reference time + REFERENCE_TIME streamTime, // stream offset time + HEVENT hEvent, // advise via this event + __out DWORD_PTR *pdwAdviseCookie// where your cookie goes + ); + + /* Ask for an asynchronous periodic notification that a time has elapsed */ + STDMETHODIMP AdvisePeriodic( + REFERENCE_TIME StartTime, // starting at this time + REFERENCE_TIME PeriodTime, // time between notifications + HSEMAPHORE hSemaphore, // advise via a semaphore + __out DWORD_PTR *pdwAdviseCookie// where your cookie goes + ); + + /* Cancel a request for notification(s) - if the notification was + * a one shot timer then this function doesn't need to be called + * as the advise is automatically cancelled, however it does no + * harm to explicitly cancel a one-shot advise. It is REQUIRED that + * clients call Unadvise to clear a Periodic advise setting. + */ + + STDMETHODIMP Unadvise(DWORD_PTR dwAdviseCookie); + + /* Methods for the benefit of derived classes or outer objects */ + + // GetPrivateTime() is the REAL clock. GetTime is just a cover for + // it. Derived classes will probably override this method but not + // GetTime() itself. + // The important point about GetPrivateTime() is it's allowed to go + // backwards. Our GetTime() will keep returning the LastGotTime + // until GetPrivateTime() catches up. + virtual REFERENCE_TIME GetPrivateTime(); + + /* Provide a method for correcting drift */ + STDMETHODIMP SetTimeDelta( const REFERENCE_TIME& TimeDelta ); + + CAMSchedule * GetSchedule() const { return m_pSchedule; } + + // IReferenceClockTimerControl methods + // + // Setting a default of 0 disables the default of 1ms + STDMETHODIMP SetDefaultTimerResolution( + REFERENCE_TIME timerResolution // in 100ns + ); + STDMETHODIMP GetDefaultTimerResolution( + __out REFERENCE_TIME* pTimerResolution // in 100ns + ); + +private: + REFERENCE_TIME m_rtPrivateTime; // Current best estimate of time + DWORD m_dwPrevSystemTime; // Last vaule we got from timeGetTime + REFERENCE_TIME m_rtLastGotTime; // Last time returned by GetTime + REFERENCE_TIME m_rtNextAdvise; // Time of next advise + UINT m_TimerResolution; + +#ifdef PERF + int m_idGetSystemTime; +#endif + +// Thread stuff +public: + void TriggerThread() // Wakes thread up. Need to do this if + { // time to next advise needs reevaluating. + EXECUTE_ASSERT(SetEvent(m_pSchedule->GetEvent())); + } + + +private: + BOOL m_bAbort; // Flag used for thread shutdown + HANDLE m_hThread; // Thread handle + + HRESULT AdviseThread(); // Method in which the advise thread runs + static DWORD __stdcall AdviseThreadFunction(__in LPVOID); // Function used to get there + +protected: + CAMSchedule * m_pSchedule; + + void Restart (IN REFERENCE_TIME rtMinTime = 0I64) ; +}; + +#endif + diff --git a/src/baseclasses/reftime.h b/src/baseclasses/reftime.h new file mode 100644 index 0000000..0ed32f6 --- /dev/null +++ b/src/baseclasses/reftime.h @@ -0,0 +1,116 @@ +//------------------------------------------------------------------------------ +// File: RefTime.h +// +// Desc: DirectShow base classes - defines CRefTime, a class that manages +// reference times. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +// +// CRefTime +// +// Manage reference times. +// Shares same data layout as REFERENCE_TIME, but adds some (nonvirtual) +// functions providing simple comparison, conversion and arithmetic. +// +// A reference time (at the moment) is a unit of seconds represented in +// 100ns units as is used in the Win32 FILETIME structure. BUT the time +// a REFERENCE_TIME represents is NOT the time elapsed since 1/1/1601 it +// will either be stream time or reference time depending upon context +// +// This class provides simple arithmetic operations on reference times +// +// keep non-virtual otherwise the data layout will not be the same as +// REFERENCE_TIME + + +// ----- +// note that you are safe to cast a CRefTime* to a REFERENCE_TIME*, but +// you will need to do so explicitly +// ----- + + +#ifndef __REFTIME__ +#define __REFTIME__ + + +const LONGLONG MILLISECONDS = (1000); // 10 ^ 3 +const LONGLONG NANOSECONDS = (1000000000); // 10 ^ 9 +const LONGLONG UNITS = (NANOSECONDS / 100); // 10 ^ 7 + +/* Unfortunately an inline function here generates a call to __allmul + - even for constants! +*/ +#define MILLISECONDS_TO_100NS_UNITS(lMs) \ + Int32x32To64((lMs), (UNITS / MILLISECONDS)) + +class CRefTime +{ +public: + + // *MUST* be the only data member so that this class is exactly + // equivalent to a REFERENCE_TIME. + // Also, must be *no virtual functions* + + REFERENCE_TIME m_time; + + inline CRefTime() + { + // default to 0 time + m_time = 0; + }; + + inline CRefTime(LONG msecs) + { + m_time = MILLISECONDS_TO_100NS_UNITS(msecs); + }; + + inline CRefTime(REFERENCE_TIME rt) + { + m_time = rt; + }; + + inline operator REFERENCE_TIME() const + { + return m_time; + }; + + inline CRefTime& operator=(const CRefTime& rt) + { + m_time = rt.m_time; + return *this; + }; + + inline CRefTime& operator=(const LONGLONG ll) + { + m_time = ll; + return *this; + }; + + inline CRefTime& operator+=(const CRefTime& rt) + { + return (*this = *this + rt); + }; + + inline CRefTime& operator-=(const CRefTime& rt) + { + return (*this = *this - rt); + }; + + inline LONG Millisecs(void) + { + return (LONG)(m_time / (UNITS / MILLISECONDS)); + }; + + inline LONGLONG GetUnits(void) + { + return m_time; + }; +}; + +const LONGLONG TimeZero = 0; + +#endif /* __REFTIME__ */ + diff --git a/src/baseclasses/renbase.h b/src/baseclasses/renbase.h new file mode 100644 index 0000000..8634c6b --- /dev/null +++ b/src/baseclasses/renbase.h @@ -0,0 +1,478 @@ +//------------------------------------------------------------------------------ +// File: RenBase.h +// +// Desc: DirectShow base classes - defines a generic ActiveX base renderer +// class. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#ifndef __RENBASE__ +#define __RENBASE__ + +// Forward class declarations + +class CBaseRenderer; +class CBaseVideoRenderer; +class CRendererInputPin; + +// This is our input pin class that channels calls to the renderer + +class CRendererInputPin : public CBaseInputPin +{ +protected: + + CBaseRenderer *m_pRenderer; + +public: + + CRendererInputPin(__inout CBaseRenderer *pRenderer, + __inout HRESULT *phr, + __in_opt LPCWSTR Name); + + // Overriden from the base pin classes + + HRESULT BreakConnect(); + HRESULT CompleteConnect(IPin *pReceivePin); + HRESULT SetMediaType(const CMediaType *pmt); + HRESULT CheckMediaType(const CMediaType *pmt); + HRESULT Active(); + HRESULT Inactive(); + + // Add rendering behaviour to interface functions + + STDMETHODIMP QueryId(__deref_out LPWSTR *Id); + STDMETHODIMP EndOfStream(); + STDMETHODIMP BeginFlush(); + STDMETHODIMP EndFlush(); + STDMETHODIMP Receive(IMediaSample *pMediaSample); + + // Helper + IMemAllocator inline *Allocator() const + { + return m_pAllocator; + } +}; + +// Main renderer class that handles synchronisation and state changes + +class CBaseRenderer : public CBaseFilter +{ +protected: + + friend class CRendererInputPin; + + friend void CALLBACK EndOfStreamTimer(UINT uID, // Timer identifier + UINT uMsg, // Not currently used + DWORD_PTR dwUser, // User information + DWORD_PTR dw1, // Windows reserved + DWORD_PTR dw2); // Is also reserved + + CRendererPosPassThru *m_pPosition; // Media seeking pass by object + CAMEvent m_RenderEvent; // Used to signal timer events + CAMEvent m_ThreadSignal; // Signalled to release worker thread + CAMEvent m_evComplete; // Signalled when state complete + BOOL m_bAbort; // Stop us from rendering more data + BOOL m_bStreaming; // Are we currently streaming + DWORD_PTR m_dwAdvise; // Timer advise cookie + IMediaSample *m_pMediaSample; // Current image media sample + BOOL m_bEOS; // Any more samples in the stream + BOOL m_bEOSDelivered; // Have we delivered an EC_COMPLETE + CRendererInputPin *m_pInputPin; // Our renderer input pin object + CCritSec m_InterfaceLock; // Critical section for interfaces + CCritSec m_RendererLock; // Controls access to internals + IQualityControl * m_pQSink; // QualityControl sink + BOOL m_bRepaintStatus; // Can we signal an EC_REPAINT + // Avoid some deadlocks by tracking filter during stop + volatile BOOL m_bInReceive; // Inside Receive between PrepareReceive + // And actually processing the sample + REFERENCE_TIME m_SignalTime; // Time when we signal EC_COMPLETE + UINT m_EndOfStreamTimer; // Used to signal end of stream + CCritSec m_ObjectCreationLock; // This lock protects the creation and + // of m_pPosition and m_pInputPin. It + // ensures that two threads cannot create + // either object simultaneously. + +public: + + CBaseRenderer(REFCLSID RenderClass, // CLSID for this renderer + __in_opt LPCTSTR pName, // Debug ONLY description + __inout_opt LPUNKNOWN pUnk, // Aggregated owner object + __inout HRESULT *phr); // General OLE return code + + ~CBaseRenderer(); + + // Overriden to say what interfaces we support and where + + virtual HRESULT GetMediaPositionInterface(REFIID riid, __deref_out void **ppv); + STDMETHODIMP NonDelegatingQueryInterface(REFIID, __deref_out void **); + + virtual HRESULT SourceThreadCanWait(BOOL bCanWait); + +#ifdef DEBUG + // Debug only dump of the renderer state + void DisplayRendererState(); +#endif + virtual HRESULT WaitForRenderTime(); + virtual HRESULT CompleteStateChange(FILTER_STATE OldState); + + // Return internal information about this filter + + BOOL IsEndOfStream() { return m_bEOS; }; + BOOL IsEndOfStreamDelivered() { return m_bEOSDelivered; }; + BOOL IsStreaming() { return m_bStreaming; }; + void SetAbortSignal(BOOL bAbort) { m_bAbort = bAbort; }; + virtual void OnReceiveFirstSample(IMediaSample *pMediaSample) { }; + CAMEvent *GetRenderEvent() { return &m_RenderEvent; }; + + // Permit access to the transition state + + void Ready() { m_evComplete.Set(); }; + void NotReady() { m_evComplete.Reset(); }; + BOOL CheckReady() { return m_evComplete.Check(); }; + + virtual int GetPinCount(); + virtual CBasePin *GetPin(int n); + FILTER_STATE GetRealState(); + void SendRepaint(); + void SendNotifyWindow(IPin *pPin,HWND hwnd); + BOOL OnDisplayChange(); + void SetRepaintStatus(BOOL bRepaint); + + // Override the filter and pin interface functions + + STDMETHODIMP Stop(); + STDMETHODIMP Pause(); + STDMETHODIMP Run(REFERENCE_TIME StartTime); + STDMETHODIMP GetState(DWORD dwMSecs, __out FILTER_STATE *State); + STDMETHODIMP FindPin(LPCWSTR Id, __deref_out IPin **ppPin); + + // These are available for a quality management implementation + + virtual void OnRenderStart(IMediaSample *pMediaSample); + virtual void OnRenderEnd(IMediaSample *pMediaSample); + virtual HRESULT OnStartStreaming() { return NOERROR; }; + virtual HRESULT OnStopStreaming() { return NOERROR; }; + virtual void OnWaitStart() { }; + virtual void OnWaitEnd() { }; + virtual void PrepareRender() { }; + +#ifdef PERF + REFERENCE_TIME m_trRenderStart; // Just before we started drawing + // Set in OnRenderStart, Used in OnRenderEnd + int m_idBaseStamp; // MSR_id for frame time stamp + int m_idBaseRenderTime; // MSR_id for true wait time + int m_idBaseAccuracy; // MSR_id for time frame is late (int) +#endif + + // Quality management implementation for scheduling rendering + + virtual BOOL ScheduleSample(IMediaSample *pMediaSample); + virtual HRESULT GetSampleTimes(IMediaSample *pMediaSample, + __out REFERENCE_TIME *pStartTime, + __out REFERENCE_TIME *pEndTime); + + virtual HRESULT ShouldDrawSampleNow(IMediaSample *pMediaSample, + __out REFERENCE_TIME *ptrStart, + __out REFERENCE_TIME *ptrEnd); + + // Lots of end of stream complexities + + void TimerCallback(); + void ResetEndOfStreamTimer(); + HRESULT NotifyEndOfStream(); + virtual HRESULT SendEndOfStream(); + virtual HRESULT ResetEndOfStream(); + virtual HRESULT EndOfStream(); + + // Rendering is based around the clock + + void SignalTimerFired(); + virtual HRESULT CancelNotification(); + virtual HRESULT ClearPendingSample(); + + // Called when the filter changes state + + virtual HRESULT Active(); + virtual HRESULT Inactive(); + virtual HRESULT StartStreaming(); + virtual HRESULT StopStreaming(); + virtual HRESULT BeginFlush(); + virtual HRESULT EndFlush(); + + // Deal with connections and type changes + + virtual HRESULT BreakConnect(); + virtual HRESULT SetMediaType(const CMediaType *pmt); + virtual HRESULT CompleteConnect(IPin *pReceivePin); + + // These look after the handling of data samples + + virtual HRESULT PrepareReceive(IMediaSample *pMediaSample); + virtual HRESULT Receive(IMediaSample *pMediaSample); + virtual BOOL HaveCurrentSample(); + virtual IMediaSample *GetCurrentSample(); + virtual HRESULT Render(IMediaSample *pMediaSample); + + // Derived classes MUST override these + virtual HRESULT DoRenderSample(IMediaSample *pMediaSample) PURE; + virtual HRESULT CheckMediaType(const CMediaType *) PURE; + + // Helper + void WaitForReceiveToComplete(); +}; + + +// CBaseVideoRenderer is a renderer class (see its ancestor class) and +// it handles scheduling of media samples so that they are drawn at the +// correct time by the reference clock. It implements a degradation +// strategy. Possible degradation modes are: +// Drop frames here (only useful if the drawing takes significant time) +// Signal supplier (upstream) to drop some frame(s) - i.e. one-off skip. +// Signal supplier to change the frame rate - i.e. ongoing skipping. +// Or any combination of the above. +// In order to determine what's useful to try we need to know what's going +// on. This is done by timing various operations (including the supplier). +// This timing is done by using timeGetTime as it is accurate enough and +// usually cheaper than calling the reference clock. It also tells the +// truth if there is an audio break and the reference clock stops. +// We provide a number of public entry points (named OnXxxStart, OnXxxEnd) +// which the rest of the renderer calls at significant moments. These do +// the timing. + +// the number of frames that the sliding averages are averaged over. +// the rule is (1024*NewObservation + (AVGPERIOD-1) * PreviousAverage)/AVGPERIOD +#define AVGPERIOD 4 +#define DO_MOVING_AVG(avg,obs) (avg = (1024*obs + (AVGPERIOD-1)*avg)/AVGPERIOD) +// Spot the bug in this macro - I can't. but it doesn't work! + +class CBaseVideoRenderer : public CBaseRenderer, // Base renderer class + public IQualProp, // Property page guff + public IQualityControl // Allow throttling +{ +protected: + + // Hungarian: + // tFoo is the time Foo in mSec (beware m_tStart from filter.h) + // trBar is the time Bar by the reference clock + + //****************************************************************** + // State variables to control synchronisation + //****************************************************************** + + // Control of sending Quality messages. We need to know whether + // we are in trouble (e.g. frames being dropped) and where the time + // is being spent. + + // When we drop a frame we play the next one early. + // The frame after that is likely to wait before drawing and counting this + // wait as spare time is unfair, so we count it as a zero wait. + // We therefore need to know whether we are playing frames early or not. + + int m_nNormal; // The number of consecutive frames + // drawn at their normal time (not early) + // -1 means we just dropped a frame. + +#ifdef PERF + BOOL m_bDrawLateFrames; // Don't drop any frames (debug and I'm + // not keen on people using it!) +#endif + + BOOL m_bSupplierHandlingQuality;// The response to Quality messages says + // our supplier is handling things. + // We will allow things to go extra late + // before dropping frames. We will play + // very early after he has dropped one. + + // Control of scheduling, frame dropping etc. + // We need to know where the time is being spent so as to tell whether + // we should be taking action here, signalling supplier or what. + // The variables are initialised to a mode of NOT dropping frames. + // They will tell the truth after a few frames. + // We typically record a start time for an event, later we get the time + // again and subtract to get the elapsed time, and we average this over + // a few frames. The average is used to tell what mode we are in. + + // Although these are reference times (64 bit) they are all DIFFERENCES + // between times which are small. An int will go up to 214 secs before + // overflow. Avoiding 64 bit multiplications and divisions seems + // worth while. + + + + // Audio-video throttling. If the user has turned up audio quality + // very high (in principle it could be any other stream, not just audio) + // then we can receive cries for help via the graph manager. In this case + // we put in a wait for some time after rendering each frame. + int m_trThrottle; + + // The time taken to render (i.e. BitBlt) frames controls which component + // needs to degrade. If the blt is expensive, the renderer degrades. + // If the blt is cheap it's done anyway and the supplier degrades. + int m_trRenderAvg; // Time frames are taking to blt + int m_trRenderLast; // Time for last frame blt + int m_tRenderStart; // Just before we started drawing (mSec) + // derived from timeGetTime. + + // When frames are dropped we will play the next frame as early as we can. + // If it was a false alarm and the machine is fast we slide gently back to + // normal timing. To do this, we record the offset showing just how early + // we really are. This will normally be negative meaning early or zero. + int m_trEarliness; + + // Target provides slow long-term feedback to try to reduce the + // average sync offset to zero. Whenever a frame is actually rendered + // early we add a msec or two, whenever late we take off a few. + // We add or take off 1/32 of the error time. + // Eventually we should be hovering around zero. For a really bad case + // where we were (say) 300mSec off, it might take 100 odd frames to + // settle down. The rate of change of this is intended to be slower + // than any other mechanism in Quartz, thereby avoiding hunting. + int m_trTarget; + + // The proportion of time spent waiting for the right moment to blt + // controls whether we bother to drop a frame or whether we reckon that + // we're doing well enough that we can stand a one-frame glitch. + int m_trWaitAvg; // Average of last few wait times + // (actually we just average how early + // we were). Negative here means LATE. + + // The average inter-frame time. + // This is used to calculate the proportion of the time used by the + // three operations (supplying us, waiting, rendering) + int m_trFrameAvg; // Average inter-frame time + int m_trDuration; // duration of last frame. + +#ifdef PERF + // Performance logging identifiers + int m_idTimeStamp; // MSR_id for frame time stamp + int m_idEarliness; // MSR_id for earliness fudge + int m_idTarget; // MSR_id for Target fudge + int m_idWaitReal; // MSR_id for true wait time + int m_idWait; // MSR_id for wait time recorded + int m_idFrameAccuracy; // MSR_id for time frame is late (int) + int m_idRenderAvg; // MSR_id for Render time recorded (int) + int m_idSchLateTime; // MSR_id for lateness at scheduler + int m_idQualityRate; // MSR_id for Quality rate requested + int m_idQualityTime; // MSR_id for Quality time requested + int m_idDecision; // MSR_id for decision code + int m_idDuration; // MSR_id for duration of a frame + int m_idThrottle; // MSR_id for audio-video throttling + //int m_idDebug; // MSR_id for trace style debugging + //int m_idSendQuality; // MSR_id for timing the notifications per se +#endif // PERF + REFERENCE_TIME m_trRememberStampForPerf; // original time stamp of frame + // with no earliness fudges etc. +#ifdef PERF + REFERENCE_TIME m_trRememberFrameForPerf; // time when previous frame rendered + + // debug... + int m_idFrameAvg; + int m_idWaitAvg; +#endif + + // PROPERTY PAGE + // This has edit fields that show the user what's happening + // These member variables hold these counts. + + int m_cFramesDropped; // cumulative frames dropped IN THE RENDERER + int m_cFramesDrawn; // Frames since streaming started seen BY THE + // RENDERER (some may be dropped upstream) + + // Next two support average sync offset and standard deviation of sync offset. + LONGLONG m_iTotAcc; // Sum of accuracies in mSec + LONGLONG m_iSumSqAcc; // Sum of squares of (accuracies in mSec) + + // Next two allow jitter calculation. Jitter is std deviation of frame time. + REFERENCE_TIME m_trLastDraw; // Time of prev frame (for inter-frame times) + LONGLONG m_iSumSqFrameTime; // Sum of squares of (inter-frame time in mSec) + LONGLONG m_iSumFrameTime; // Sum of inter-frame times in mSec + + // To get performance statistics on frame rate, jitter etc, we need + // to record the lateness and inter-frame time. What we actually need are the + // data above (sum, sum of squares and number of entries for each) but the data + // is generated just ahead of time and only later do we discover whether the + // frame was actually drawn or not. So we have to hang on to the data + int m_trLate; // hold onto frame lateness + int m_trFrame; // hold onto inter-frame time + + int m_tStreamingStart; // if streaming then time streaming started + // else time of last streaming session + // used for property page statistics +#ifdef PERF + LONGLONG m_llTimeOffset; // timeGetTime()*10000+m_llTimeOffset==ref time +#endif + +public: + + + CBaseVideoRenderer(REFCLSID RenderClass, // CLSID for this renderer + __in_opt LPCTSTR pName, // Debug ONLY description + __inout_opt LPUNKNOWN pUnk, // Aggregated owner object + __inout HRESULT *phr); // General OLE return code + + ~CBaseVideoRenderer(); + + // IQualityControl methods - Notify allows audio-video throttling + + STDMETHODIMP SetSink( IQualityControl * piqc); + STDMETHODIMP Notify( IBaseFilter * pSelf, Quality q); + + // These provide a full video quality management implementation + + void OnRenderStart(IMediaSample *pMediaSample); + void OnRenderEnd(IMediaSample *pMediaSample); + void OnWaitStart(); + void OnWaitEnd(); + HRESULT OnStartStreaming(); + HRESULT OnStopStreaming(); + void ThrottleWait(); + + // Handle the statistics gathering for our quality management + + void PreparePerformanceData(int trLate, int trFrame); + virtual void RecordFrameLateness(int trLate, int trFrame); + virtual void OnDirectRender(IMediaSample *pMediaSample); + virtual HRESULT ResetStreamingTimes(); + BOOL ScheduleSample(IMediaSample *pMediaSample); + HRESULT ShouldDrawSampleNow(IMediaSample *pMediaSample, + __inout REFERENCE_TIME *ptrStart, + __inout REFERENCE_TIME *ptrEnd); + + virtual HRESULT SendQuality(REFERENCE_TIME trLate, REFERENCE_TIME trRealStream); + STDMETHODIMP JoinFilterGraph(__inout_opt IFilterGraph * pGraph, __in_opt LPCWSTR pName); + + // + // Do estimates for standard deviations for per-frame + // statistics + // + // *piResult = (llSumSq - iTot * iTot / m_cFramesDrawn - 1) / + // (m_cFramesDrawn - 2) + // or 0 if m_cFramesDrawn <= 3 + // + HRESULT GetStdDev( + int nSamples, + __out int *piResult, + LONGLONG llSumSq, + LONGLONG iTot + ); +public: + + // IQualProp property page support + + STDMETHODIMP get_FramesDroppedInRenderer(__out int *cFramesDropped); + STDMETHODIMP get_FramesDrawn(__out int *pcFramesDrawn); + STDMETHODIMP get_AvgFrameRate(__out int *piAvgFrameRate); + STDMETHODIMP get_Jitter(__out int *piJitter); + STDMETHODIMP get_AvgSyncOffset(__out int *piAvg); + STDMETHODIMP get_DevSyncOffset(__out int *piDev); + + // Implement an IUnknown interface and expose IQualProp + + DECLARE_IUNKNOWN + STDMETHODIMP NonDelegatingQueryInterface(REFIID riid,__deref_out VOID **ppv); +}; + +#endif // __RENBASE__ + diff --git a/src/baseclasses/schedule.h b/src/baseclasses/schedule.h new file mode 100644 index 0000000..65ed402 --- /dev/null +++ b/src/baseclasses/schedule.h @@ -0,0 +1,128 @@ +//------------------------------------------------------------------------------ +// File: Schedule.h +// +// Desc: DirectShow base classes. +// +// Copyright (c) 1996-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#ifndef __CAMSchedule__ +#define __CAMSchedule__ + +class CAMSchedule : private CBaseObject +{ +public: + virtual ~CAMSchedule(); + // ev is the event we should fire if the advise time needs re-evaluating + CAMSchedule( HANDLE ev ); + + DWORD GetAdviseCount(); + REFERENCE_TIME GetNextAdviseTime(); + + // We need a method for derived classes to add advise packets, we return the cookie + DWORD_PTR AddAdvisePacket( const REFERENCE_TIME & time1, const REFERENCE_TIME & time2, HANDLE h, BOOL periodic ); + // And a way to cancel + HRESULT Unadvise(DWORD_PTR dwAdviseCookie); + + // Tell us the time please, and we'll dispatch the expired events. We return the time of the next event. + // NB: The time returned will be "useless" if you start adding extra Advises. But that's the problem of + // whoever is using this helper class (typically a clock). + REFERENCE_TIME Advise( const REFERENCE_TIME & rtTime ); + + // Get the event handle which will be set if advise time requires re-evaluation. + HANDLE GetEvent() const { return m_ev; } + +private: + // We define the nodes that will be used in our singly linked list + // of advise packets. The list is ordered by time, with the + // elements that will expire first at the front. + class CAdvisePacket + { + public: + CAdvisePacket() + {} + + CAdvisePacket * m_next; + DWORD_PTR m_dwAdviseCookie; + REFERENCE_TIME m_rtEventTime; // Time at which event should be set + REFERENCE_TIME m_rtPeriod; // Periodic time + HANDLE m_hNotify; // Handle to event or semephore + BOOL m_bPeriodic; // TRUE => Periodic event + + CAdvisePacket( __inout_opt CAdvisePacket * next, LONGLONG time ) : m_next(next), m_rtEventTime(time) + {} + + void InsertAfter( __inout CAdvisePacket * p ) + { + p->m_next = m_next; + m_next = p; + } + + int IsZ() const // That is, is it the node that represents the end of the list + { return m_next == 0; } + + CAdvisePacket * RemoveNext() + { + CAdvisePacket *const next = m_next; + CAdvisePacket *const new_next = next->m_next; + m_next = new_next; + return next; + } + + void DeleteNext() + { + delete RemoveNext(); + } + + CAdvisePacket * Next() const + { + CAdvisePacket * result = m_next; + if (result->IsZ()) result = 0; + return result; + } + + DWORD_PTR Cookie() const + { return m_dwAdviseCookie; } + }; + + // Structure is: + // head -> elmt1 -> elmt2 -> z -> null + // So an empty list is: head -> z -> null + // Having head & z as links makes insertaion, + // deletion and shunting much easier. + CAdvisePacket head, z; // z is both a tail and a sentry + + volatile DWORD_PTR m_dwNextCookie; // Strictly increasing + volatile DWORD m_dwAdviseCount; // Number of elements on list + + CCritSec m_Serialize; + + // AddAdvisePacket: adds the packet, returns the cookie (0 if failed) + DWORD_PTR AddAdvisePacket( __inout CAdvisePacket * pPacket ); + // Event that we should set if the packed added above will be the next to fire. + const HANDLE m_ev; + + // A Shunt is where we have changed the first element in the + // list and want it re-evaluating (i.e. repositioned) in + // the list. + void ShuntHead(); + + // Rather than delete advise packets, we cache them for future use + CAdvisePacket * m_pAdviseCache; + DWORD m_dwCacheCount; + enum { dwCacheMax = 5 }; // Don't bother caching more than five + + void Delete( __inout CAdvisePacket * pLink );// This "Delete" will cache the Link + +// Attributes and methods for debugging +public: +#ifdef DEBUG + void DumpLinkedList(); +#else + void DumpLinkedList() {} +#endif + +}; + +#endif // __CAMSchedule__ diff --git a/src/baseclasses/seekpt.h b/src/baseclasses/seekpt.h new file mode 100644 index 0000000..26abdf3 --- /dev/null +++ b/src/baseclasses/seekpt.h @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// File: SeekPT.h +// +// Desc: DirectShow base classes. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#ifndef __seekpt_h__ +#define __seekpt_h__ + + +class CSeekingPassThru : public ISeekingPassThru, public CUnknown +{ +public: + static CUnknown *CreateInstance(__inout_opt LPUNKNOWN pUnk, __inout HRESULT *phr); + CSeekingPassThru(__in_opt LPCTSTR pName, __inout_opt LPUNKNOWN pUnk, __inout HRESULT *phr); + ~CSeekingPassThru(); + + DECLARE_IUNKNOWN; + STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, __deref_out void ** ppv); + + STDMETHODIMP Init(BOOL bSupportRendering, IPin *pPin); + +private: + CPosPassThru *m_pPosPassThru; +}; + +#endif diff --git a/src/baseclasses/source.cpp b/src/baseclasses/source.cpp new file mode 100644 index 0000000..063fa54 --- /dev/null +++ b/src/baseclasses/source.cpp @@ -0,0 +1,522 @@ +//------------------------------------------------------------------------------ +// File: Source.cpp +// +// Desc: DirectShow base classes - implements CSource, which is a Quartz +// source filter 'template.' +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +// Locking Strategy. +// +// Hold the filter critical section (m_pFilter->pStateLock()) to serialise +// access to functions. Note that, in general, this lock may be held +// by a function when the worker thread may want to hold it. Therefore +// if you wish to access shared state from the worker thread you will +// need to add another critical section object. The execption is during +// the threads processing loop, when it is safe to get the filter critical +// section from within FillBuffer(). + +#include + + +// +// CSource::Constructor +// +// Initialise the pin count for the filter. The user will create the pins in +// the derived class. +CSource::CSource(__in_opt LPCTSTR pName, __inout_opt LPUNKNOWN lpunk, CLSID clsid) + : CBaseFilter(pName, lpunk, &m_cStateLock, clsid), + m_iPins(0), + m_paStreams(NULL) +{ +} + +CSource::CSource(__in_opt LPCTSTR pName, __inout_opt LPUNKNOWN lpunk, CLSID clsid, __inout HRESULT *phr) + : CBaseFilter(pName, lpunk, &m_cStateLock, clsid), + m_iPins(0), + m_paStreams(NULL) +{ + UNREFERENCED_PARAMETER(phr); +} + +#ifdef UNICODE +CSource::CSource(__in_opt LPCSTR pName, __inout_opt LPUNKNOWN lpunk, CLSID clsid) + : CBaseFilter(pName, lpunk, &m_cStateLock, clsid), + m_iPins(0), + m_paStreams(NULL) +{ +} + +CSource::CSource(__in_opt LPCSTR pName, __inout_opt LPUNKNOWN lpunk, CLSID clsid, __inout HRESULT *phr) + : CBaseFilter(pName, lpunk, &m_cStateLock, clsid), + m_iPins(0), + m_paStreams(NULL) +{ + UNREFERENCED_PARAMETER(phr); +} +#endif + +// +// CSource::Destructor +// +CSource::~CSource() +{ + /* Free our pins and pin array */ + while (m_iPins != 0) { + // deleting the pins causes them to be removed from the array... + delete m_paStreams[m_iPins - 1]; + } + + ASSERT(m_paStreams == NULL); +} + + +// +// Add a new pin +// +HRESULT CSource::AddPin(__in CSourceStream *pStream) +{ + CAutoLock lock(&m_cStateLock); + + /* Allocate space for this pin and the old ones */ + CSourceStream **paStreams = new CSourceStream *[m_iPins + 1]; + if (paStreams == NULL) { + return E_OUTOFMEMORY; + } + if (m_paStreams != NULL) { + CopyMemory((PVOID)paStreams, (PVOID)m_paStreams, + m_iPins * sizeof(m_paStreams[0])); + paStreams[m_iPins] = pStream; + delete [] m_paStreams; + } + m_paStreams = paStreams; + m_paStreams[m_iPins] = pStream; + m_iPins++; + return S_OK; +} + +// +// Remove a pin - pStream is NOT deleted +// +HRESULT CSource::RemovePin(__in CSourceStream *pStream) +{ + int i; + for (i = 0; i < m_iPins; i++) { + if (m_paStreams[i] == pStream) { + if (m_iPins == 1) { + delete [] m_paStreams; + m_paStreams = NULL; + } else { + /* no need to reallocate */ + while (++i < m_iPins) + m_paStreams[i - 1] = m_paStreams[i]; + } + m_iPins--; + return S_OK; + } + } + return S_FALSE; +} + +// +// FindPin +// +// Set *ppPin to the IPin* that has the id Id. +// or to NULL if the Id cannot be matched. +STDMETHODIMP CSource::FindPin(LPCWSTR Id, __deref_out IPin **ppPin) +{ + CheckPointer(ppPin,E_POINTER); + ValidateReadWritePtr(ppPin,sizeof(IPin *)); + // The -1 undoes the +1 in QueryId and ensures that totally invalid + // strings (for which WstrToInt delivers 0) give a deliver a NULL pin. + int i = WstrToInt(Id) -1; + *ppPin = GetPin(i); + if (*ppPin!=NULL){ + (*ppPin)->AddRef(); + return NOERROR; + } else { + return VFW_E_NOT_FOUND; + } +} + +// +// FindPinNumber +// +// return the number of the pin with this IPin* or -1 if none +int CSource::FindPinNumber(__in IPin *iPin) { + int i; + for (i=0; in && n>=0 it follows that m_iPins>0 + // which is what used to be checked (i.e. checking that we have a pin) + if ((n >= 0) && (n < m_iPins)) { + + ASSERT(m_paStreams[n]); + return m_paStreams[n]; + } + return NULL; +} + + +// + + +// * +// * --- CSourceStream ---- +// * + +// +// Set Id to point to a CoTaskMemAlloc'd +STDMETHODIMP CSourceStream::QueryId(__deref_out LPWSTR *Id) { + CheckPointer(Id,E_POINTER); + ValidateReadWritePtr(Id,sizeof(LPWSTR)); + + // We give the pins id's which are 1,2,... + // FindPinNumber returns -1 for an invalid pin + int i = 1+ m_pFilter->FindPinNumber(this); + if (i<1) return VFW_E_NOT_FOUND; + *Id = (LPWSTR)CoTaskMemAlloc(sizeof(WCHAR) * 12); + if (*Id==NULL) { + return E_OUTOFMEMORY; + } + IntToWstr(i, *Id); + return NOERROR; +} + + + +// +// CSourceStream::Constructor +// +// increments the number of pins present on the filter +CSourceStream::CSourceStream( + __in_opt LPCTSTR pObjectName, + __inout HRESULT *phr, + __inout CSource *ps, + __in_opt LPCWSTR pPinName) + : CBaseOutputPin(pObjectName, ps, ps->pStateLock(), phr, pPinName), + m_pFilter(ps) { + + *phr = m_pFilter->AddPin(this); +} + +#ifdef UNICODE +CSourceStream::CSourceStream( + __in_opt LPCSTR pObjectName, + __inout HRESULT *phr, + __inout CSource *ps, + __in_opt LPCWSTR pPinName) + : CBaseOutputPin(pObjectName, ps, ps->pStateLock(), phr, pPinName), + m_pFilter(ps) { + + *phr = m_pFilter->AddPin(this); +} +#endif +// +// CSourceStream::Destructor +// +// Decrements the number of pins on this filter +CSourceStream::~CSourceStream(void) { + + m_pFilter->RemovePin(this); +} + + +// +// CheckMediaType +// +// Do we support this type? Provides the default support for 1 type. +HRESULT CSourceStream::CheckMediaType(const CMediaType *pMediaType) { + + CAutoLock lock(m_pFilter->pStateLock()); + + CMediaType mt; + GetMediaType(&mt); + + if (mt == *pMediaType) { + return NOERROR; + } + + return E_FAIL; +} + + +// +// GetMediaType/3 +// +// By default we support only one type +// iPosition indexes are 0-n +HRESULT CSourceStream::GetMediaType(int iPosition, __inout CMediaType *pMediaType) { + + CAutoLock lock(m_pFilter->pStateLock()); + + if (iPosition<0) { + return E_INVALIDARG; + } + if (iPosition>0) { + return VFW_S_NO_MORE_ITEMS; + } + return GetMediaType(pMediaType); +} + + +// +// Active +// +// The pin is active - start up the worker thread +HRESULT CSourceStream::Active(void) { + + CAutoLock lock(m_pFilter->pStateLock()); + + HRESULT hr; + + if (m_pFilter->IsActive()) { + return S_FALSE; // succeeded, but did not allocate resources (they already exist...) + } + + // do nothing if not connected - its ok not to connect to + // all pins of a source filter + if (!IsConnected()) { + return NOERROR; + } + + hr = CBaseOutputPin::Active(); + if (FAILED(hr)) { + return hr; + } + + ASSERT(!ThreadExists()); + + // start the thread + if (!Create()) { + return E_FAIL; + } + + // Tell thread to initialize. If OnThreadCreate Fails, so does this. + hr = Init(); + if (FAILED(hr)) + return hr; + + return Pause(); +} + + +// +// Inactive +// +// Pin is inactive - shut down the worker thread +// Waits for the worker to exit before returning. +HRESULT CSourceStream::Inactive(void) { + + CAutoLock lock(m_pFilter->pStateLock()); + + HRESULT hr; + + // do nothing if not connected - its ok not to connect to + // all pins of a source filter + if (!IsConnected()) { + return NOERROR; + } + + // !!! need to do this before trying to stop the thread, because + // we may be stuck waiting for our own allocator!!! + + hr = CBaseOutputPin::Inactive(); // call this first to Decommit the allocator + if (FAILED(hr)) { + return hr; + } + + if (ThreadExists()) { + hr = Stop(); + + if (FAILED(hr)) { + return hr; + } + + hr = Exit(); + if (FAILED(hr)) { + return hr; + } + + Close(); // Wait for the thread to exit, then tidy up. + } + + // hr = CBaseOutputPin::Inactive(); // call this first to Decommit the allocator + //if (FAILED(hr)) { + // return hr; + //} + + return NOERROR; +} + + +// +// ThreadProc +// +// When this returns the thread exits +// Return codes > 0 indicate an error occured +DWORD CSourceStream::ThreadProc(void) { + + HRESULT hr; // the return code from calls + Command com; + + do { + com = GetRequest(); + if (com != CMD_INIT) { + DbgLog((LOG_ERROR, 1, TEXT("Thread expected init command"))); + Reply((DWORD) E_UNEXPECTED); + } + } while (com != CMD_INIT); + + DbgLog((LOG_TRACE, 1, TEXT("CSourceStream worker thread initializing"))); + + hr = OnThreadCreate(); // perform set up tasks + if (FAILED(hr)) { + DbgLog((LOG_ERROR, 1, TEXT("CSourceStream::OnThreadCreate failed. Aborting thread."))); + OnThreadDestroy(); + Reply(hr); // send failed return code from OnThreadCreate + return 1; + } + + // Initialisation suceeded + Reply(NOERROR); + + Command cmd; + do { + cmd = GetRequest(); + + switch (cmd) { + + case CMD_EXIT: + Reply(NOERROR); + break; + + case CMD_RUN: + DbgLog((LOG_ERROR, 1, TEXT("CMD_RUN received before a CMD_PAUSE???"))); + // !!! fall through??? + + case CMD_PAUSE: + Reply(NOERROR); + DoBufferProcessingLoop(); + break; + + case CMD_STOP: + Reply(NOERROR); + break; + + default: + DbgLog((LOG_ERROR, 1, TEXT("Unknown command %d received!"), cmd)); + Reply((DWORD) E_NOTIMPL); + break; + } + } while (cmd != CMD_EXIT); + + hr = OnThreadDestroy(); // tidy up. + if (FAILED(hr)) { + DbgLog((LOG_ERROR, 1, TEXT("CSourceStream::OnThreadDestroy failed. Exiting thread."))); + return 1; + } + + DbgLog((LOG_TRACE, 1, TEXT("CSourceStream worker thread exiting"))); + return 0; +} + + +// +// DoBufferProcessingLoop +// +// Grabs a buffer and calls the users processing function. +// Overridable, so that different delivery styles can be catered for. +HRESULT CSourceStream::DoBufferProcessingLoop(void) { + + Command com; + + OnThreadStartPlay(); + + do { + while (!CheckRequest(&com)) { + + IMediaSample *pSample; + + HRESULT hr = GetDeliveryBuffer(&pSample,NULL,NULL,0); + if (FAILED(hr)) { + Sleep(1); + continue; // go round again. Perhaps the error will go away + // or the allocator is decommited & we will be asked to + // exit soon. + } + + // Virtual function user will override. + hr = FillBuffer(pSample); + + if (hr == S_OK) { + hr = Deliver(pSample); + pSample->Release(); + + // downstream filter returns S_FALSE if it wants us to + // stop or an error if it's reporting an error. + if(hr != S_OK) + { + DbgLog((LOG_TRACE, 2, TEXT("Deliver() returned %08x; stopping"), hr)); + return S_OK; + } + + } else if (hr == S_FALSE) { + // derived class wants us to stop pushing data + pSample->Release(); + DeliverEndOfStream(); + return S_OK; + } else { + // derived class encountered an error + pSample->Release(); + DbgLog((LOG_ERROR, 1, TEXT("Error %08lX from FillBuffer!!!"), hr)); + DeliverEndOfStream(); + m_pFilter->NotifyEvent(EC_ERRORABORT, hr, 0); + return hr; + } + + // all paths release the sample + } + + // For all commands sent to us there must be a Reply call! + + if (com == CMD_RUN || com == CMD_PAUSE) { + Reply(NOERROR); + } else if (com != CMD_STOP) { + Reply((DWORD) E_UNEXPECTED); + DbgLog((LOG_ERROR, 1, TEXT("Unexpected command!!!"))); + } + } while (com != CMD_STOP); + + return S_FALSE; +} + diff --git a/src/baseclasses/source.h b/src/baseclasses/source.h new file mode 100644 index 0000000..e6e451b --- /dev/null +++ b/src/baseclasses/source.h @@ -0,0 +1,172 @@ +//------------------------------------------------------------------------------ +// File: Source.h +// +// Desc: DirectShow base classes - defines classes to simplify creation of +// ActiveX source filters that support continuous generation of data. +// No support is provided for IMediaControl or IMediaPosition. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +// +// Derive your source filter from CSource. +// During construction either: +// Create some CSourceStream objects to manage your pins +// Provide the user with a means of doing so eg, an IPersistFile interface. +// +// CSource provides: +// IBaseFilter interface management +// IMediaFilter interface management, via CBaseFilter +// Pin counting for CBaseFilter +// +// Derive a class from CSourceStream to manage your output pin types +// Implement GetMediaType/1 to return the type you support. If you support multiple +// types then overide GetMediaType/3, CheckMediaType and GetMediaTypeCount. +// Implement Fillbuffer() to put data into one buffer. +// +// CSourceStream provides: +// IPin management via CBaseOutputPin +// Worker thread management + +#ifndef __CSOURCE__ +#define __CSOURCE__ + +class CSourceStream; // The class that will handle each pin + + +// +// CSource +// +// Override construction to provide a means of creating +// CSourceStream derived objects - ie a way of creating pins. +class CSource : public CBaseFilter { +public: + + CSource(__in_opt LPCTSTR pName, __inout_opt LPUNKNOWN lpunk, CLSID clsid, __inout HRESULT *phr); + CSource(__in_opt LPCTSTR pName, __inout_opt LPUNKNOWN lpunk, CLSID clsid); +#ifdef UNICODE + CSource(__in_opt LPCSTR pName, __inout_opt LPUNKNOWN lpunk, CLSID clsid, __inout HRESULT *phr); + CSource(__in_opt LPCSTR pName, __inout_opt LPUNKNOWN lpunk, CLSID clsid); +#endif + ~CSource(); + + int GetPinCount(void); + CBasePin *GetPin(int n); + + // -- Utilities -- + + CCritSec* pStateLock(void) { return &m_cStateLock; } // provide our critical section + + HRESULT AddPin(__in CSourceStream *); + HRESULT RemovePin(__in CSourceStream *); + + STDMETHODIMP FindPin( + LPCWSTR Id, + __deref_out IPin ** ppPin + ); + + int FindPinNumber(__in IPin *iPin); + +protected: + + int m_iPins; // The number of pins on this filter. Updated by CSourceStream + // constructors & destructors. + CSourceStream **m_paStreams; // the pins on this filter. + + CCritSec m_cStateLock; // Lock this to serialize function accesses to the filter state + +}; + + +// +// CSourceStream +// +// Use this class to manage a stream of data that comes from a +// pin. +// Uses a worker thread to put data on the pin. +class CSourceStream : public CAMThread, public CBaseOutputPin { +public: + + CSourceStream(__in_opt LPCTSTR pObjectName, + __inout HRESULT *phr, + __inout CSource *pms, + __in_opt LPCWSTR pName); +#ifdef UNICODE + CSourceStream(__in_opt LPCSTR pObjectName, + __inout HRESULT *phr, + __inout CSource *pms, + __in_opt LPCWSTR pName); +#endif + virtual ~CSourceStream(void); // virtual destructor ensures derived class destructors are called too. + +protected: + + CSource *m_pFilter; // The parent of this stream + + // * + // * Data Source + // * + // * The following three functions: FillBuffer, OnThreadCreate/Destroy, are + // * called from within the ThreadProc. They are used in the creation of + // * the media samples this pin will provide + // * + + // Override this to provide the worker thread a means + // of processing a buffer + virtual HRESULT FillBuffer(IMediaSample *pSamp) PURE; + + // Called as the thread is created/destroyed - use to perform + // jobs such as start/stop streaming mode + // If OnThreadCreate returns an error the thread will exit. + virtual HRESULT OnThreadCreate(void) {return NOERROR;}; + virtual HRESULT OnThreadDestroy(void) {return NOERROR;}; + virtual HRESULT OnThreadStartPlay(void) {return NOERROR;}; + + // * + // * Worker Thread + // * + + HRESULT Active(void); // Starts up the worker thread + HRESULT Inactive(void); // Exits the worker thread. + +public: + // thread commands + enum Command {CMD_INIT, CMD_PAUSE, CMD_RUN, CMD_STOP, CMD_EXIT}; + HRESULT Init(void) { return CallWorker(CMD_INIT); } + HRESULT Exit(void) { return CallWorker(CMD_EXIT); } + HRESULT Run(void) { return CallWorker(CMD_RUN); } + HRESULT Pause(void) { return CallWorker(CMD_PAUSE); } + HRESULT Stop(void) { return CallWorker(CMD_STOP); } + +protected: + Command GetRequest(void) { return (Command) CAMThread::GetRequest(); } + BOOL CheckRequest(Command *pCom) { return CAMThread::CheckRequest( (DWORD *) pCom); } + + // override these if you want to add thread commands + virtual DWORD ThreadProc(void); // the thread function + + virtual HRESULT DoBufferProcessingLoop(void); // the loop executed whilst running + + + // * + // * AM_MEDIA_TYPE support + // * + + // If you support more than one media type then override these 2 functions + virtual HRESULT CheckMediaType(const CMediaType *pMediaType); + virtual HRESULT GetMediaType(int iPosition, __inout CMediaType *pMediaType); // List pos. 0-n + + // If you support only one type then override this fn. + // This will only be called by the default implementations + // of CheckMediaType and GetMediaType(int, CMediaType*) + // You must override this fn. or the above 2! + virtual HRESULT GetMediaType(__inout CMediaType *pMediaType) {return E_UNEXPECTED;} + + STDMETHODIMP QueryId( + __deref_out LPWSTR * Id + ); +}; + +#endif // __CSOURCE__ + diff --git a/src/baseclasses/streams.h b/src/baseclasses/streams.h new file mode 100644 index 0000000..541037c --- /dev/null +++ b/src/baseclasses/streams.h @@ -0,0 +1,202 @@ +//------------------------------------------------------------------------------ +// File: Streams.h +// +// Desc: DirectShow base classes - defines overall streams architecture. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#ifndef __STREAMS__ +#define __STREAMS__ + +#ifdef _MSC_VER +// disable some level-4 warnings, use #pragma warning(enable:###) to re-enable +#pragma warning(disable:4100) // warning C4100: unreferenced formal parameter +#pragma warning(disable:4201) // warning C4201: nonstandard extension used : nameless struct/union +#pragma warning(disable:4511) // warning C4511: copy constructor could not be generated +#pragma warning(disable:4512) // warning C4512: assignment operator could not be generated +#pragma warning(disable:4514) // warning C4514: "unreferenced inline function has been removed" + +#if _MSC_VER>=1100 +#define AM_NOVTABLE __declspec(novtable) +#else +#define AM_NOVTABLE +#endif +#endif // MSC_VER + + +// Because of differences between Visual C++ and older Microsoft SDKs, +// you may have defined _DEBUG without defining DEBUG. This logic +// ensures that both will be set if Visual C++ sets _DEBUG. +#ifdef _DEBUG +#ifndef DEBUG +#define DEBUG +#endif +#endif + + +#include +#include +#include +#include +#include + + +#ifndef NUMELMS +#if _WIN32_WINNT < 0x0600 + #define NUMELMS(aa) (sizeof(aa)/sizeof((aa)[0])) +#else + #define NUMELMS(aa) ARRAYSIZE(aa) +#endif +#endif + +/////////////////////////////////////////////////////////////////////////// +// The following definitions come from the Platform SDK and are required if +// the applicaiton is being compiled with the headers from Visual C++ 6.0. +/////////////////////////////////////////////////// //////////////////////// +#ifndef InterlockedExchangePointer + #define InterlockedExchangePointer(Target, Value) \ + (PVOID)InterlockedExchange((PLONG)(Target), (LONG)(Value)) +#endif + +#ifndef _WAVEFORMATEXTENSIBLE_ +#define _WAVEFORMATEXTENSIBLE_ +typedef struct { + WAVEFORMATEX Format; + union { + WORD wValidBitsPerSample; /* bits of precision */ + WORD wSamplesPerBlock; /* valid if wBitsPerSample==0 */ + WORD wReserved; /* If neither applies, set to zero. */ + } Samples; + DWORD dwChannelMask; /* which channels are */ + /* present in stream */ + GUID SubFormat; +} WAVEFORMATEXTENSIBLE, *PWAVEFORMATEXTENSIBLE; +#endif // !_WAVEFORMATEXTENSIBLE_ + +#if !defined(WAVE_FORMAT_EXTENSIBLE) +#define WAVE_FORMAT_EXTENSIBLE 0xFFFE +#endif // !defined(WAVE_FORMAT_EXTENSIBLE) + +#ifndef GetWindowLongPtr + #define GetWindowLongPtrA GetWindowLongA + #define GetWindowLongPtrW GetWindowLongW + #ifdef UNICODE + #define GetWindowLongPtr GetWindowLongPtrW + #else + #define GetWindowLongPtr GetWindowLongPtrA + #endif // !UNICODE +#endif // !GetWindowLongPtr + +#ifndef SetWindowLongPtr + #define SetWindowLongPtrA SetWindowLongA + #define SetWindowLongPtrW SetWindowLongW + #ifdef UNICODE + #define SetWindowLongPtr SetWindowLongPtrW + #else + #define SetWindowLongPtr SetWindowLongPtrA + #endif // !UNICODE +#endif // !SetWindowLongPtr + +#ifndef GWLP_WNDPROC + #define GWLP_WNDPROC (-4) +#endif +#ifndef GWLP_HINSTANCE + #define GWLP_HINSTANCE (-6) +#endif +#ifndef GWLP_HWNDPARENT + #define GWLP_HWNDPARENT (-8) +#endif +#ifndef GWLP_USERDATA + #define GWLP_USERDATA (-21) +#endif +#ifndef GWLP_ID + #define GWLP_ID (-12) +#endif +#ifndef DWLP_MSGRESULT + #define DWLP_MSGRESULT 0 +#endif +#ifndef DWLP_DLGPROC + #define DWLP_DLGPROC DWLP_MSGRESULT + sizeof(LRESULT) +#endif +#ifndef DWLP_USER + #define DWLP_USER DWLP_DLGPROC + sizeof(DLGPROC) +#endif + + +#pragma warning(push) +#pragma warning(disable: 4312 4244) +// _GetWindowLongPtr +// Templated version of GetWindowLongPtr, to suppress spurious compiler warning. +template +T _GetWindowLongPtr(HWND hwnd, int nIndex) +{ + return (T)GetWindowLongPtr(hwnd, nIndex); +} + +// _SetWindowLongPtr +// Templated version of SetWindowLongPtr, to suppress spurious compiler warning. +template +LONG_PTR _SetWindowLongPtr(HWND hwnd, int nIndex, T p) +{ + return SetWindowLongPtr(hwnd, nIndex, (LONG_PTR)p); +} +#pragma warning(pop) + +/////////////////////////////////////////////////////////////////////////// +// End Platform SDK definitions +/////////////////////////////////////////////////////////////////////////// + + +#include // Generated IDL header file for streams interfaces +#include // required by amvideo.h + +#include // Helper class for REFERENCE_TIME management +#include // Debug support for logging and ASSERTs +#include // ActiveMovie video interfaces and definitions +//include amaudio.h explicitly if you need it. it requires the DX SDK. +//#include // ActiveMovie audio interfaces and definitions +#include // General helper classes for threads etc +#include // Base COM classes to support IUnknown +#include // Filter registration support functions +#include // Performance measurement +//#include // Light weight com function prototypes + +//#include // Simple cache container class +#include // Non MFC generic list class +//#include // CMsgThread +#include // Helper class for managing media types +#include // conversions between FOURCCs and GUIDs +#include // generated from control.odl +//#include // control interface utility classes +#include // event code definitions +#include // Main streams architecture class hierachy +//#include // Generic transform filter +//#include // Generic transform-in-place filter +#include // declaration of type GUIDs and well-known clsids +#include // Generic source filter +//#include // Output pin queueing +#include // HRESULT status and error definitions +//#include // Base class for writing ActiveX renderers +#include // Helps with filters that manage windows +//#include // Implements the IVideoWindow interface +//#include // Specifically video related classes +#include // Base clock class +//#include // System clock +//#include // IPersistStream helper class +//#include // Video Transform Filter base class +//#include +//#include // Base property page class +//#include // IAMStreamControl support +//#include // External device control interface defines +//#include // audio filter device error event codes + + + +#else + #ifdef DEBUG + #pragma message("STREAMS.H included TWICE") + #endif +#endif // __STREAMS__ + diff --git a/src/baseclasses/winutil.cpp b/src/baseclasses/winutil.cpp new file mode 100644 index 0000000..5357d01 --- /dev/null +++ b/src/baseclasses/winutil.cpp @@ -0,0 +1,2746 @@ +//------------------------------------------------------------------------------ +// File: WinUtil.cpp +// +// Desc: DirectShow base classes - implements generic window handler class. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#include +#include +#include +#include +#include + +static UINT MsgDestroy; + +// Constructor + +CBaseWindow::CBaseWindow(BOOL bDoGetDC, bool bDoPostToDestroy) : + m_hInstance(g_hInst), + m_hwnd(NULL), + m_hdc(NULL), + m_bActivated(FALSE), + m_pClassName(NULL), + m_ClassStyles(0), + m_WindowStyles(0), + m_WindowStylesEx(0), + m_ShowStageMessage(0), + m_ShowStageTop(0), + m_MemoryDC(NULL), + m_hPalette(NULL), + m_bBackground(FALSE), +#ifdef DEBUG + m_bRealizing(FALSE), +#endif + m_bNoRealize(FALSE), + m_bDoPostToDestroy(bDoPostToDestroy) +{ + m_bDoGetDC = bDoGetDC; +} + + +// Prepare a window by spinning off a worker thread to do the creation and +// also poll the message input queue. We leave this to be called by derived +// classes because they might want to override methods like MessageLoop and +// InitialiseWindow, if we do this during construction they'll ALWAYS call +// this base class methods. We make the worker thread create the window so +// it owns it rather than the filter graph thread which is constructing us + +HRESULT CBaseWindow::PrepareWindow() +{ + if (m_hwnd) return NOERROR; + ASSERT(m_hwnd == NULL); + ASSERT(m_hdc == NULL); + + // Get the derived object's window and class styles + + m_pClassName = GetClassWindowStyles(&m_ClassStyles, + &m_WindowStyles, + &m_WindowStylesEx); + if (m_pClassName == NULL) { + return E_FAIL; + } + + // Register our special private messages + m_ShowStageMessage = RegisterWindowMessage(SHOWSTAGE); + + // RegisterWindowMessage() returns 0 if an error occurs. + if (0 == m_ShowStageMessage) { + return AmGetLastErrorToHResult(); + } + + m_ShowStageTop = RegisterWindowMessage(SHOWSTAGETOP); + if (0 == m_ShowStageTop) { + return AmGetLastErrorToHResult(); + } + + m_RealizePalette = RegisterWindowMessage(REALIZEPALETTE); + if (0 == m_RealizePalette) { + return AmGetLastErrorToHResult(); + } + + MsgDestroy = RegisterWindowMessage(TEXT("AM_DESTROY")); + if (0 == MsgDestroy) { + return AmGetLastErrorToHResult(); + } + + return DoCreateWindow(); +} + + +// Destructor just a placeholder so that we know it becomes virtual +// Derived classes MUST call DoneWithWindow in their destructors so +// that no messages arrive after the derived class constructor ends + +#ifdef DEBUG +CBaseWindow::~CBaseWindow() +{ + ASSERT(m_hwnd == NULL); + ASSERT(m_hdc == NULL); +} +#endif + + +// We use the sync worker event to have the window destroyed. All we do is +// signal the event and wait on the window thread handle. Trying to send it +// messages causes too many problems, furthermore to be on the safe side we +// just wait on the thread handle while it returns WAIT_TIMEOUT or there is +// a sent message to process on this thread. If the constructor failed to +// create the thread in the first place then the loop will get terminated + +HRESULT CBaseWindow::DoneWithWindow() +{ + if (!IsWindow(m_hwnd) || (GetWindowThreadProcessId(m_hwnd, NULL) != GetCurrentThreadId())) { + + if (IsWindow(m_hwnd)) { + + // This code should only be executed if the window exists and if the window's + // messages are processed on a different thread. + ASSERT(GetWindowThreadProcessId(m_hwnd, NULL) != GetCurrentThreadId()); + + if (m_bDoPostToDestroy) { + + HRESULT hr = S_OK; + CAMEvent m_evDone(FALSE, &hr); + if (FAILED(hr)) { + return hr; + } + + // We must post a message to destroy the window + // That way we can't be in the middle of processing a + // message posted to our window when we do go away + // Sending a message gives less synchronization. + PostMessage(m_hwnd, MsgDestroy, (WPARAM)(HANDLE)m_evDone, 0); + WaitDispatchingMessages(m_evDone, INFINITE); + } else { + SendMessage(m_hwnd, MsgDestroy, 0, 0); + } + } + + // + // This is not a leak, the window manager automatically free's + // hdc's that were got via GetDC, which is the case here. + // We set it to NULL so that we don't get any asserts later. + // + m_hdc = NULL; + + // + // We need to free this DC though because USER32 does not know + // anything about it. + // + if (m_MemoryDC) + { + EXECUTE_ASSERT(DeleteDC(m_MemoryDC)); + m_MemoryDC = NULL; + } + + // Reset the window variables + m_hwnd = NULL; + + return NOERROR; + } + const HWND hwnd = m_hwnd; + if (hwnd == NULL) { + return NOERROR; + } + + InactivateWindow(); + NOTE("Inactivated"); + + // Reset the window styles before destruction + + SetWindowLong(hwnd,GWL_STYLE,m_WindowStyles); + ASSERT(GetParent(hwnd) == NULL); + NOTE1("Reset window styles %d",m_WindowStyles); + + // UnintialiseWindow sets m_hwnd to NULL so save a copy + UninitialiseWindow(); + DbgLog((LOG_TRACE, 2, TEXT("Destroying 0x%8.8X"), hwnd)); + if (!DestroyWindow(hwnd)) { + DbgLog((LOG_TRACE, 0, TEXT("DestroyWindow %8.8X failed code %d"), + hwnd, GetLastError())); + DbgBreak(""); + } + + // Reset our state so we can be prepared again + + m_pClassName = NULL; + m_ClassStyles = 0; + m_WindowStyles = 0; + m_WindowStylesEx = 0; + m_ShowStageMessage = 0; + m_ShowStageTop = 0; + + return NOERROR; +} + + +// Called at the end to put the window in an inactive state. The pending list +// will always have been cleared by this time so event if the worker thread +// gets has been signaled and gets in to render something it will find both +// the state has been changed and that there are no available sample images +// Since we wait on the window thread to complete we don't lock the object + +HRESULT CBaseWindow::InactivateWindow() +{ + // Has the window been activated + if (m_bActivated == FALSE) { + return S_FALSE; + } + + m_bActivated = FALSE; + ShowWindow(m_hwnd,SW_HIDE); + return NOERROR; +} + + +HRESULT CBaseWindow::CompleteConnect() +{ + m_bActivated = FALSE; + return NOERROR; +} + +// This displays a normal window. We ask the base window class for default +// sizes which unless overriden will return DEFWIDTH and DEFHEIGHT. We go +// through a couple of extra hoops to get the client area the right size +// as the object specifies which accounts for the AdjustWindowRectEx calls +// We also DWORD align the left and top coordinates of the window here to +// maximise the chance of being able to use DCI/DirectDraw primary surface + +HRESULT CBaseWindow::ActivateWindow() +{ + // Has the window been sized and positioned already + + if (m_bActivated == TRUE || GetParent(m_hwnd) != NULL) { + + SetWindowPos(m_hwnd, // Our window handle + HWND_TOP, // Put it at the top + 0, 0, 0, 0, // Leave in current position + SWP_NOMOVE | // Don't change it's place + SWP_NOSIZE); // Change Z-order only + + m_bActivated = TRUE; + return S_FALSE; + } + + // Calculate the desired client rectangle + + RECT WindowRect, ClientRect = GetDefaultRect(); + GetWindowRect(m_hwnd,&WindowRect); + AdjustWindowRectEx(&ClientRect,GetWindowLong(m_hwnd,GWL_STYLE), + FALSE,GetWindowLong(m_hwnd,GWL_EXSTYLE)); + + // Align left and top edges on DWORD boundaries + + UINT WindowFlags = (SWP_NOACTIVATE | SWP_FRAMECHANGED); + WindowRect.left -= (WindowRect.left & 3); + WindowRect.top -= (WindowRect.top & 3); + + SetWindowPos(m_hwnd, // Window handle + HWND_TOP, // Put it at the top + WindowRect.left, // Align left edge + WindowRect.top, // And also top place + WIDTH(&ClientRect), // Horizontal size + HEIGHT(&ClientRect), // Vertical size + WindowFlags); // Don't show window + + m_bActivated = TRUE; + return NOERROR; +} + + +// This can be used to DWORD align the window for maximum performance + +HRESULT CBaseWindow::PerformanceAlignWindow() +{ + RECT ClientRect,WindowRect; + GetWindowRect(m_hwnd,&WindowRect); + ASSERT(m_bActivated == TRUE); + + // Don't do this if we're owned + + if (GetParent(m_hwnd)) { + return NOERROR; + } + + // Align left and top edges on DWORD boundaries + + GetClientRect(m_hwnd, &ClientRect); + MapWindowPoints(m_hwnd, HWND_DESKTOP, (LPPOINT) &ClientRect, 2); + WindowRect.left -= (ClientRect.left & 3); + WindowRect.top -= (ClientRect.top & 3); + UINT WindowFlags = (SWP_NOACTIVATE | SWP_NOSIZE); + + SetWindowPos(m_hwnd, // Window handle + HWND_TOP, // Put it at the top + WindowRect.left, // Align left edge + WindowRect.top, // And also top place + (int) 0,(int) 0, // Ignore these sizes + WindowFlags); // Don't show window + + return NOERROR; +} + + +// Install a palette into the base window - we may be called by a different +// thread to the one that owns the window. We have to be careful how we do +// the palette realisation as we could be a different thread to the window +// which would cause an inter thread send message. Therefore we realise the +// palette by sending it a special message but without the window locked + +HRESULT CBaseWindow::SetPalette(HPALETTE hPalette) +{ + // We must own the window lock during the change + { + CAutoLock cWindowLock(&m_WindowLock); + CAutoLock cPaletteLock(&m_PaletteLock); + ASSERT(hPalette); + m_hPalette = hPalette; + } + return SetPalette(); +} + + +HRESULT CBaseWindow::SetPalette() +{ + if (!m_bNoRealize) { + SendMessage(m_hwnd, m_RealizePalette, 0, 0); + return S_OK; + } else { + // Just select the palette + ASSERT(m_hdc); + ASSERT(m_MemoryDC); + + CAutoLock cPaletteLock(&m_PaletteLock); + SelectPalette(m_hdc,m_hPalette,m_bBackground); + SelectPalette(m_MemoryDC,m_hPalette,m_bBackground); + + return S_OK; + } +} + + +void CBaseWindow::UnsetPalette() +{ + CAutoLock cWindowLock(&m_WindowLock); + CAutoLock cPaletteLock(&m_PaletteLock); + + // Get a standard VGA colour palette + + HPALETTE hPalette = (HPALETTE) GetStockObject(DEFAULT_PALETTE); + ASSERT(hPalette); + + SelectPalette(GetWindowHDC(), hPalette, TRUE); + SelectPalette(GetMemoryHDC(), hPalette, TRUE); + + m_hPalette = NULL; +} + + +void CBaseWindow::LockPaletteLock() +{ + m_PaletteLock.Lock(); +} + + +void CBaseWindow::UnlockPaletteLock() +{ + m_PaletteLock.Unlock(); +} + + +// Realise our palettes in the window and device contexts + +HRESULT CBaseWindow::DoRealisePalette(BOOL bForceBackground) +{ + { + CAutoLock cPaletteLock(&m_PaletteLock); + + if (m_hPalette == NULL) { + return NOERROR; + } + + // Realize the palette on the window thread + ASSERT(m_hdc); + ASSERT(m_MemoryDC); + + SelectPalette(m_hdc,m_hPalette,m_bBackground || bForceBackground); + SelectPalette(m_MemoryDC,m_hPalette,m_bBackground); + } + + // If we grab a critical section here we can deadlock + // with the window thread because one of the side effects + // of RealizePalette is to send a WM_PALETTECHANGED message + // to every window in the system. In our handling + // of WM_PALETTECHANGED we used to grab this CS too. + // The really bad case is when our renderer calls DoRealisePalette() + // while we're in the middle of processing a palette change + // for another window. + // So don't hold the critical section while actually realising + // the palette. In any case USER is meant to manage palette + // handling - we shouldn't have to serialize everything as well + ASSERT(CritCheckOut(&m_WindowLock)); + ASSERT(CritCheckOut(&m_PaletteLock)); + + EXECUTE_ASSERT(RealizePalette(m_hdc) != GDI_ERROR); + EXECUTE_ASSERT(RealizePalette(m_MemoryDC) != GDI_ERROR); + + return (GdiFlush() == FALSE ? S_FALSE : S_OK); +} + + +// This is the global window procedure + +LRESULT CALLBACK WndProc(HWND hwnd, // Window handle + UINT uMsg, // Message ID + WPARAM wParam, // First parameter + LPARAM lParam) // Other parameter +{ + + // Get the window long that holds our window object pointer + // If it is NULL then we are initialising the window in which + // case the object pointer has been passed in the window creation + // structure. IF we get any messages before WM_NCCREATE we will + // pass them to DefWindowProc. + + CBaseWindow *pBaseWindow = _GetWindowLongPtr(hwnd,0); + + if (pBaseWindow == NULL) { + + // Get the structure pointer from the create struct. + // We can only do this for WM_NCCREATE which should be one of + // the first messages we receive. Anything before this will + // have to be passed to DefWindowProc (i.e. WM_GETMINMAXINFO) + + // If the message is WM_NCCREATE we set our pBaseWindow pointer + // and will then place it in the window structure + + // turn off WS_EX_LAYOUTRTL style for quartz windows + if (uMsg == WM_NCCREATE) { + SetWindowLong(hwnd, GWL_EXSTYLE, GetWindowLong(hwnd, GWL_EXSTYLE) & ~0x400000); + } + + if ((uMsg != WM_NCCREATE) + || (NULL == (pBaseWindow = *(CBaseWindow**) ((LPCREATESTRUCT)lParam)->lpCreateParams))) + { + return(DefWindowProc(hwnd, uMsg, wParam, lParam)); + } + + // Set the window LONG to be the object who created us +#ifdef DEBUG + SetLastError(0); // because of the way SetWindowLong works +#endif + + LONG_PTR rc = _SetWindowLongPtr(hwnd, (DWORD) 0, pBaseWindow); + + +#ifdef DEBUG + if (0 == rc) { + // SetWindowLong MIGHT have failed. (Read the docs which admit + // that it is awkward to work out if you have had an error.) + LONG lasterror = GetLastError(); + ASSERT(0 == lasterror); + // If this is not the case we have not set the pBaseWindow pointer + // into the window structure and we will blow up. + } +#endif + + } + // See if this is the packet of death + if (uMsg == MsgDestroy && uMsg != 0) { + pBaseWindow->DoneWithWindow(); + if (pBaseWindow->m_bDoPostToDestroy) { + EXECUTE_ASSERT(SetEvent((HANDLE)wParam)); + } + return 0; + } + return pBaseWindow->OnReceiveMessage(hwnd,uMsg,wParam,lParam); +} + + +// When the window size changes we adjust our member variables that +// contain the dimensions of the client rectangle for our window so +// that we come to render an image we will know whether to stretch + +BOOL CBaseWindow::OnSize(LONG Width, LONG Height) +{ + m_Width = Width; + m_Height = Height; + return TRUE; +} + + +// This function handles the WM_CLOSE message + +BOOL CBaseWindow::OnClose() +{ + ShowWindow(m_hwnd,SW_HIDE); + return TRUE; +} + + +// This is called by the worker window thread when it receives a terminate +// message from the window object destructor to delete all the resources we +// allocated during initialisation. By the time the worker thread exits all +// processing will have been completed as the source filter disconnection +// flushes the image pending sample, therefore the GdiFlush should succeed + +HRESULT CBaseWindow::UninitialiseWindow() +{ + // Have we already cleaned up + + if (m_hwnd == NULL) { + ASSERT(m_hdc == NULL); + ASSERT(m_MemoryDC == NULL); + return NOERROR; + } + + // Release the window resources + + EXECUTE_ASSERT(GdiFlush()); + + if (m_hdc) + { + EXECUTE_ASSERT(ReleaseDC(m_hwnd,m_hdc)); + m_hdc = NULL; + } + + if (m_MemoryDC) + { + EXECUTE_ASSERT(DeleteDC(m_MemoryDC)); + m_MemoryDC = NULL; + } + + // Reset the window variables + m_hwnd = NULL; + + return NOERROR; +} + + +// This is called by the worker window thread after it has created the main +// window and it wants to initialise the rest of the owner objects window +// variables such as the device contexts. We execute this function with the +// critical section still locked. Nothing in this function must generate any +// SendMessage calls to the window because this is executing on the window +// thread so the message will never be processed and we will deadlock + +HRESULT CBaseWindow::InitialiseWindow(HWND hwnd) +{ + // Initialise the window variables + + ASSERT(IsWindow(hwnd)); + m_hwnd = hwnd; + + if (m_bDoGetDC) + { + EXECUTE_ASSERT(m_hdc = GetDC(hwnd)); + EXECUTE_ASSERT(m_MemoryDC = CreateCompatibleDC(m_hdc)); + + EXECUTE_ASSERT(SetStretchBltMode(m_hdc,COLORONCOLOR)); + EXECUTE_ASSERT(SetStretchBltMode(m_MemoryDC,COLORONCOLOR)); + } + + return NOERROR; +} + +HRESULT CBaseWindow::DoCreateWindow() +{ + WNDCLASS wndclass; // Used to register classes + BOOL bRegistered; // Is this class registered + HWND hwnd; // Handle to our window + + bRegistered = GetClassInfo(m_hInstance, // Module instance + m_pClassName, // Window class + &wndclass); // Info structure + + // if the window is to be used for drawing puposes and we are getting a DC + // for the entire lifetime of the window then changes the class style to do + // say so. If we don't set this flag then the DC comes from the cache and is + // really bad. + if (m_bDoGetDC) + { + m_ClassStyles |= CS_OWNDC; + } + + if (bRegistered == FALSE) { + + // Register the renderer window class + + wndclass.lpszClassName = m_pClassName; + wndclass.style = m_ClassStyles; + wndclass.lpfnWndProc = WndProc; + wndclass.cbClsExtra = 0; + wndclass.cbWndExtra = sizeof(CBaseWindow *); + wndclass.hInstance = m_hInstance; + wndclass.hIcon = NULL; + wndclass.hCursor = LoadCursor (NULL, IDC_ARROW); + wndclass.hbrBackground = (HBRUSH) NULL; + wndclass.lpszMenuName = NULL; + + RegisterClass(&wndclass); + } + + // Create the frame window. Pass the pBaseWindow information in the + // CreateStruct which allows our message handling loop to get hold of + // the pBaseWindow pointer. + + CBaseWindow *pBaseWindow = this; // The owner window object + hwnd = CreateWindowEx(m_WindowStylesEx, // Extended styles + m_pClassName, // Registered name + TEXT("ActiveMovie Window"), // Window title + m_WindowStyles, // Window styles + CW_USEDEFAULT, // Start x position + CW_USEDEFAULT, // Start y position + DEFWIDTH, // Window width + DEFHEIGHT, // Window height + NULL, // Parent handle + NULL, // Menu handle + m_hInstance, // Instance handle + &pBaseWindow); // Creation data + + // If we failed signal an error to the object constructor (based on the + // last Win32 error on this thread) then signal the constructor thread + // to continue, release the mutex to let others have a go and exit + + if (hwnd == NULL) { + DWORD Error = GetLastError(); + return AmHresultFromWin32(Error); + } + + // Check the window LONG is the object who created us + ASSERT(GetWindowLongPtr(hwnd, 0) == (LONG_PTR)this); + + // Initialise the window and then signal the constructor so that it can + // continue and then finally unlock the object's critical section. The + // window class is left registered even after we terminate the thread + // as we don't know when the last window has been closed. So we allow + // the operating system to free the class resources as appropriate + + InitialiseWindow(hwnd); + + DbgLog((LOG_TRACE, 2, TEXT("Created window class (%s) HWND(%8.8X)"), + m_pClassName, hwnd)); + + return S_OK; +} + + +// The base class provides some default handling and calls DefWindowProc + +LRESULT CBaseWindow::OnReceiveMessage(HWND hwnd, // Window handle + UINT uMsg, // Message ID + WPARAM wParam, // First parameter + LPARAM lParam) // Other parameter +{ + ASSERT(IsWindow(hwnd)); + + if (PossiblyEatMessage(uMsg, wParam, lParam)) + return 0; + + // This is sent by the IVideoWindow SetWindowForeground method. If the + // window is invisible we will show it and make it topmost without the + // foreground focus. If the window is visible it will also be made the + // topmost window without the foreground focus. If wParam is TRUE then + // for both cases the window will be forced into the foreground focus + + if (uMsg == m_ShowStageMessage) { + + BOOL bVisible = IsWindowVisible(hwnd); + SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW | + (bVisible ? SWP_NOACTIVATE : 0)); + + // Should we bring the window to the foreground + if (wParam == TRUE) { + SetForegroundWindow(hwnd); + } + return (LRESULT) 1; + } + + // When we go fullscreen we have to add the WS_EX_TOPMOST style to the + // video window so that it comes out above any task bar (this is more + // relevant to WindowsNT than Windows95). However the SetWindowPos call + // must be on the same thread as that which created the window. The + // wParam parameter can be TRUE or FALSE to set and reset the topmost + + if (uMsg == m_ShowStageTop) { + HWND HwndTop = (wParam == TRUE ? HWND_TOPMOST : HWND_NOTOPMOST); + BOOL bVisible = IsWindowVisible(hwnd); + SetWindowPos(hwnd, HwndTop, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | + (wParam == TRUE ? SWP_SHOWWINDOW : 0) | + (bVisible ? SWP_NOACTIVATE : 0)); + return (LRESULT) 1; + } + + // New palette stuff + if (uMsg == m_RealizePalette) { + ASSERT(m_hwnd == hwnd); + return OnPaletteChange(m_hwnd,WM_QUERYNEWPALETTE); + } + + switch (uMsg) { + + // Repaint the window if the system colours change + + case WM_SYSCOLORCHANGE: + + InvalidateRect(hwnd,NULL,FALSE); + return (LRESULT) 1; + + // Somebody has changed the palette + case WM_PALETTECHANGED: + + OnPaletteChange((HWND)wParam,uMsg); + return (LRESULT) 0; + + // We are about to receive the keyboard focus so we ask GDI to realise + // our logical palette again and hopefully it will be fully installed + // without any mapping having to be done during any picture rendering + + case WM_QUERYNEWPALETTE: + ASSERT(m_hwnd == hwnd); + return OnPaletteChange(m_hwnd,uMsg); + + // do NOT fwd WM_MOVE. the parameters are the location of the parent + // window, NOT what the renderer should be looking at. But we need + // to make sure the overlay is moved with the parent window, so we + // do this. + case WM_MOVE: + if (IsWindowVisible(m_hwnd)) { + PostMessage(m_hwnd,WM_PAINT,0,0); + } + break; + + // Store the width and height as useful base class members + + case WM_SIZE: + + OnSize(LOWORD(lParam), HIWORD(lParam)); + return (LRESULT) 0; + + // Intercept the WM_CLOSE messages to hide the window + + case WM_CLOSE: + + OnClose(); + return (LRESULT) 0; + } + return DefWindowProc(hwnd,uMsg,wParam,lParam); +} + + +// This handles the Windows palette change messages - if we do realise our +// palette then we return TRUE otherwise we return FALSE. If our window is +// foreground application then we should get first choice of colours in the +// system palette entries. We get best performance when our logical palette +// includes the standard VGA colours (at the beginning and end) otherwise +// GDI may have to map from our palette to the device palette while drawing + +LRESULT CBaseWindow::OnPaletteChange(HWND hwnd,UINT Message) +{ + // First check we are not changing the palette during closedown + + if (m_hwnd == NULL || hwnd == NULL) { + return (LRESULT) 0; + } + ASSERT(!m_bRealizing); + + // Should we realise our palette again + + if ((Message == WM_QUERYNEWPALETTE || hwnd != m_hwnd)) { + // It seems that even if we're invisible that we can get asked + // to realize our palette and this can cause really ugly side-effects + // Seems like there's another bug but this masks it a least for the + // shutting down case. + if (!IsWindowVisible(m_hwnd)) { + DbgLog((LOG_TRACE, 1, TEXT("Realizing when invisible!"))); + return (LRESULT) 0; + } + + // Avoid recursion with multiple graphs in the same app +#ifdef DEBUG + m_bRealizing = TRUE; +#endif + DoRealisePalette(Message != WM_QUERYNEWPALETTE); +#ifdef DEBUG + m_bRealizing = FALSE; +#endif + + // Should we redraw the window with the new palette + if (Message == WM_PALETTECHANGED) { + InvalidateRect(m_hwnd,NULL,FALSE); + } + } + + return (LRESULT) 1; +} + + +// Determine if the window exists. + +bool CBaseWindow::WindowExists() +{ + return !!IsWindow(m_hwnd); +} + + +// Return the default window rectangle + +RECT CBaseWindow::GetDefaultRect() +{ + RECT DefaultRect = {0,0,DEFWIDTH,DEFHEIGHT}; + ASSERT(m_hwnd); + // ASSERT(m_hdc); + return DefaultRect; +} + + +// Return the current window width + +LONG CBaseWindow::GetWindowWidth() +{ + ASSERT(m_hwnd); + // ASSERT(m_hdc); + return m_Width; +} + + +// Return the current window height + +LONG CBaseWindow::GetWindowHeight() +{ + ASSERT(m_hwnd); + // ASSERT(m_hdc); + return m_Height; +} + + +// Return the window handle + +HWND CBaseWindow::GetWindowHWND() +{ + ASSERT(m_hwnd); + // ASSERT(m_hdc); + return m_hwnd; +} + + +// Return the window drawing device context + +HDC CBaseWindow::GetWindowHDC() +{ + ASSERT(m_hwnd); + ASSERT(m_hdc); + return m_hdc; +} + + +// Return the offscreen window drawing device context + +HDC CBaseWindow::GetMemoryHDC() +{ + ASSERT(m_hwnd); + ASSERT(m_MemoryDC); + return m_MemoryDC; +} + + +#ifdef DEBUG +HPALETTE CBaseWindow::GetPalette() +{ + // The palette lock should always be held when accessing + // m_hPalette. + ASSERT(CritCheckIn(&m_PaletteLock)); + return m_hPalette; +} +#endif // DEBUG + + +// This is available to clients who want to change the window visiblity. It's +// little more than an indirection to the Win32 ShowWindow although these is +// some benefit in going through here as this function may change sometime + +HRESULT CBaseWindow::DoShowWindow(LONG ShowCmd) +{ + ShowWindow(m_hwnd,ShowCmd); + return NOERROR; +} + + +// Generate a WM_PAINT message for the video window + +void CBaseWindow::PaintWindow(BOOL bErase) +{ + InvalidateRect(m_hwnd,NULL,bErase); +} + + +// Allow an application to have us set the video window in the foreground. We +// have this because it is difficult for one thread to do do this to a window +// owned by another thread. Rather than expose the message we use to execute +// the inter thread send message we provide the interface function. All we do +// is to SendMessage to the video window renderer thread with a WM_SHOWSTAGE + +void CBaseWindow::DoSetWindowForeground(BOOL bFocus) +{ + SendMessage(m_hwnd,m_ShowStageMessage,(WPARAM) bFocus,(LPARAM) 0); +} + + +// Constructor initialises the owning object pointer. Since we are a worker +// class for the main window object we have relatively few state variables to +// look after. We are given device context handles to use later on as well as +// the source and destination rectangles (but reset them here just in case) + +CDrawImage::CDrawImage(__inout CBaseWindow *pBaseWindow) : + m_pBaseWindow(pBaseWindow), + m_hdc(NULL), + m_MemoryDC(NULL), + m_bStretch(FALSE), + m_pMediaType(NULL), + m_bUsingImageAllocator(FALSE) +{ + ASSERT(pBaseWindow); + ResetPaletteVersion(); + SetRectEmpty(&m_TargetRect); + SetRectEmpty(&m_SourceRect); + + m_perfidRenderTime = MSR_REGISTER(TEXT("Single Blt time")); +} + + +// Overlay the image time stamps on the picture. Access to this method is +// serialised by the caller. We display the sample start and end times on +// top of the video using TextOut on the device context we are handed. If +// there isn't enough room in the window for the times we don't show them + +void CDrawImage::DisplaySampleTimes(IMediaSample *pSample) +{ +#ifdef DEBUG + // + // Only allow the "annoying" time messages if the users has turned the + // logging "way up" + // + BOOL bAccept = DbgCheckModuleLevel(LOG_TRACE, 5); + if (bAccept == FALSE) { + return; + } +#endif + + TCHAR szTimes[TIMELENGTH]; // Time stamp strings + ASSERT(pSample); // Quick sanity check + RECT ClientRect; // Client window size + SIZE Size; // Size of text output + + // Get the time stamps and window size + + pSample->GetTime((REFERENCE_TIME*)&m_StartSample, (REFERENCE_TIME*)&m_EndSample); + HWND hwnd = m_pBaseWindow->GetWindowHWND(); + EXECUTE_ASSERT(GetClientRect(hwnd,&ClientRect)); + + // Format the sample time stamps + + (void)StringCchPrintf(szTimes,NUMELMS(szTimes),TEXT("%08d : %08d"), + m_StartSample.Millisecs(), + m_EndSample.Millisecs()); + + ASSERT(lstrlen(szTimes) < TIMELENGTH); + + // Put the times in the middle at the bottom of the window + + GetTextExtentPoint32(m_hdc,szTimes,lstrlen(szTimes),&Size); + INT XPos = ((ClientRect.right - ClientRect.left) - Size.cx) / 2; + INT YPos = ((ClientRect.bottom - ClientRect.top) - Size.cy) * 4 / 5; + + // Check the window is big enough to have sample times displayed + + if ((XPos > 0) && (YPos > 0)) { + TextOut(m_hdc,XPos,YPos,szTimes,lstrlen(szTimes)); + } +} + + +// This is called when the drawing code sees that the image has a down level +// palette cookie. We simply call the SetDIBColorTable Windows API with the +// palette that is found after the BITMAPINFOHEADER - we return no errors + +void CDrawImage::UpdateColourTable(HDC hdc,__in BITMAPINFOHEADER *pbmi) +{ + ASSERT(pbmi->biClrUsed); + RGBQUAD *pColourTable = (RGBQUAD *)(pbmi+1); + + // Set the new palette in the device context + + UINT uiReturn = SetDIBColorTable(hdc,(UINT) 0, + pbmi->biClrUsed, + pColourTable); + + // Should always succeed but check in debug builds + ASSERT(uiReturn == pbmi->biClrUsed); +} + + +// No source rectangle scaling is done by the base class + +RECT CDrawImage::ScaleSourceRect(const RECT *pSource) +{ + ASSERT(pSource); + return *pSource; +} + + +// This is called when the funky output pin uses our allocator. The samples we +// allocate are special because the memory is shared between us and GDI thus +// removing one copy when we ask for the image to be rendered. The source type +// information is in the main renderer m_mtIn field which is initialised when +// the media type is agreed in SetMediaType, the media type may be changed on +// the fly if, for example, the source filter needs to change the palette + +void CDrawImage::FastRender(IMediaSample *pMediaSample) +{ + BITMAPINFOHEADER *pbmi; // Image format data + DIBDATA *pDibData; // Stores DIB information + BYTE *pImage; // Pointer to image data + HBITMAP hOldBitmap; // Store the old bitmap + CImageSample *pSample; // Pointer to C++ object + + ASSERT(m_pMediaType); + + // From the untyped source format block get the VIDEOINFO and subsequently + // the BITMAPINFOHEADER structure. We can cast the IMediaSample interface + // to a CImageSample object so we can retrieve it's DIBSECTION details + + pbmi = HEADER(m_pMediaType->Format()); + pSample = (CImageSample *) pMediaSample; + pDibData = pSample->GetDIBData(); + hOldBitmap = (HBITMAP) SelectObject(m_MemoryDC,pDibData->hBitmap); + + // Get a pointer to the real image data + + HRESULT hr = pMediaSample->GetPointer(&pImage); + if (FAILED(hr)) { + return; + } + + // Do we need to update the colour table, we increment our palette cookie + // each time we get a dynamic format change. The sample palette cookie is + // stored in the DIBDATA structure so we try to keep the fields in sync + // By the time we get to draw the images the format change will be done + // so all we do is ask the renderer for what it's palette version is + + if (pDibData->PaletteVersion < GetPaletteVersion()) { + ASSERT(pbmi->biBitCount <= iPALETTE); + UpdateColourTable(m_MemoryDC,pbmi); + pDibData->PaletteVersion = GetPaletteVersion(); + } + + // This allows derived classes to change the source rectangle that we do + // the drawing with. For example a renderer may ask a codec to stretch + // the video from 320x240 to 640x480, in which case the source we see in + // here will still be 320x240, although the source we want to draw with + // should be scaled up to 640x480. The base class implementation of this + // method does nothing but return the same rectangle as we are passed in + + RECT SourceRect = ScaleSourceRect(&m_SourceRect); + + // Is the window the same size as the video + + if (m_bStretch == FALSE) { + + // Put the image straight into the window + + BitBlt( + (HDC) m_hdc, // Target device HDC + m_TargetRect.left, // X sink position + m_TargetRect.top, // Y sink position + m_TargetRect.right - m_TargetRect.left, // Destination width + m_TargetRect.bottom - m_TargetRect.top, // Destination height + m_MemoryDC, // Source device context + SourceRect.left, // X source position + SourceRect.top, // Y source position + SRCCOPY); // Simple copy + + } else { + + // Stretch the image when copying to the window + + StretchBlt( + (HDC) m_hdc, // Target device HDC + m_TargetRect.left, // X sink position + m_TargetRect.top, // Y sink position + m_TargetRect.right - m_TargetRect.left, // Destination width + m_TargetRect.bottom - m_TargetRect.top, // Destination height + m_MemoryDC, // Source device HDC + SourceRect.left, // X source position + SourceRect.top, // Y source position + SourceRect.right - SourceRect.left, // Source width + SourceRect.bottom - SourceRect.top, // Source height + SRCCOPY); // Simple copy + } + + // This displays the sample times over the top of the image. This used to + // draw the times into the offscreen device context however that actually + // writes the text into the image data buffer which may not be writable + + #ifdef DEBUG + DisplaySampleTimes(pMediaSample); + #endif + + // Put the old bitmap back into the device context so we don't leak + SelectObject(m_MemoryDC,hOldBitmap); +} + + +// This is called when there is a sample ready to be drawn, unfortunately the +// output pin was being rotten and didn't choose our super excellent shared +// memory DIB allocator so we have to do this slow render using boring old GDI +// SetDIBitsToDevice and StretchDIBits. The down side of using these GDI +// functions is that the image data has to be copied across from our address +// space into theirs before going to the screen (although in reality the cost +// is small because all they do is to map the buffer into their address space) + +void CDrawImage::SlowRender(IMediaSample *pMediaSample) +{ + // Get the BITMAPINFOHEADER for the connection + + ASSERT(m_pMediaType); + BITMAPINFOHEADER *pbmi = HEADER(m_pMediaType->Format()); + BYTE *pImage; + + // Get the image data buffer + + HRESULT hr = pMediaSample->GetPointer(&pImage); + if (FAILED(hr)) { + return; + } + + // This allows derived classes to change the source rectangle that we do + // the drawing with. For example a renderer may ask a codec to stretch + // the video from 320x240 to 640x480, in which case the source we see in + // here will still be 320x240, although the source we want to draw with + // should be scaled up to 640x480. The base class implementation of this + // method does nothing but return the same rectangle as we are passed in + + RECT SourceRect = ScaleSourceRect(&m_SourceRect); + + LONG lAdjustedSourceTop = SourceRect.top; + // if the origin of bitmap is bottom-left, adjust soruce_rect_top + // to be the bottom-left corner instead of the top-left. + if (pbmi->biHeight > 0) { + lAdjustedSourceTop = pbmi->biHeight - SourceRect.bottom; + } + // Is the window the same size as the video + + if (m_bStretch == FALSE) { + + // Put the image straight into the window + + SetDIBitsToDevice( + (HDC) m_hdc, // Target device HDC + m_TargetRect.left, // X sink position + m_TargetRect.top, // Y sink position + m_TargetRect.right - m_TargetRect.left, // Destination width + m_TargetRect.bottom - m_TargetRect.top, // Destination height + SourceRect.left, // X source position + lAdjustedSourceTop, // Adjusted Y source position + (UINT) 0, // Start scan line + pbmi->biHeight, // Scan lines present + pImage, // Image data + (BITMAPINFO *) pbmi, // DIB header + DIB_RGB_COLORS); // Type of palette + + } else { + + // Stretch the image when copying to the window + + StretchDIBits( + (HDC) m_hdc, // Target device HDC + m_TargetRect.left, // X sink position + m_TargetRect.top, // Y sink position + m_TargetRect.right - m_TargetRect.left, // Destination width + m_TargetRect.bottom - m_TargetRect.top, // Destination height + SourceRect.left, // X source position + lAdjustedSourceTop, // Adjusted Y source position + SourceRect.right - SourceRect.left, // Source width + SourceRect.bottom - SourceRect.top, // Source height + pImage, // Image data + (BITMAPINFO *) pbmi, // DIB header + DIB_RGB_COLORS, // Type of palette + SRCCOPY); // Simple image copy + } + + // This shows the sample reference times over the top of the image which + // looks a little flickery. I tried using GdiSetBatchLimit and GdiFlush to + // control the screen updates but it doesn't quite work as expected and + // only partially reduces the flicker. I also tried using a memory context + // and combining the two in that before doing a final BitBlt operation to + // the screen, unfortunately this has considerable performance penalties + // and also means that this code is not executed when compiled retail + + #ifdef DEBUG + DisplaySampleTimes(pMediaSample); + #endif +} + + +// This is called with an IMediaSample interface on the image to be drawn. We +// decide on the drawing mechanism based on who's allocator we are using. We +// may be called when the window wants an image painted by WM_PAINT messages +// We can't realise the palette here because we have the renderer lock, any +// call to realise may cause an interthread send message to the window thread +// which may in turn be waiting to get the renderer lock before servicing it + +BOOL CDrawImage::DrawImage(IMediaSample *pMediaSample) +{ + ASSERT(m_hdc); + ASSERT(m_MemoryDC); + NotifyStartDraw(); + + // If the output pin used our allocator then the samples passed are in + // fact CVideoSample objects that contain CreateDIBSection data that we + // use to do faster image rendering, they may optionally also contain a + // DirectDraw surface pointer in which case we do not do the drawing + + if (m_bUsingImageAllocator == FALSE) { + SlowRender(pMediaSample); + EXECUTE_ASSERT(GdiFlush()); + NotifyEndDraw(); + return TRUE; + } + + // This is a DIBSECTION buffer + + FastRender(pMediaSample); + EXECUTE_ASSERT(GdiFlush()); + NotifyEndDraw(); + return TRUE; +} + + +BOOL CDrawImage::DrawVideoImageHere( + HDC hdc, + IMediaSample *pMediaSample, + __in LPRECT lprcSrc, + __in LPRECT lprcDst + ) +{ + ASSERT(m_pMediaType); + BITMAPINFOHEADER *pbmi = HEADER(m_pMediaType->Format()); + BYTE *pImage; + + // Get the image data buffer + + HRESULT hr = pMediaSample->GetPointer(&pImage); + if (FAILED(hr)) { + return FALSE; + } + + RECT SourceRect; + RECT TargetRect; + + if (lprcSrc) { + SourceRect = *lprcSrc; + } + else SourceRect = ScaleSourceRect(&m_SourceRect); + + if (lprcDst) { + TargetRect = *lprcDst; + } + else TargetRect = m_TargetRect; + + LONG lAdjustedSourceTop = SourceRect.top; + // if the origin of bitmap is bottom-left, adjust soruce_rect_top + // to be the bottom-left corner instead of the top-left. + if (pbmi->biHeight > 0) { + lAdjustedSourceTop = pbmi->biHeight - SourceRect.bottom; + } + + + // Stretch the image when copying to the DC + + BOOL bRet = (0 != StretchDIBits(hdc, + TargetRect.left, + TargetRect.top, + TargetRect.right - TargetRect.left, + TargetRect.bottom - TargetRect.top, + SourceRect.left, + lAdjustedSourceTop, + SourceRect.right - SourceRect.left, + SourceRect.bottom - SourceRect.top, + pImage, + (BITMAPINFO *)pbmi, + DIB_RGB_COLORS, + SRCCOPY)); + return bRet; +} + + +// This is called by the owning window object after it has created the window +// and it's drawing contexts. We are constructed with the base window we'll +// be drawing into so when given the notification we retrive the device HDCs +// to draw with. We cannot call these in our constructor as they are virtual + +void CDrawImage::SetDrawContext() +{ + m_MemoryDC = m_pBaseWindow->GetMemoryHDC(); + m_hdc = m_pBaseWindow->GetWindowHDC(); +} + + +// This is called to set the target rectangle in the video window, it will be +// called whenever a WM_SIZE message is retrieved from the message queue. We +// simply store the rectangle and use it later when we do the drawing calls + +void CDrawImage::SetTargetRect(__in RECT *pTargetRect) +{ + ASSERT(pTargetRect); + m_TargetRect = *pTargetRect; + SetStretchMode(); +} + + +// Return the current target rectangle + +void CDrawImage::GetTargetRect(__out RECT *pTargetRect) +{ + ASSERT(pTargetRect); + *pTargetRect = m_TargetRect; +} + + +// This is called when we want to change the section of the image to draw. We +// use this information in the drawing operation calls later on. We must also +// see if the source and destination rectangles have the same dimensions. If +// not we must stretch during the drawing rather than a direct pixel copy + +void CDrawImage::SetSourceRect(__in RECT *pSourceRect) +{ + ASSERT(pSourceRect); + m_SourceRect = *pSourceRect; + SetStretchMode(); +} + + +// Return the current source rectangle + +void CDrawImage::GetSourceRect(__out RECT *pSourceRect) +{ + ASSERT(pSourceRect); + *pSourceRect = m_SourceRect; +} + + +// This is called when either the source or destination rectanges change so we +// can update the stretch flag. If the rectangles don't match we stretch the +// video during the drawing otherwise we call the fast pixel copy functions +// NOTE the source and/or the destination rectangle may be completely empty + +void CDrawImage::SetStretchMode() +{ + // Calculate the overall rectangle dimensions + + LONG SourceWidth = m_SourceRect.right - m_SourceRect.left; + LONG SinkWidth = m_TargetRect.right - m_TargetRect.left; + LONG SourceHeight = m_SourceRect.bottom - m_SourceRect.top; + LONG SinkHeight = m_TargetRect.bottom - m_TargetRect.top; + + m_bStretch = TRUE; + if (SourceWidth == SinkWidth) { + if (SourceHeight == SinkHeight) { + m_bStretch = FALSE; + } + } +} + + +// Tell us whose allocator we are using. This should be called with TRUE if +// the filter agrees to use an allocator based around the CImageAllocator +// SDK base class - whose image buffers are made through CreateDIBSection. +// Otherwise this should be called with FALSE and we will draw the images +// using SetDIBitsToDevice and StretchDIBitsToDevice. None of these calls +// can handle buffers which have non zero strides (like DirectDraw uses) + +void CDrawImage::NotifyAllocator(BOOL bUsingImageAllocator) +{ + m_bUsingImageAllocator = bUsingImageAllocator; +} + + +// Are we using the image DIBSECTION allocator + +BOOL CDrawImage::UsingImageAllocator() +{ + return m_bUsingImageAllocator; +} + + +// We need the media type of the connection so that we can get the BITMAPINFO +// from it. We use that in the calls to draw the image such as StretchDIBits +// and also when updating the colour table held in shared memory DIBSECTIONs + +void CDrawImage::NotifyMediaType(__in CMediaType *pMediaType) +{ + m_pMediaType = pMediaType; +} + + +// We store in this object a cookie maintaining the current palette version. +// Each time a palettised format is changed we increment this value so that +// when we come to draw the images we look at the colour table value they +// have and if less than the current we know to update it. This version is +// only needed and indeed used when working with shared memory DIBSECTIONs + +LONG CDrawImage::GetPaletteVersion() +{ + return m_PaletteVersion; +} + + +// Resets the current palette version number + +void CDrawImage::ResetPaletteVersion() +{ + m_PaletteVersion = PALETTE_VERSION; +} + + +// Increment the current palette version + +void CDrawImage::IncrementPaletteVersion() +{ + m_PaletteVersion++; +} + + +// Constructor must initialise the base allocator. Each sample we create has a +// palette version cookie on board. When the source filter changes the palette +// during streaming the window object increments an internal cookie counter it +// keeps as well. When it comes to render the samples it looks at the cookie +// values and if they don't match then it knows to update the sample's colour +// table. However we always create samples with a cookie of PALETTE_VERSION +// If there have been multiple format changes and we disconnect and reconnect +// thereby causing the samples to be reallocated we will create them with a +// cookie much lower than the current version, this isn't a problem since it +// will be seen by the window object and the versions will then be updated + +CImageAllocator::CImageAllocator(__inout CBaseFilter *pFilter, + __in_opt LPCTSTR pName, + __inout HRESULT *phr) : + CBaseAllocator(pName,NULL,phr,TRUE,TRUE), + m_pFilter(pFilter) +{ + ASSERT(phr); + ASSERT(pFilter); +} + + +// Check our DIB buffers have been released + +#ifdef DEBUG +CImageAllocator::~CImageAllocator() +{ + ASSERT(m_bCommitted == FALSE); +} +#endif + + +// Called from destructor and also from base class to free resources. We work +// our way through the list of media samples deleting the DIBSECTION created +// for each. All samples should be back in our list so there is no chance a +// filter is still using one to write on the display or hold on a pending list + +void CImageAllocator::Free() +{ + ASSERT(m_lAllocated == m_lFree.GetCount()); + EXECUTE_ASSERT(GdiFlush()); + CImageSample *pSample; + DIBDATA *pDibData; + + while (m_lFree.GetCount() != 0) { + pSample = (CImageSample *) m_lFree.RemoveHead(); + pDibData = pSample->GetDIBData(); + EXECUTE_ASSERT(DeleteObject(pDibData->hBitmap)); + EXECUTE_ASSERT(CloseHandle(pDibData->hMapping)); + delete pSample; + } + + m_lAllocated = 0; +} + + +// Prepare the allocator by checking all the input parameters + +STDMETHODIMP CImageAllocator::CheckSizes(__in ALLOCATOR_PROPERTIES *pRequest) +{ + // Check we have a valid connection + + if (m_pMediaType == NULL) { + return VFW_E_NOT_CONNECTED; + } + + // NOTE We always create a DIB section with the source format type which + // may contain a source palette. When we do the BitBlt drawing operation + // the target display device may contain a different palette (we may not + // have the focus) in which case GDI will do after the palette mapping + + VIDEOINFOHEADER *pVideoInfo = (VIDEOINFOHEADER *) m_pMediaType->Format(); + + // When we call CreateDIBSection it implicitly maps only enough memory + // for the image as defined by thee BITMAPINFOHEADER. If the user asks + // for an image smaller than this then we reject the call, if they ask + // for an image larger than this then we return what they can have + + if ((DWORD) pRequest->cbBuffer < pVideoInfo->bmiHeader.biSizeImage) { + return E_INVALIDARG; + } + + // Reject buffer prefixes + + if (pRequest->cbPrefix > 0) { + return E_INVALIDARG; + } + + pRequest->cbBuffer = pVideoInfo->bmiHeader.biSizeImage; + return NOERROR; +} + + +// Agree the number of media sample buffers and their sizes. The base class +// this allocator is derived from allows samples to be aligned only on byte +// boundaries NOTE the buffers are not allocated until the Commit call + +STDMETHODIMP CImageAllocator::SetProperties( + __in ALLOCATOR_PROPERTIES * pRequest, + __out ALLOCATOR_PROPERTIES * pActual) +{ + ALLOCATOR_PROPERTIES Adjusted = *pRequest; + + // Check the parameters fit with the current connection + + HRESULT hr = CheckSizes(&Adjusted); + if (FAILED(hr)) { + return hr; + } + return CBaseAllocator::SetProperties(&Adjusted, pActual); +} + + +// Commit the memory by allocating the agreed number of media samples. For +// each sample we are committed to creating we have a CImageSample object +// that we use to manage it's resources. This is initialised with a DIBDATA +// structure that contains amongst other things the GDI DIBSECTION handle +// We will access the renderer media type during this so we must have locked +// (to prevent the format changing for example). The class overrides Commit +// and Decommit to do this locking (base class Commit in turn calls Alloc) + +HRESULT CImageAllocator::Alloc(void) +{ + ASSERT(m_pMediaType); + CImageSample *pSample; + DIBDATA DibData; + + // Check the base allocator says it's ok to continue + + HRESULT hr = CBaseAllocator::Alloc(); + if (FAILED(hr)) { + return hr; + } + + // We create a new memory mapped object although we don't map it into our + // address space because GDI does that in CreateDIBSection. It is possible + // that we run out of resources before creating all the samples in which + // case the available sample list is left with those already created + + ASSERT(m_lAllocated == 0); + while (m_lAllocated < m_lCount) { + + // Create and initialise a shared memory GDI buffer + + hr = CreateDIB(m_lSize,DibData); + if (FAILED(hr)) { + return hr; + } + + // Create the sample object and pass it the DIBDATA + + pSample = CreateImageSample(DibData.pBase,m_lSize); + if (pSample == NULL) { + EXECUTE_ASSERT(DeleteObject(DibData.hBitmap)); + EXECUTE_ASSERT(CloseHandle(DibData.hMapping)); + return E_OUTOFMEMORY; + } + + // Add the completed sample to the available list + + pSample->SetDIBData(&DibData); + m_lFree.Add(pSample); + m_lAllocated++; + } + return NOERROR; +} + + +// We have a virtual method that allocates the samples so that a derived class +// may override it and allocate more specialised sample objects. So long as it +// derives its samples from CImageSample then all this code will still work ok + +CImageSample *CImageAllocator::CreateImageSample(__in_bcount(Length) LPBYTE pData,LONG Length) +{ + HRESULT hr = NOERROR; + CImageSample *pSample; + + // Allocate the new sample and check the return codes + + pSample = new CImageSample((CBaseAllocator *) this, // Base class + NAME("Video sample"), // DEBUG name + (HRESULT *) &hr, // Return code + (LPBYTE) pData, // DIB address + (LONG) Length); // Size of DIB + + if (pSample == NULL || FAILED(hr)) { + delete pSample; + return NULL; + } + return pSample; +} + + +// This function allocates a shared memory block for use by the source filter +// generating DIBs for us to render. The memory block is created in shared +// memory so that GDI doesn't have to copy the memory when we do a BitBlt + +HRESULT CImageAllocator::CreateDIB(LONG InSize,DIBDATA &DibData) +{ + BITMAPINFO *pbmi; // Format information for pin + BYTE *pBase; // Pointer to the actual image + HANDLE hMapping; // Handle to mapped object + HBITMAP hBitmap; // DIB section bitmap handle + + // Create a file mapping object and map into our address space + + hMapping = CreateFileMapping(hMEMORY, // Use system page file + NULL, // No security attributes + PAGE_READWRITE, // Full access to memory + (DWORD) 0, // Less than 4Gb in size + InSize, // Size of buffer + NULL); // No name to section + if (hMapping == NULL) { + DWORD Error = GetLastError(); + return MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, Error); + } + + // NOTE We always create a DIB section with the source format type which + // may contain a source palette. When we do the BitBlt drawing operation + // the target display device may contain a different palette (we may not + // have the focus) in which case GDI will do after the palette mapping + + pbmi = (BITMAPINFO *) HEADER(m_pMediaType->Format()); + if (m_pMediaType == NULL) { + DbgBreak("Invalid media type"); + } + + hBitmap = CreateDIBSection((HDC) NULL, // NO device context + pbmi, // Format information + DIB_RGB_COLORS, // Use the palette + (VOID **) &pBase, // Pointer to image data + hMapping, // Mapped memory handle + (DWORD) 0); // Offset into memory + + if (hBitmap == NULL || pBase == NULL) { + EXECUTE_ASSERT(CloseHandle(hMapping)); + DWORD Error = GetLastError(); + return MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, Error); + } + + // Initialise the DIB information structure + + DibData.hBitmap = hBitmap; + DibData.hMapping = hMapping; + DibData.pBase = pBase; + DibData.PaletteVersion = PALETTE_VERSION; + GetObject(hBitmap,sizeof(DIBSECTION),(VOID *)&DibData.DibSection); + + return NOERROR; +} + + +// We use the media type during the DIBSECTION creation + +void CImageAllocator::NotifyMediaType(__in CMediaType *pMediaType) +{ + m_pMediaType = pMediaType; +} + + +// Overriden to increment the owning object's reference count + +STDMETHODIMP_(ULONG) CImageAllocator::NonDelegatingAddRef() +{ + return m_pFilter->AddRef(); +} + + +// Overriden to decrement the owning object's reference count + +STDMETHODIMP_(ULONG) CImageAllocator::NonDelegatingRelease() +{ + return m_pFilter->Release(); +} + + +// If you derive a class from CMediaSample that has to transport specialised +// member variables and entry points then there are three alternate solutions +// The first is to create a memory buffer larger than actually required by the +// sample and store your information either at the beginning of it or at the +// end, the former being moderately safer allowing for misbehaving transform +// filters. You then adjust the buffer address when you create the base media +// sample. This has the disadvantage of breaking up the memory allocated to +// the samples into separate blocks. The second solution is to implement a +// class derived from CMediaSample and support additional interface(s) that +// convey your private data. This means defining a custom interface. The final +// alternative is to create a class that inherits from CMediaSample and adds +// the private data structures, when you get an IMediaSample in your Receive() +// call check to see if your allocator is being used, and if it is then cast +// the IMediaSample into one of your objects. Additional checks can be made +// to ensure the sample's this pointer is known to be one of your own objects + +CImageSample::CImageSample(__inout CBaseAllocator *pAllocator, + __in_opt LPCTSTR pName, + __inout HRESULT *phr, + __in_bcount(length) LPBYTE pBuffer, + LONG length) : + CMediaSample(pName,pAllocator,phr,pBuffer,length), + m_bInit(FALSE) +{ + ASSERT(pAllocator); + ASSERT(pBuffer); +} + + +// Set the shared memory DIB information + +void CImageSample::SetDIBData(__in DIBDATA *pDibData) +{ + ASSERT(pDibData); + m_DibData = *pDibData; + m_bInit = TRUE; +} + + +// Retrieve the shared memory DIB data + +__out DIBDATA *CImageSample::GetDIBData() +{ + ASSERT(m_bInit == TRUE); + return &m_DibData; +} + + +// This class handles the creation of a palette. It is fairly specialist and +// is intended to simplify palette management for video renderer filters. It +// is for this reason that the constructor requires three other objects with +// which it interacts, namely a base media filter, a base window and a base +// drawing object although the base window or the draw object may be NULL to +// ignore that part of us. We try not to create and install palettes unless +// absolutely necessary as they typically require WM_PALETTECHANGED messages +// to be sent to every window thread in the system which is very expensive + +CImagePalette::CImagePalette(__inout CBaseFilter *pBaseFilter, + __inout CBaseWindow *pBaseWindow, + __inout CDrawImage *pDrawImage) : + m_pBaseWindow(pBaseWindow), + m_pFilter(pBaseFilter), + m_pDrawImage(pDrawImage), + m_hPalette(NULL) +{ + ASSERT(m_pFilter); +} + + +// Destructor + +#ifdef DEBUG +CImagePalette::~CImagePalette() +{ + ASSERT(m_hPalette == NULL); +} +#endif + + +// We allow dynamic format changes of the palette but rather than change the +// palette every time we call this to work out whether an update is required. +// If the original type didn't use a palette and the new one does (or vica +// versa) then we return TRUE. If neither formats use a palette we'll return +// FALSE. If both formats use a palette we compare their colours and return +// FALSE if they match. This therefore short circuits palette creation unless +// absolutely necessary since installing palettes is an expensive operation + +BOOL CImagePalette::ShouldUpdate(const VIDEOINFOHEADER *pNewInfo, + const VIDEOINFOHEADER *pOldInfo) +{ + // We may not have a current format yet + + if (pOldInfo == NULL) { + return TRUE; + } + + // Do both formats not require a palette + + if (ContainsPalette(pNewInfo) == FALSE) { + if (ContainsPalette(pOldInfo) == FALSE) { + return FALSE; + } + } + + // Compare the colours to see if they match + + DWORD VideoEntries = pNewInfo->bmiHeader.biClrUsed; + if (ContainsPalette(pNewInfo) == TRUE) + if (ContainsPalette(pOldInfo) == TRUE) + if (pOldInfo->bmiHeader.biClrUsed == VideoEntries) + if (pOldInfo->bmiHeader.biClrUsed > 0) + if (memcmp((PVOID) GetBitmapPalette(pNewInfo), + (PVOID) GetBitmapPalette(pOldInfo), + VideoEntries * sizeof(RGBQUAD)) == 0) { + + return FALSE; + } + return TRUE; +} + + +// This is normally called when the input pin type is set to install a palette +// We will typically be called from two different places. The first is when we +// have negotiated a palettised media type after connection, the other is when +// we receive a new type during processing with an updated palette in which +// case we must remove and release the resources held by the current palette + +// We can be passed an optional device name if we wish to prepare a palette +// for a specific monitor on a multi monitor system + +HRESULT CImagePalette::PreparePalette(const CMediaType *pmtNew, + const CMediaType *pmtOld, + __in LPSTR szDevice) +{ + const VIDEOINFOHEADER *pNewInfo = (VIDEOINFOHEADER *) pmtNew->Format(); + const VIDEOINFOHEADER *pOldInfo = (VIDEOINFOHEADER *) pmtOld->Format(); + ASSERT(pNewInfo); + + // This is an performance optimisation, when we get a media type we check + // to see if the format requires a palette change. If either we need one + // when previously we didn't or vica versa then this returns TRUE, if we + // previously needed a palette and we do now it compares their colours + + if (ShouldUpdate(pNewInfo,pOldInfo) == FALSE) { + NOTE("No update needed"); + return S_FALSE; + } + + // We must notify the filter graph that the application may have changed + // the palette although in practice we don't bother checking to see if it + // is really different. If it tries to get the palette either the window + // or renderer lock will ensure it doesn't get in until we are finished + + RemovePalette(); + m_pFilter->NotifyEvent(EC_PALETTE_CHANGED,0,0); + + // Do we need a palette for the new format + + if (ContainsPalette(pNewInfo) == FALSE) { + NOTE("New has no palette"); + return S_FALSE; + } + + if (m_pBaseWindow) { + m_pBaseWindow->LockPaletteLock(); + } + + // If we're changing the palette on the fly then we increment our palette + // cookie which is compared against the cookie also stored in all of our + // DIBSECTION media samples. If they don't match when we come to draw it + // then we know the sample is out of date and we'll update it's palette + + NOTE("Making new colour palette"); + m_hPalette = MakePalette(pNewInfo, szDevice); + ASSERT(m_hPalette != NULL); + + if (m_pBaseWindow) { + m_pBaseWindow->UnlockPaletteLock(); + } + + // The window in which the new palette is to be realised may be a NULL + // pointer to signal that no window is in use, if so we don't call it + // Some filters just want to use this object to create/manage palettes + + if (m_pBaseWindow) m_pBaseWindow->SetPalette(m_hPalette); + + // This is the only time where we need access to the draw object to say + // to it that a new palette will be arriving on a sample real soon. The + // constructor may take a NULL pointer in which case we don't call this + + if (m_pDrawImage) m_pDrawImage->IncrementPaletteVersion(); + return NOERROR; +} + + +// Helper function to copy a palette out of any kind of VIDEOINFO (ie it may +// be YUV or true colour) into a palettised VIDEOINFO. We use this changing +// palettes on DirectDraw samples as a source filter can attach a palette to +// any buffer (eg YUV) and hand it back. We make a new palette out of that +// format and then copy the palette colours into the current connection type + +HRESULT CImagePalette::CopyPalette(const CMediaType *pSrc,__out CMediaType *pDest) +{ + // Reset the destination palette before starting + + VIDEOINFOHEADER *pDestInfo = (VIDEOINFOHEADER *) pDest->Format(); + pDestInfo->bmiHeader.biClrUsed = 0; + pDestInfo->bmiHeader.biClrImportant = 0; + + // Does the destination have a palette + + if (PALETTISED(pDestInfo) == FALSE) { + NOTE("No destination palette"); + return S_FALSE; + } + + // Does the source contain a palette + + const VIDEOINFOHEADER *pSrcInfo = (VIDEOINFOHEADER *) pSrc->Format(); + if (ContainsPalette(pSrcInfo) == FALSE) { + NOTE("No source palette"); + return S_FALSE; + } + + // The number of colours may be zero filled + + DWORD PaletteEntries = pSrcInfo->bmiHeader.biClrUsed; + if (PaletteEntries == 0) { + DWORD Maximum = (1 << pSrcInfo->bmiHeader.biBitCount); + NOTE1("Setting maximum colours (%d)",Maximum); + PaletteEntries = Maximum; + } + + // Make sure the destination has enough room for the palette + + ASSERT(pSrcInfo->bmiHeader.biClrUsed <= iPALETTE_COLORS); + ASSERT(pSrcInfo->bmiHeader.biClrImportant <= PaletteEntries); + ASSERT(COLORS(pDestInfo) == GetBitmapPalette(pDestInfo)); + pDestInfo->bmiHeader.biClrUsed = PaletteEntries; + pDestInfo->bmiHeader.biClrImportant = pSrcInfo->bmiHeader.biClrImportant; + ULONG BitmapSize = GetBitmapFormatSize(HEADER(pSrcInfo)); + + if (pDest->FormatLength() < BitmapSize) { + NOTE("Reallocating destination"); + pDest->ReallocFormatBuffer(BitmapSize); + } + + // Now copy the palette colours across + + CopyMemory((PVOID) COLORS(pDestInfo), + (PVOID) GetBitmapPalette(pSrcInfo), + PaletteEntries * sizeof(RGBQUAD)); + + return NOERROR; +} + + +// This is normally called when the palette is changed (typically during a +// dynamic format change) to remove any palette we previously installed. We +// replace it (if necessary) in the video window with a standard VGA palette +// that should always be available even if this is a true colour display + +HRESULT CImagePalette::RemovePalette() +{ + if (m_pBaseWindow) { + m_pBaseWindow->LockPaletteLock(); + } + + // Do we have a palette to remove + + if (m_hPalette != NULL) { + + if (m_pBaseWindow) { + // Make sure that the window's palette handle matches + // our palette handle. + ASSERT(m_hPalette == m_pBaseWindow->GetPalette()); + + m_pBaseWindow->UnsetPalette(); + } + + EXECUTE_ASSERT(DeleteObject(m_hPalette)); + m_hPalette = NULL; + } + + if (m_pBaseWindow) { + m_pBaseWindow->UnlockPaletteLock(); + } + + return NOERROR; +} + + +// Called to create a palette for the object, the data structure used by GDI +// to describe a palette is a LOGPALETTE, this includes a variable number of +// PALETTEENTRY fields which are the colours, we have to convert the RGBQUAD +// colour fields we are handed in a BITMAPINFO from the media type into these +// This handles extraction of palettes from true colour and YUV media formats + +// We can be passed an optional device name if we wish to prepare a palette +// for a specific monitor on a multi monitor system + +HPALETTE CImagePalette::MakePalette(const VIDEOINFOHEADER *pVideoInfo, __in LPSTR szDevice) +{ + ASSERT(ContainsPalette(pVideoInfo) == TRUE); + ASSERT(pVideoInfo->bmiHeader.biClrUsed <= iPALETTE_COLORS); + BITMAPINFOHEADER *pHeader = HEADER(pVideoInfo); + + const RGBQUAD *pColours; // Pointer to the palette + LOGPALETTE *lp; // Used to create a palette + HPALETTE hPalette; // Logical palette object + + lp = (LOGPALETTE *) new BYTE[sizeof(LOGPALETTE) + SIZE_PALETTE]; + if (lp == NULL) { + return NULL; + } + + // Unfortunately for some hare brained reason a GDI palette entry (a + // PALETTEENTRY structure) is different to a palette entry from a DIB + // format (a RGBQUAD structure) so we have to do the field conversion + // The VIDEOINFO containing the palette may be a true colour type so + // we use GetBitmapPalette to skip over any bit fields if they exist + + lp->palVersion = PALVERSION; + lp->palNumEntries = (USHORT) pHeader->biClrUsed; + if (lp->palNumEntries == 0) lp->palNumEntries = (1 << pHeader->biBitCount); + pColours = GetBitmapPalette(pVideoInfo); + + for (DWORD dwCount = 0;dwCount < lp->palNumEntries;dwCount++) { + lp->palPalEntry[dwCount].peRed = pColours[dwCount].rgbRed; + lp->palPalEntry[dwCount].peGreen = pColours[dwCount].rgbGreen; + lp->palPalEntry[dwCount].peBlue = pColours[dwCount].rgbBlue; + lp->palPalEntry[dwCount].peFlags = 0; + } + + MakeIdentityPalette(lp->palPalEntry, lp->palNumEntries, szDevice); + + // Create a logical palette + + hPalette = CreatePalette(lp); + ASSERT(hPalette != NULL); + delete[] lp; + return hPalette; +} + + +// GDI does a fair job of compressing the palette entries you give it, so for +// example if you have five entries with an RGB colour (0,0,0) it will remove +// all but one of them. When you subsequently draw an image it will map from +// your logical palette to the compressed device palette. This function looks +// to see if it is trying to be an identity palette and if so sets the flags +// field in the PALETTEENTRYs so they remain expanded to boost performance + +// We can be passed an optional device name if we wish to prepare a palette +// for a specific monitor on a multi monitor system + +HRESULT CImagePalette::MakeIdentityPalette(__inout_ecount_full(iColours) PALETTEENTRY *pEntry,INT iColours, __in LPSTR szDevice) +{ + PALETTEENTRY SystemEntries[10]; // System palette entries + BOOL bIdentityPalette = TRUE; // Is an identity palette + ASSERT(iColours <= iPALETTE_COLORS); // Should have a palette + const int PalLoCount = 10; // First ten reserved colours + const int PalHiStart = 246; // Last VGA palette entries + + // Does this have the full colour range + + if (iColours < 10) { + return S_FALSE; + } + + // Apparently some displays have odd numbers of system colours + + // Get a DC on the right monitor - it's ugly, but this is the way you have + // to do it + HDC hdc; + if (szDevice == NULL || lstrcmpiLocaleIndependentA(szDevice, "DISPLAY") == 0) + hdc = CreateDCA("DISPLAY", NULL, NULL, NULL); + else + hdc = CreateDCA(NULL, szDevice, NULL, NULL); + if (NULL == hdc) { + return E_OUTOFMEMORY; + } + INT Reserved = GetDeviceCaps(hdc,NUMRESERVED); + if (Reserved != 20) { + DeleteDC(hdc); + return S_FALSE; + } + + // Compare our palette against the first ten system entries. The reason I + // don't do a memory compare between our two arrays of colours is because + // I am not sure what will be in the flags fields for the system entries + + UINT Result = GetSystemPaletteEntries(hdc,0,PalLoCount,SystemEntries); + for (UINT Count = 0;Count < Result;Count++) { + if (SystemEntries[Count].peRed != pEntry[Count].peRed || + SystemEntries[Count].peGreen != pEntry[Count].peGreen || + SystemEntries[Count].peBlue != pEntry[Count].peBlue) { + bIdentityPalette = FALSE; + } + } + + // And likewise compare against the last ten entries + + Result = GetSystemPaletteEntries(hdc,PalHiStart,PalLoCount,SystemEntries); + for (UINT Count = 0;Count < Result;Count++) { + if (INT(Count) + PalHiStart < iColours) { + if (SystemEntries[Count].peRed != pEntry[PalHiStart + Count].peRed || + SystemEntries[Count].peGreen != pEntry[PalHiStart + Count].peGreen || + SystemEntries[Count].peBlue != pEntry[PalHiStart + Count].peBlue) { + bIdentityPalette = FALSE; + } + } + } + + // If not an identity palette then return S_FALSE + + DeleteDC(hdc); + if (bIdentityPalette == FALSE) { + return S_FALSE; + } + + // Set the non VGA entries so that GDI doesn't map them + + for (UINT Count = PalLoCount;INT(Count) < min(PalHiStart,iColours);Count++) { + pEntry[Count].peFlags = PC_NOCOLLAPSE; + } + return NOERROR; +} + + +// Constructor initialises the VIDEOINFO we keep storing the current display +// format. The format can be changed at any time, to reset the format held +// by us call the RefreshDisplayType directly (it's a public method). Since +// more than one thread will typically call us (ie window threads resetting +// the type and source threads in the type checking methods) we have a lock + +CImageDisplay::CImageDisplay() +{ + RefreshDisplayType(NULL); +} + + + +// This initialises the format we hold which contains the display device type +// We do a conversion on the display device type in here so that when we start +// type checking input formats we can assume that certain fields have been set +// correctly, an example is when we make the 16 bit mask fields explicit. This +// is normally called when we receive WM_DEVMODECHANGED device change messages + +// The optional szDeviceName parameter tells us which monitor we are interested +// in for a multi monitor system + +HRESULT CImageDisplay::RefreshDisplayType(__in_opt LPSTR szDeviceName) +{ + CAutoLock cDisplayLock(this); + + // Set the preferred format type + + ZeroMemory((PVOID)&m_Display,sizeof(VIDEOINFOHEADER)+sizeof(TRUECOLORINFO)); + m_Display.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + m_Display.bmiHeader.biBitCount = FALSE; + + // Get the bit depth of a device compatible bitmap + + // get caps of whichever monitor they are interested in (multi monitor) + HDC hdcDisplay; + // it's ugly, but this is the way you have to do it + if (szDeviceName == NULL || lstrcmpiLocaleIndependentA(szDeviceName, "DISPLAY") == 0) + hdcDisplay = CreateDCA("DISPLAY", NULL, NULL, NULL); + else + hdcDisplay = CreateDCA(NULL, szDeviceName, NULL, NULL); + if (hdcDisplay == NULL) { + ASSERT(FALSE); + DbgLog((LOG_ERROR,1,TEXT("ACK! Can't get a DC for %hs"), + szDeviceName ? szDeviceName : "")); + return E_FAIL; + } else { + DbgLog((LOG_TRACE,3,TEXT("Created a DC for %s"), + szDeviceName ? szDeviceName : "")); + } + HBITMAP hbm = CreateCompatibleBitmap(hdcDisplay,1,1); + if ( hbm ) + { + GetDIBits(hdcDisplay,hbm,0,1,NULL,(BITMAPINFO *)&m_Display.bmiHeader,DIB_RGB_COLORS); + + // This call will get the colour table or the proper bitfields + GetDIBits(hdcDisplay,hbm,0,1,NULL,(BITMAPINFO *)&m_Display.bmiHeader,DIB_RGB_COLORS); + DeleteObject(hbm); + } + DeleteDC(hdcDisplay); + + // Complete the display type initialisation + + ASSERT(CheckHeaderValidity(&m_Display)); + UpdateFormat(&m_Display); + DbgLog((LOG_TRACE,3,TEXT("New DISPLAY bit depth =%d"), + m_Display.bmiHeader.biBitCount)); + return NOERROR; +} + + +// We assume throughout this code that any bitfields masks are allowed no +// more than eight bits to store a colour component. This checks that the +// bit count assumption is enforced and also makes sure that all the bits +// set are contiguous. We return a boolean TRUE if the field checks out ok + +BOOL CImageDisplay::CheckBitFields(const VIDEOINFO *pInput) +{ + DWORD *pBitFields = (DWORD *) BITMASKS(pInput); + + for (INT iColour = iRED;iColour <= iBLUE;iColour++) { + + // First of all work out how many bits are set + + DWORD SetBits = CountSetBits(pBitFields[iColour]); + if (SetBits > iMAXBITS || SetBits == 0) { + NOTE1("Bit fields for component %d invalid",iColour); + return FALSE; + } + + // Next work out the number of zero bits prefix + DWORD PrefixBits = CountPrefixBits(pBitFields[iColour]); + + // This is going to see if all the bits set are contiguous (as they + // should be). We know how much to shift them right by from the + // count of prefix bits. The number of bits set defines a mask, we + // invert this (ones complement) and AND it with the shifted bit + // fields. If the result is NON zero then there are bit(s) sticking + // out the left hand end which means they are not contiguous + + DWORD TestField = pBitFields[iColour] >> PrefixBits; + DWORD Mask = ULONG_MAX << SetBits; + if (TestField & Mask) { + NOTE1("Bit fields for component %d not contiguous",iColour); + return FALSE; + } + } + return TRUE; +} + + +// This counts the number of bits set in the input field + +DWORD CImageDisplay::CountSetBits(DWORD Field) +{ + // This is a relatively well known bit counting algorithm + + DWORD Count = 0; + DWORD init = Field; + + // Until the input is exhausted, count the number of bits + + while (init) { + init = init & (init - 1); // Turn off the bottommost bit + Count++; + } + return Count; +} + + +// This counts the number of zero bits upto the first one set NOTE the input +// field should have been previously checked to ensure there is at least one +// set although if we don't find one set we return the impossible value 32 + +DWORD CImageDisplay::CountPrefixBits(DWORD Field) +{ + DWORD Mask = 1; + DWORD Count = 0; + + while (TRUE) { + if (Field & Mask) { + return Count; + } + Count++; + + ASSERT(Mask != 0x80000000); + if (Mask == 0x80000000) { + return Count; + } + Mask <<= 1; + } +} + + +// This is called to check the BITMAPINFOHEADER for the input type. There are +// many implicit dependancies between the fields in a header structure which +// if we validate now make for easier manipulation in subsequent handling. We +// also check that the BITMAPINFOHEADER matches it's specification such that +// fields likes the number of planes is one, that it's structure size is set +// correctly and that the bitmap dimensions have not been set as negative + +BOOL CImageDisplay::CheckHeaderValidity(const VIDEOINFO *pInput) +{ + // Check the bitmap width and height are not negative. + + if (pInput->bmiHeader.biWidth <= 0 || + pInput->bmiHeader.biHeight <= 0) { + NOTE("Invalid bitmap dimensions"); + return FALSE; + } + + // Check the compression is either BI_RGB or BI_BITFIELDS + + if (pInput->bmiHeader.biCompression != BI_RGB) { + if (pInput->bmiHeader.biCompression != BI_BITFIELDS) { + NOTE("Invalid compression format"); + return FALSE; + } + } + + // If BI_BITFIELDS compression format check the colour depth + + if (pInput->bmiHeader.biCompression == BI_BITFIELDS) { + if (pInput->bmiHeader.biBitCount != 16) { + if (pInput->bmiHeader.biBitCount != 32) { + NOTE("BI_BITFIELDS not 16/32 bit depth"); + return FALSE; + } + } + } + + // Check the assumptions about the layout of the bit fields + + if (pInput->bmiHeader.biCompression == BI_BITFIELDS) { + if (CheckBitFields(pInput) == FALSE) { + NOTE("Bit fields are not valid"); + return FALSE; + } + } + + // Are the number of planes equal to one + + if (pInput->bmiHeader.biPlanes != 1) { + NOTE("Number of planes not one"); + return FALSE; + } + + // Check the image size is consistent (it can be zero) + + if (pInput->bmiHeader.biSizeImage != GetBitmapSize(&pInput->bmiHeader)) { + if (pInput->bmiHeader.biSizeImage) { + NOTE("Image size incorrectly set"); + return FALSE; + } + } + + // Check the size of the structure + + if (pInput->bmiHeader.biSize != sizeof(BITMAPINFOHEADER)) { + NOTE("Size of BITMAPINFOHEADER wrong"); + return FALSE; + } + return CheckPaletteHeader(pInput); +} + + +// This runs a few simple tests against the palette fields in the input to +// see if it looks vaguely correct. The tests look at the number of palette +// colours present, the number considered important and the biCompression +// field which should always be BI_RGB as no other formats are meaningful + +BOOL CImageDisplay::CheckPaletteHeader(const VIDEOINFO *pInput) +{ + // The checks here are for palettised videos only + + if (PALETTISED(pInput) == FALSE) { + if (pInput->bmiHeader.biClrUsed) { + NOTE("Invalid palette entries"); + return FALSE; + } + return TRUE; + } + + // Compression type of BI_BITFIELDS is meaningless for palette video + + if (pInput->bmiHeader.biCompression != BI_RGB) { + NOTE("Palettised video must be BI_RGB"); + return FALSE; + } + + // Check the number of palette colours is correct + + if (pInput->bmiHeader.biClrUsed > PALETTE_ENTRIES(pInput)) { + NOTE("Too many colours in palette"); + return FALSE; + } + + // The number of important colours shouldn't exceed the number used + + if (pInput->bmiHeader.biClrImportant > pInput->bmiHeader.biClrUsed) { + NOTE("Too many important colours"); + return FALSE; + } + return TRUE; +} + + +// Return the format of the video display + +const VIDEOINFO *CImageDisplay::GetDisplayFormat() +{ + return &m_Display; +} + + +// Return TRUE if the display uses a palette + +BOOL CImageDisplay::IsPalettised() +{ + return PALETTISED(&m_Display); +} + + +// Return the bit depth of the current display setting + +WORD CImageDisplay::GetDisplayDepth() +{ + return m_Display.bmiHeader.biBitCount; +} + + +// Initialise the optional fields in a VIDEOINFO. These are mainly to do with +// the source and destination rectangles and palette information such as the +// number of colours present. It simplifies our code just a little if we don't +// have to keep checking for all the different valid permutations in a header +// every time we want to do anything with it (an example would be creating a +// palette). We set the base class media type before calling this function so +// that the media types between the pins match after a connection is made + +HRESULT CImageDisplay::UpdateFormat(__inout VIDEOINFO *pVideoInfo) +{ + ASSERT(pVideoInfo); + + BITMAPINFOHEADER *pbmi = HEADER(pVideoInfo); + SetRectEmpty(&pVideoInfo->rcSource); + SetRectEmpty(&pVideoInfo->rcTarget); + + // Set the number of colours explicitly + + if (PALETTISED(pVideoInfo)) { + if (pVideoInfo->bmiHeader.biClrUsed == 0) { + pVideoInfo->bmiHeader.biClrUsed = PALETTE_ENTRIES(pVideoInfo); + } + } + + // The number of important colours shouldn't exceed the number used, on + // some displays the number of important colours is not initialised when + // retrieving the display type so we set the colours used correctly + + if (pVideoInfo->bmiHeader.biClrImportant > pVideoInfo->bmiHeader.biClrUsed) { + pVideoInfo->bmiHeader.biClrImportant = PALETTE_ENTRIES(pVideoInfo); + } + + // Change the image size field to be explicit + + if (pVideoInfo->bmiHeader.biSizeImage == 0) { + pVideoInfo->bmiHeader.biSizeImage = GetBitmapSize(&pVideoInfo->bmiHeader); + } + return NOERROR; +} + + +// Lots of video rendering filters want code to check proposed formats are ok +// This checks the VIDEOINFO we are passed as a media type. If the media type +// is a valid media type then we return NOERROR otherwise E_INVALIDARG. Note +// however we only accept formats that can be easily displayed in the display +// so if we are on a 16 bit device we will not accept 24 bit images. The one +// complexity is that most displays draw 8 bit palettised images efficiently +// Also if the input format is less colour bits per pixel then we also accept + +HRESULT CImageDisplay::CheckVideoType(const VIDEOINFO *pInput) +{ + // First of all check the VIDEOINFOHEADER looks correct + + if (CheckHeaderValidity(pInput) == FALSE) { + return E_INVALIDARG; + } + + // Virtually all devices support palettised images efficiently + + if (m_Display.bmiHeader.biBitCount == pInput->bmiHeader.biBitCount) { + if (PALETTISED(pInput) == TRUE) { + ASSERT(PALETTISED(&m_Display) == TRUE); + NOTE("(Video) Type connection ACCEPTED"); + return NOERROR; + } + } + + + // Is the display depth greater than the input format + + if (m_Display.bmiHeader.biBitCount > pInput->bmiHeader.biBitCount) { + NOTE("(Video) Mismatch agreed"); + return NOERROR; + } + + // Is the display depth less than the input format + + if (m_Display.bmiHeader.biBitCount < pInput->bmiHeader.biBitCount) { + NOTE("(Video) Format mismatch"); + return E_INVALIDARG; + } + + + // Both input and display formats are either BI_RGB or BI_BITFIELDS + + ASSERT(m_Display.bmiHeader.biBitCount == pInput->bmiHeader.biBitCount); + ASSERT(PALETTISED(pInput) == FALSE); + ASSERT(PALETTISED(&m_Display) == FALSE); + + // BI_RGB 16 bit representation is implicitly RGB555, and likewise BI_RGB + // 24 bit representation is RGB888. So we initialise a pointer to the bit + // fields they really mean and check against the display device format + // This is only going to be called when both formats are equal bits pixel + + const DWORD *pInputMask = GetBitMasks(pInput); + const DWORD *pDisplayMask = GetBitMasks((VIDEOINFO *)&m_Display); + + if (pInputMask[iRED] != pDisplayMask[iRED] || + pInputMask[iGREEN] != pDisplayMask[iGREEN] || + pInputMask[iBLUE] != pDisplayMask[iBLUE]) { + + NOTE("(Video) Bit field mismatch"); + return E_INVALIDARG; + } + + NOTE("(Video) Type connection ACCEPTED"); + return NOERROR; +} + + +// Return the bit masks for the true colour VIDEOINFO provided + +const DWORD *CImageDisplay::GetBitMasks(const VIDEOINFO *pVideoInfo) +{ + static const DWORD FailMasks[] = {0,0,0}; + + if (pVideoInfo->bmiHeader.biCompression == BI_BITFIELDS) { + return BITMASKS(pVideoInfo); + } + + ASSERT(pVideoInfo->bmiHeader.biCompression == BI_RGB); + + switch (pVideoInfo->bmiHeader.biBitCount) { + case 16: return bits555; + case 24: return bits888; + case 32: return bits888; + default: return FailMasks; + } +} + + +// Check to see if we can support media type pmtIn as proposed by the output +// pin - We first check that the major media type is video and also identify +// the media sub type. Then we thoroughly check the VIDEOINFO type provided +// As well as the contained VIDEOINFO being correct the major type must be +// video, the subtype a recognised video format and the type GUID correct + +HRESULT CImageDisplay::CheckMediaType(const CMediaType *pmtIn) +{ + // Does this have a VIDEOINFOHEADER format block + + const GUID *pFormatType = pmtIn->FormatType(); + if (*pFormatType != FORMAT_VideoInfo) { + NOTE("Format GUID not a VIDEOINFOHEADER"); + return E_INVALIDARG; + } + ASSERT(pmtIn->Format()); + + // Check the format looks reasonably ok + + ULONG Length = pmtIn->FormatLength(); + if (Length < SIZE_VIDEOHEADER) { + NOTE("Format smaller than a VIDEOHEADER"); + return E_FAIL; + } + + VIDEOINFO *pInput = (VIDEOINFO *) pmtIn->Format(); + + // Check the major type is MEDIATYPE_Video + + const GUID *pMajorType = pmtIn->Type(); + if (*pMajorType != MEDIATYPE_Video) { + NOTE("Major type not MEDIATYPE_Video"); + return E_INVALIDARG; + } + + // Check we can identify the media subtype + + const GUID *pSubType = pmtIn->Subtype(); + if (GetBitCount(pSubType) == USHRT_MAX) { + NOTE("Invalid video media subtype"); + return E_INVALIDARG; + } + return CheckVideoType(pInput); +} + + +// Given a video format described by a VIDEOINFO structure we return the mask +// that is used to obtain the range of acceptable colours for this type, for +// example, the mask for a 24 bit true colour format is 0xFF in all cases. A +// 16 bit 5:6:5 display format uses 0xF8, 0xFC and 0xF8, therefore given any +// RGB triplets we can AND them with these fields to find one that is valid + +BOOL CImageDisplay::GetColourMask(__out DWORD *pMaskRed, + __out DWORD *pMaskGreen, + __out DWORD *pMaskBlue) +{ + CAutoLock cDisplayLock(this); + *pMaskRed = 0xFF; + *pMaskGreen = 0xFF; + *pMaskBlue = 0xFF; + + // If this format is palettised then it doesn't have bit fields + + if (m_Display.bmiHeader.biBitCount < 16) { + return FALSE; + } + + // If this is a 24 bit true colour display then it can handle all the + // possible colour component ranges described by a byte. It is never + // allowed for a 24 bit colour depth image to have BI_BITFIELDS set + + if (m_Display.bmiHeader.biBitCount == 24) { + ASSERT(m_Display.bmiHeader.biCompression == BI_RGB); + return TRUE; + } + + // Calculate the mask based on the format's bit fields + + const DWORD *pBitFields = (DWORD *) GetBitMasks((VIDEOINFO *)&m_Display); + DWORD *pOutputMask[] = { pMaskRed, pMaskGreen, pMaskBlue }; + + // We know from earlier testing that there are no more than iMAXBITS + // bits set in the mask and that they are all contiguous. All that + // therefore remains is to shift them into the correct position + + for (INT iColour = iRED;iColour <= iBLUE;iColour++) { + + // This works out how many bits there are and where they live + + DWORD PrefixBits = CountPrefixBits(pBitFields[iColour]); + DWORD SetBits = CountSetBits(pBitFields[iColour]); + + // The first shift moves the bit field so that it is right justified + // in the DWORD, after which we then shift it back left which then + // puts the leading bit in the bytes most significant bit position + + *(pOutputMask[iColour]) = pBitFields[iColour] >> PrefixBits; + *(pOutputMask[iColour]) <<= (iMAXBITS - SetBits); + } + return TRUE; +} + + +/* Helper to convert to VIDEOINFOHEADER2 +*/ +STDAPI ConvertVideoInfoToVideoInfo2(__inout AM_MEDIA_TYPE *pmt) +{ + if (pmt->formattype != FORMAT_VideoInfo) { + return E_INVALIDARG; + } + if (NULL == pmt->pbFormat || pmt->cbFormat < sizeof(VIDEOINFOHEADER)) { + return E_INVALIDARG; + } + VIDEOINFO *pVideoInfo = (VIDEOINFO *)pmt->pbFormat; + DWORD dwNewSize; + HRESULT hr = DWordAdd(pmt->cbFormat, sizeof(VIDEOINFOHEADER2) - sizeof(VIDEOINFOHEADER), &dwNewSize); + if (FAILED(hr)) { + return hr; + } + PVOID pvNew = CoTaskMemAlloc(dwNewSize); + if (pvNew == NULL) { + return E_OUTOFMEMORY; + } + CopyMemory(pvNew, pmt->pbFormat, FIELD_OFFSET(VIDEOINFOHEADER, bmiHeader)); + ZeroMemory((PBYTE)pvNew + FIELD_OFFSET(VIDEOINFOHEADER, bmiHeader), + sizeof(VIDEOINFOHEADER2) - sizeof(VIDEOINFOHEADER)); + CopyMemory((PBYTE)pvNew + FIELD_OFFSET(VIDEOINFOHEADER2, bmiHeader), + pmt->pbFormat + FIELD_OFFSET(VIDEOINFOHEADER, bmiHeader), + pmt->cbFormat - FIELD_OFFSET(VIDEOINFOHEADER, bmiHeader)); + VIDEOINFOHEADER2 *pVideoInfo2 = (VIDEOINFOHEADER2 *)pvNew; + pVideoInfo2->dwPictAspectRatioX = (DWORD)pVideoInfo2->bmiHeader.biWidth; + pVideoInfo2->dwPictAspectRatioY = (DWORD)abs(pVideoInfo2->bmiHeader.biHeight); + pmt->formattype = FORMAT_VideoInfo2; + CoTaskMemFree(pmt->pbFormat); + pmt->pbFormat = (PBYTE)pvNew; + pmt->cbFormat += sizeof(VIDEOINFOHEADER2) - sizeof(VIDEOINFOHEADER); + return S_OK; +} + + +// Check a media type containing VIDEOINFOHEADER +STDAPI CheckVideoInfoType(const AM_MEDIA_TYPE *pmt) +{ + if (NULL == pmt || NULL == pmt->pbFormat) { + return E_POINTER; + } + if (pmt->majortype != MEDIATYPE_Video || + pmt->formattype != FORMAT_VideoInfo || + pmt->cbFormat < sizeof(VIDEOINFOHEADER)) { + return VFW_E_TYPE_NOT_ACCEPTED; + } + const VIDEOINFOHEADER *pHeader = (const VIDEOINFOHEADER *)pmt->pbFormat; + if (!ValidateBitmapInfoHeader( + &pHeader->bmiHeader, + pmt->cbFormat - FIELD_OFFSET(VIDEOINFOHEADER, bmiHeader))) { + return VFW_E_TYPE_NOT_ACCEPTED; + } + + return S_OK; +} + +// Check a media type containing VIDEOINFOHEADER2 +STDAPI CheckVideoInfo2Type(const AM_MEDIA_TYPE *pmt) +{ + if (NULL == pmt || NULL == pmt->pbFormat) { + return E_POINTER; + } + if (pmt->majortype != MEDIATYPE_Video || + pmt->formattype != FORMAT_VideoInfo2 || + pmt->cbFormat < sizeof(VIDEOINFOHEADER2)) { + return VFW_E_TYPE_NOT_ACCEPTED; + } + const VIDEOINFOHEADER2 *pHeader = (const VIDEOINFOHEADER2 *)pmt->pbFormat; + if (!ValidateBitmapInfoHeader( + &pHeader->bmiHeader, + pmt->cbFormat - FIELD_OFFSET(VIDEOINFOHEADER2, bmiHeader))) { + return VFW_E_TYPE_NOT_ACCEPTED; + } + + return S_OK; +} diff --git a/src/baseclasses/winutil.h b/src/baseclasses/winutil.h new file mode 100644 index 0000000..641b53a --- /dev/null +++ b/src/baseclasses/winutil.h @@ -0,0 +1,419 @@ +//------------------------------------------------------------------------------ +// File: WinUtil.h +// +// Desc: DirectShow base classes - defines generic handler classes. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +// Make sure that you call PrepareWindow to initialise the window after +// the object has been constructed. It is a separate method so that +// derived classes can override useful methods like MessageLoop. Also +// any derived class must call DoneWithWindow in its destructor. If it +// doesn't a message may be retrieved and call a derived class member +// function while a thread is executing the base class destructor code + +#ifndef __WINUTIL__ +#define __WINUTIL__ + +const int DEFWIDTH = 320; // Initial window width +const int DEFHEIGHT = 240; // Initial window height +const int CAPTION = 256; // Maximum length of caption +const int TIMELENGTH = 50; // Maximum length of times +const int PROFILESTR = 128; // Normal profile string +const WORD PALVERSION = 0x300; // GDI palette version +const LONG PALETTE_VERSION = (LONG) 1; // Initial palette version +const COLORREF VIDEO_COLOUR = 0; // Defaults to black background +const HANDLE hMEMORY = (HANDLE) (-1); // Says to open as memory file + +#define WIDTH(x) ((*(x)).right - (*(x)).left) +#define HEIGHT(x) ((*(x)).bottom - (*(x)).top) +#define SHOWSTAGE TEXT("WM_SHOWSTAGE") +#define SHOWSTAGETOP TEXT("WM_SHOWSTAGETOP") +#define REALIZEPALETTE TEXT("WM_REALIZEPALETTE") + +class AM_NOVTABLE CBaseWindow +{ +protected: + + HINSTANCE m_hInstance; // Global module instance handle + HWND m_hwnd; // Handle for our window + HDC m_hdc; // Device context for the window + LONG m_Width; // Client window width + LONG m_Height; // Client window height + BOOL m_bActivated; // Has the window been activated + LPTSTR m_pClassName; // Static string holding class name + DWORD m_ClassStyles; // Passed in to our constructor + DWORD m_WindowStyles; // Likewise the initial window styles + DWORD m_WindowStylesEx; // And the extended window styles + UINT m_ShowStageMessage; // Have the window shown with focus + UINT m_ShowStageTop; // Makes the window WS_EX_TOPMOST + UINT m_RealizePalette; // Makes us realize our new palette + HDC m_MemoryDC; // Used for fast BitBlt operations + HPALETTE m_hPalette; // Handle to any palette we may have + BYTE m_bNoRealize; // Don't realize palette now + BYTE m_bBackground; // Should we realise in background + BYTE m_bRealizing; // already realizing the palette + CCritSec m_WindowLock; // Serialise window object access + BOOL m_bDoGetDC; // Should this window get a DC + bool m_bDoPostToDestroy; // Use PostMessage to destroy + CCritSec m_PaletteLock; // This lock protects m_hPalette. + // It should be held anytime the + // program use the value of m_hPalette. + + // Maps windows message procedure into C++ methods + friend LRESULT CALLBACK WndProc(HWND hwnd, // Window handle + UINT uMsg, // Message ID + WPARAM wParam, // First parameter + LPARAM lParam); // Other parameter + + virtual LRESULT OnPaletteChange(HWND hwnd, UINT Message); + +public: + + CBaseWindow(BOOL bDoGetDC = TRUE, bool bPostToDestroy = false); + +#ifdef DEBUG + virtual ~CBaseWindow(); +#endif + + virtual HRESULT DoneWithWindow(); + virtual HRESULT PrepareWindow(); + virtual HRESULT InactivateWindow(); + virtual HRESULT ActivateWindow(); + virtual BOOL OnSize(LONG Width, LONG Height); + virtual BOOL OnClose(); + virtual RECT GetDefaultRect(); + virtual HRESULT UninitialiseWindow(); + virtual HRESULT InitialiseWindow(HWND hwnd); + + HRESULT CompleteConnect(); + HRESULT DoCreateWindow(); + + HRESULT PerformanceAlignWindow(); + HRESULT DoShowWindow(LONG ShowCmd); + void PaintWindow(BOOL bErase); + void DoSetWindowForeground(BOOL bFocus); + virtual HRESULT SetPalette(HPALETTE hPalette); + void SetRealize(BOOL bRealize) + { + m_bNoRealize = !bRealize; + } + + // Jump over to the window thread to set the current palette + HRESULT SetPalette(); + void UnsetPalette(void); + virtual HRESULT DoRealisePalette(BOOL bForceBackground = FALSE); + + void LockPaletteLock(); + void UnlockPaletteLock(); + + virtual BOOL PossiblyEatMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) + { return FALSE; }; + + // Access our window information + + bool WindowExists(); + LONG GetWindowWidth(); + LONG GetWindowHeight(); + HWND GetWindowHWND(); + HDC GetMemoryHDC(); + HDC GetWindowHDC(); + + #ifdef DEBUG + HPALETTE GetPalette(); + #endif // DEBUG + + // This is the window procedure the derived object should override + + virtual LRESULT OnReceiveMessage(HWND hwnd, // Window handle + UINT uMsg, // Message ID + WPARAM wParam, // First parameter + LPARAM lParam); // Other parameter + + // Must be overriden to return class and window styles + + virtual LPTSTR GetClassWindowStyles( + __out DWORD *pClassStyles, // Class styles + __out DWORD *pWindowStyles, // Window styles + __out DWORD *pWindowStylesEx) PURE; // Extended styles +}; + + +// This helper class is entirely subservient to the owning CBaseWindow object +// All this object does is to split out the actual drawing operation from the +// main object (because it was becoming too large). We have a number of entry +// points to set things like the draw device contexts, to implement the actual +// drawing and to set the destination rectangle in the client window. We have +// no critical section locking in this class because we are used exclusively +// by the owning window object which looks after serialising calls into us + +// If you want to use this class make sure you call NotifyAllocator once the +// allocate has been agreed, also call NotifyMediaType with a pointer to a +// NON stack based CMediaType once that has been set (we keep a pointer to +// the original rather than taking a copy). When the palette changes call +// IncrementPaletteVersion (easiest thing to do is to also call this method +// in the SetMediaType method most filters implement). Finally before you +// start rendering anything call SetDrawContext so that we can get the HDCs +// for drawing from the CBaseWindow object we are given during construction + +class CDrawImage +{ +protected: + + CBaseWindow *m_pBaseWindow; // Owning video window object + CRefTime m_StartSample; // Start time for the current sample + CRefTime m_EndSample; // And likewise it's end sample time + HDC m_hdc; // Main window device context + HDC m_MemoryDC; // Offscreen draw device context + RECT m_TargetRect; // Target destination rectangle + RECT m_SourceRect; // Source image rectangle + BOOL m_bStretch; // Do we have to stretch the images + BOOL m_bUsingImageAllocator; // Are the samples shared DIBSECTIONs + CMediaType *m_pMediaType; // Pointer to the current format + int m_perfidRenderTime; // Time taken to render an image + LONG m_PaletteVersion; // Current palette version cookie + + // Draw the video images in the window + + void SlowRender(IMediaSample *pMediaSample); + void FastRender(IMediaSample *pMediaSample); + void DisplaySampleTimes(IMediaSample *pSample); + void UpdateColourTable(HDC hdc,__in BITMAPINFOHEADER *pbmi); + void SetStretchMode(); + +public: + + // Used to control the image drawing + + CDrawImage(__inout CBaseWindow *pBaseWindow); + BOOL DrawImage(IMediaSample *pMediaSample); + BOOL DrawVideoImageHere(HDC hdc, IMediaSample *pMediaSample, + __in LPRECT lprcSrc, __in LPRECT lprcDst); + void SetDrawContext(); + void SetTargetRect(__in RECT *pTargetRect); + void SetSourceRect(__in RECT *pSourceRect); + void GetTargetRect(__out RECT *pTargetRect); + void GetSourceRect(__out RECT *pSourceRect); + virtual RECT ScaleSourceRect(const RECT *pSource); + + // Handle updating palettes as they change + + LONG GetPaletteVersion(); + void ResetPaletteVersion(); + void IncrementPaletteVersion(); + + // Tell us media types and allocator assignments + + void NotifyAllocator(BOOL bUsingImageAllocator); + void NotifyMediaType(__in CMediaType *pMediaType); + BOOL UsingImageAllocator(); + + // Called when we are about to draw an image + + void NotifyStartDraw() { + MSR_START(m_perfidRenderTime); + }; + + // Called when we complete an image rendering + + void NotifyEndDraw() { + MSR_STOP(m_perfidRenderTime); + }; +}; + + +// This is the structure used to keep information about each GDI DIB. All the +// samples we create from our allocator will have a DIBSECTION allocated to +// them. When we receive the sample we know we can BitBlt straight to an HDC + +typedef struct tagDIBDATA { + + LONG PaletteVersion; // Current palette version in use + DIBSECTION DibSection; // Details of DIB section allocated + HBITMAP hBitmap; // Handle to bitmap for drawing + HANDLE hMapping; // Handle to shared memory block + BYTE *pBase; // Pointer to base memory address + +} DIBDATA; + + +// This class inherits from CMediaSample and uses all of it's methods but it +// overrides the constructor to initialise itself with the DIBDATA structure +// When we come to render an IMediaSample we will know if we are using our own +// allocator, and if we are, we can cast the IMediaSample to a pointer to one +// of these are retrieve the DIB section information and hence the HBITMAP + +class CImageSample : public CMediaSample +{ +protected: + + DIBDATA m_DibData; // Information about the DIBSECTION + BOOL m_bInit; // Is the DIB information setup + +public: + + // Constructor + + CImageSample(__inout CBaseAllocator *pAllocator, + __in_opt LPCTSTR pName, + __inout HRESULT *phr, + __in_bcount(length) LPBYTE pBuffer, + LONG length); + + // Maintain the DIB/DirectDraw state + + void SetDIBData(__in DIBDATA *pDibData); + __out DIBDATA *GetDIBData(); +}; + + +// This is an allocator based on the abstract CBaseAllocator base class that +// allocates sample buffers in shared memory. The number and size of these +// are determined when the output pin calls Prepare on us. The shared memory +// blocks are used in subsequent calls to GDI CreateDIBSection, once that +// has been done the output pin can fill the buffers with data which will +// then be handed to GDI through BitBlt calls and thereby remove one copy + +class CImageAllocator : public CBaseAllocator +{ +protected: + + CBaseFilter *m_pFilter; // Delegate reference counts to + CMediaType *m_pMediaType; // Pointer to the current format + + // Used to create and delete samples + + HRESULT Alloc(); + void Free(); + + // Manage the shared DIBSECTION and DCI/DirectDraw buffers + + HRESULT CreateDIB(LONG InSize,DIBDATA &DibData); + STDMETHODIMP CheckSizes(__in ALLOCATOR_PROPERTIES *pRequest); + virtual CImageSample *CreateImageSample(__in_bcount(Length) LPBYTE pData,LONG Length); + +public: + + // Constructor and destructor + + CImageAllocator(__inout CBaseFilter *pFilter,__in_opt LPCTSTR pName,__inout HRESULT *phr); +#ifdef DEBUG + ~CImageAllocator(); +#endif + + STDMETHODIMP_(ULONG) NonDelegatingAddRef(); + STDMETHODIMP_(ULONG) NonDelegatingRelease(); + void NotifyMediaType(__in CMediaType *pMediaType); + + // Agree the number of buffers to be used and their size + + STDMETHODIMP SetProperties( + __in ALLOCATOR_PROPERTIES *pRequest, + __out ALLOCATOR_PROPERTIES *pActual); +}; + + +// This class is a fairly specialised helper class for image renderers that +// have to create and manage palettes. The CBaseWindow class looks after +// realising palettes once they have been installed. This class can be used +// to create the palette handles from a media format (which must contain a +// VIDEOINFO structure in the format block). We try to make the palette an +// identity palette to maximise performance and also only change palettes +// if actually required to (we compare palette colours before updating). +// All the methods are virtual so that they can be overriden if so required + +class CImagePalette +{ +protected: + + CBaseWindow *m_pBaseWindow; // Window to realise palette in + CBaseFilter *m_pFilter; // Media filter to send events + CDrawImage *m_pDrawImage; // Object who will be drawing + HPALETTE m_hPalette; // The palette handle we own + +public: + + CImagePalette(__inout CBaseFilter *pBaseFilter, + __inout CBaseWindow *pBaseWindow, + __inout CDrawImage *pDrawImage); + +#ifdef DEBUG + virtual ~CImagePalette(); +#endif + + static HPALETTE MakePalette(const VIDEOINFOHEADER *pVideoInfo, __in LPSTR szDevice); + HRESULT RemovePalette(); + static HRESULT MakeIdentityPalette(__inout_ecount_full(iColours) PALETTEENTRY *pEntry,INT iColours, __in LPSTR szDevice); + HRESULT CopyPalette(const CMediaType *pSrc,__out CMediaType *pDest); + BOOL ShouldUpdate(const VIDEOINFOHEADER *pNewInfo,const VIDEOINFOHEADER *pOldInfo); + HRESULT PreparePalette(const CMediaType *pmtNew,const CMediaType *pmtOld,__in LPSTR szDevice); + + BOOL DrawVideoImageHere(HDC hdc, IMediaSample *pMediaSample, __in LPRECT lprcSrc, __in LPRECT lprcDst) + { + return m_pDrawImage->DrawVideoImageHere(hdc, pMediaSample, lprcSrc,lprcDst); + } +}; + + +// Another helper class really for video based renderers. Most such renderers +// need to know what the display format is to some degree or another. This +// class initialises itself with the display format. The format can be asked +// for through GetDisplayFormat and various other accessor functions. If a +// filter detects a display format change (perhaps it gets a WM_DEVMODECHANGE +// message then it can call RefreshDisplayType to reset that format). Also +// many video renderers will want to check formats as they are proposed by +// source filters. This class provides methods to check formats and only +// accept those video formats that can be efficiently drawn using GDI calls + +class CImageDisplay : public CCritSec +{ +protected: + + // This holds the display format; biSize should not be too big, so we can + // safely use the VIDEOINFO structure + VIDEOINFO m_Display; + + static DWORD CountSetBits(const DWORD Field); + static DWORD CountPrefixBits(const DWORD Field); + static BOOL CheckBitFields(const VIDEOINFO *pInput); + +public: + + // Constructor and destructor + + CImageDisplay(); + + // Used to manage BITMAPINFOHEADERs and the display format + + const VIDEOINFO *GetDisplayFormat(); + HRESULT RefreshDisplayType(__in_opt LPSTR szDeviceName); + static BOOL CheckHeaderValidity(const VIDEOINFO *pInput); + static BOOL CheckPaletteHeader(const VIDEOINFO *pInput); + BOOL IsPalettised(); + WORD GetDisplayDepth(); + + // Provide simple video format type checking + + HRESULT CheckMediaType(const CMediaType *pmtIn); + HRESULT CheckVideoType(const VIDEOINFO *pInput); + HRESULT UpdateFormat(__inout VIDEOINFO *pVideoInfo); + const DWORD *GetBitMasks(const VIDEOINFO *pVideoInfo); + + BOOL GetColourMask(__out DWORD *pMaskRed, + __out DWORD *pMaskGreen, + __out DWORD *pMaskBlue); +}; + +// Convert a FORMAT_VideoInfo to FORMAT_VideoInfo2 +STDAPI ConvertVideoInfoToVideoInfo2(__inout AM_MEDIA_TYPE *pmt); + +// Check a media type containing VIDEOINFOHEADER +STDAPI CheckVideoInfoType(const AM_MEDIA_TYPE *pmt); + +// Check a media type containing VIDEOINFOHEADER +STDAPI CheckVideoInfo2Type(const AM_MEDIA_TYPE *pmt); + +#endif // __WINUTIL__ + diff --git a/src/baseclasses/wxdebug.cpp b/src/baseclasses/wxdebug.cpp new file mode 100644 index 0000000..0e70313 --- /dev/null +++ b/src/baseclasses/wxdebug.cpp @@ -0,0 +1,1474 @@ +//------------------------------------------------------------------------------ +// File: WXDebug.cpp +// +// Desc: DirectShow base classes - implements ActiveX system debugging +// facilities. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#define _WINDLL + +#include +#include +#include +#include + +#ifdef DEBUG +#ifdef UNICODE +#ifndef _UNICODE +#define _UNICODE +#endif // _UNICODE +#endif // UNICODE +#endif // DEBUG + +#include +#include + +#ifdef DEBUG +static void DisplayBITMAPINFO(const BITMAPINFOHEADER* pbmi); +static void DisplayRECT(LPCTSTR szLabel, const RECT& rc); + +// The Win32 wsprintf() function writes a maximum of 1024 characters to it's output buffer. +// See the documentation for wsprintf()'s lpOut parameter for more information. +const INT iDEBUGINFO = 1024; // Used to format strings + +/* For every module and executable we store a debugging level for each of + the five categories (eg LOG_ERROR and LOG_TIMING). This makes it easy + to isolate and debug individual modules without seeing everybody elses + spurious debug output. The keys are stored in the registry under the + HKEY_LOCAL_MACHINE\SOFTWARE\Debug\\ key values + NOTE these must be in the same order as their enumeration definition */ + +const LPCTSTR pKeyNames[] = { + TEXT("TIMING"), // Timing and performance measurements + TEXT("TRACE"), // General step point call tracing + TEXT("MEMORY"), // Memory and object allocation/destruction + TEXT("LOCKING"), // Locking/unlocking of critical sections + TEXT("ERROR"), // Debug error notification + TEXT("CUSTOM1"), + TEXT("CUSTOM2"), + TEXT("CUSTOM3"), + TEXT("CUSTOM4"), + TEXT("CUSTOM5") + }; + +const TCHAR CAutoTrace::_szEntering[] = TEXT("->: %s"); +const TCHAR CAutoTrace::_szLeaving[] = TEXT("<-: %s"); + +const INT iMAXLEVELS = NUMELMS(pKeyNames); // Maximum debug categories + +HINSTANCE m_hInst; // Module instance handle +TCHAR m_ModuleName[iDEBUGINFO]; // Cut down module name +DWORD m_Levels[iMAXLEVELS]; // Debug level per category +CRITICAL_SECTION m_CSDebug; // Controls access to list +DWORD m_dwNextCookie; // Next active object ID +ObjectDesc *pListHead = NULL; // First active object +DWORD m_dwObjectCount; // Active object count +BOOL m_bInit = FALSE; // Have we been initialised +HANDLE m_hOutput = INVALID_HANDLE_VALUE; // Optional output written here +DWORD dwWaitTimeout = INFINITE; // Default timeout value +DWORD dwTimeOffset; // Time of first DbgLog call +bool g_fUseKASSERT = false; // don't create messagebox +bool g_fDbgInDllEntryPoint = false; +bool g_fAutoRefreshLevels = false; + +LPCTSTR pBaseKey = TEXT("SOFTWARE\\Microsoft\\DirectShow\\Debug"); +LPCTSTR pGlobalKey = TEXT("GLOBAL"); +static CHAR *pUnknownName = "UNKNOWN"; + +LPCTSTR TimeoutName = TEXT("TIMEOUT"); + +/* This sets the instance handle that the debug library uses to find + the module's file name from the Win32 GetModuleFileName function */ + +void WINAPI DbgInitialise(HINSTANCE hInst) +{ + InitializeCriticalSection(&m_CSDebug); + m_bInit = TRUE; + + m_hInst = hInst; + DbgInitModuleName(); + if (GetProfileInt(m_ModuleName, TEXT("BreakOnLoad"), 0)) + DebugBreak(); + DbgInitModuleSettings(false); + DbgInitGlobalSettings(true); + dwTimeOffset = timeGetTime(); +} + + +/* This is called to clear up any resources the debug library uses - at the + moment we delete our critical section and the object list. The values we + retrieve from the registry are all done during initialisation but we don't + go looking for update notifications while we are running, if the values + are changed then the application has to be restarted to pick them up */ + +void WINAPI DbgTerminate() +{ + if (m_hOutput != INVALID_HANDLE_VALUE) { + EXECUTE_ASSERT(CloseHandle(m_hOutput)); + m_hOutput = INVALID_HANDLE_VALUE; + } + DeleteCriticalSection(&m_CSDebug); + m_bInit = FALSE; +} + + +/* This is called by DbgInitLogLevels to read the debug settings + for each logging category for this module from the registry */ + +void WINAPI DbgInitKeyLevels(HKEY hKey, bool fTakeMax) +{ + LONG lReturn; // Create key return value + LONG lKeyPos; // Current key category + DWORD dwKeySize; // Size of the key value + DWORD dwKeyType; // Receives it's type + DWORD dwKeyValue; // This fields value + + /* Try and read a value for each key position in turn */ + for (lKeyPos = 0;lKeyPos < iMAXLEVELS;lKeyPos++) { + + dwKeySize = sizeof(DWORD); + lReturn = RegQueryValueEx( + hKey, // Handle to an open key + pKeyNames[lKeyPos], // Subkey name derivation + NULL, // Reserved field + &dwKeyType, // Returns the field type + (LPBYTE) &dwKeyValue, // Returns the field's value + &dwKeySize ); // Number of bytes transferred + + /* If either the key was not available or it was not a DWORD value + then we ensure only the high priority debug logging is output + but we try and update the field to a zero filled DWORD value */ + + if (lReturn != ERROR_SUCCESS || dwKeyType != REG_DWORD) { + + dwKeyValue = 0; + lReturn = RegSetValueEx( + hKey, // Handle of an open key + pKeyNames[lKeyPos], // Address of subkey name + (DWORD) 0, // Reserved field + REG_DWORD, // Type of the key field + (PBYTE) &dwKeyValue, // Value for the field + sizeof(DWORD)); // Size of the field buffer + + if (lReturn != ERROR_SUCCESS) { + DbgLog((LOG_ERROR,1,TEXT("Could not create subkey %s"),pKeyNames[lKeyPos])); + dwKeyValue = 0; + } + } + if(fTakeMax) + { + m_Levels[lKeyPos] = max(dwKeyValue,m_Levels[lKeyPos]); + } + else + { + if((m_Levels[lKeyPos] & LOG_FORCIBLY_SET) == 0) { + m_Levels[lKeyPos] = dwKeyValue; + } + } + } + + /* Read the timeout value for catching hangs */ + dwKeySize = sizeof(DWORD); + lReturn = RegQueryValueEx( + hKey, // Handle to an open key + TimeoutName, // Subkey name derivation + NULL, // Reserved field + &dwKeyType, // Returns the field type + (LPBYTE) &dwWaitTimeout, // Returns the field's value + &dwKeySize ); // Number of bytes transferred + + /* If either the key was not available or it was not a DWORD value + then we ensure only the high priority debug logging is output + but we try and update the field to a zero filled DWORD value */ + + if (lReturn != ERROR_SUCCESS || dwKeyType != REG_DWORD) { + + dwWaitTimeout = INFINITE; + lReturn = RegSetValueEx( + hKey, // Handle of an open key + TimeoutName, // Address of subkey name + (DWORD) 0, // Reserved field + REG_DWORD, // Type of the key field + (PBYTE) &dwWaitTimeout, // Value for the field + sizeof(DWORD)); // Size of the field buffer + + if (lReturn != ERROR_SUCCESS) { + DbgLog((LOG_ERROR,1,TEXT("Could not create subkey %s"),pKeyNames[lKeyPos])); + dwWaitTimeout = INFINITE; + } + } +} + +void WINAPI DbgOutString(LPCTSTR psz) +{ + if (m_hOutput != INVALID_HANDLE_VALUE) { + UINT cb = lstrlen(psz); + DWORD dw; +#ifdef UNICODE + CHAR szDest[2048]; + WideCharToMultiByte(CP_ACP, 0, psz, -1, szDest, NUMELMS(szDest), 0, 0); + WriteFile (m_hOutput, szDest, cb, &dw, NULL); +#else + WriteFile (m_hOutput, psz, cb, &dw, NULL); +#endif + } else { + OutputDebugString (psz); + } +} + + + + +HRESULT DbgUniqueProcessName(LPCTSTR inName, LPTSTR outName) +{ + HRESULT hr = S_OK; + const TCHAR *pIn = inName; + int dotPos = -1; + + //scan the input and record the last '.' position + while (*pIn && (pIn - inName) < MAX_PATH) + { + if ( TEXT('.') == *pIn ) + dotPos = (int)(pIn-inName); + ++pIn; + } + + if (*pIn) //input should be zero-terminated within MAX_PATH + return E_INVALIDARG; + + DWORD dwProcessId = GetCurrentProcessId(); + + if (dotPos < 0) + { + //no extension in the input, appending process id to the input + hr = StringCchPrintf(outName, MAX_PATH, TEXT("%s_%d"), inName, dwProcessId); + } + else + { + TCHAR pathAndBasename[MAX_PATH] = {0}; + + //there's an extension - zero-terminate the path and basename first by copying + hr = StringCchCopyN(pathAndBasename, MAX_PATH, inName, (size_t)dotPos); + + //re-combine path, basename and extension with processId appended to a basename + if (SUCCEEDED(hr)) + hr = StringCchPrintf(outName, MAX_PATH, TEXT("%s_%d%s"), pathAndBasename, dwProcessId, inName + dotPos); + } + + return hr; +} + + +/* Called by DbgInitGlobalSettings to setup alternate logging destinations + */ + +void WINAPI DbgInitLogTo ( + HKEY hKey) +{ + LONG lReturn; + DWORD dwKeyType; + DWORD dwKeySize; + TCHAR szFile[MAX_PATH] = {0}; + static const TCHAR cszKey[] = TEXT("LogToFile"); + + dwKeySize = MAX_PATH; + lReturn = RegQueryValueEx( + hKey, // Handle to an open key + cszKey, // Subkey name derivation + NULL, // Reserved field + &dwKeyType, // Returns the field type + (LPBYTE) szFile, // Returns the field's value + &dwKeySize); // Number of bytes transferred + + // create an empty key if it does not already exist + // + if (lReturn != ERROR_SUCCESS || dwKeyType != REG_SZ) + { + dwKeySize = sizeof(TCHAR); + lReturn = RegSetValueEx( + hKey, // Handle of an open key + cszKey, // Address of subkey name + (DWORD) 0, // Reserved field + REG_SZ, // Type of the key field + (PBYTE)szFile, // Value for the field + dwKeySize); // Size of the field buffer + } + + // if an output-to was specified. try to open it. + // + if (m_hOutput != INVALID_HANDLE_VALUE) { + EXECUTE_ASSERT(CloseHandle (m_hOutput)); + m_hOutput = INVALID_HANDLE_VALUE; + } + if (szFile[0] != 0) + { + if (!lstrcmpi(szFile, TEXT("Console"))) { + m_hOutput = GetStdHandle (STD_OUTPUT_HANDLE); + if (m_hOutput == INVALID_HANDLE_VALUE) { + AllocConsole (); + m_hOutput = GetStdHandle (STD_OUTPUT_HANDLE); + } + SetConsoleTitle (TEXT("ActiveX Debug Output")); + } else if (szFile[0] && + lstrcmpi(szFile, TEXT("Debug")) && + lstrcmpi(szFile, TEXT("Debugger")) && + lstrcmpi(szFile, TEXT("Deb"))) + { + m_hOutput = CreateFile(szFile, GENERIC_WRITE, + FILE_SHARE_READ, + NULL, OPEN_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + NULL); + + if (INVALID_HANDLE_VALUE == m_hOutput && + GetLastError() == ERROR_SHARING_VIOLATION) + { + TCHAR uniqueName[MAX_PATH] = {0}; + if (SUCCEEDED(DbgUniqueProcessName(szFile, uniqueName))) + { + m_hOutput = CreateFile(uniqueName, GENERIC_WRITE, + FILE_SHARE_READ, + NULL, OPEN_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + NULL); + } + } + + if (INVALID_HANDLE_VALUE != m_hOutput) + { + static const TCHAR cszBar[] = TEXT("\r\n\r\n=====DbgInitialize()=====\r\n\r\n"); + SetFilePointer (m_hOutput, 0, NULL, FILE_END); + DbgOutString (cszBar); + } + } + } +} + + + +/* This is called by DbgInitLogLevels to read the global debug settings for + each logging category for this module from the registry. Normally each + module has it's own values set for it's different debug categories but + setting the global SOFTWARE\Debug\Global applies them to ALL modules */ + +void WINAPI DbgInitGlobalSettings(bool fTakeMax) +{ + LONG lReturn; // Create key return value + TCHAR szInfo[iDEBUGINFO]; // Constructs key names + HKEY hGlobalKey; // Global override key + + /* Construct the global base key name */ + (void)StringCchPrintf(szInfo,NUMELMS(szInfo),TEXT("%s\\%s"),pBaseKey,pGlobalKey); + + /* Create or open the key for this module */ + lReturn = RegCreateKeyEx(HKEY_LOCAL_MACHINE, // Handle of an open key + szInfo, // Address of subkey name + (DWORD) 0, // Reserved value + NULL, // Address of class name + (DWORD) 0, // Special options flags + GENERIC_READ | GENERIC_WRITE, // Desired security access + NULL, // Key security descriptor + &hGlobalKey, // Opened handle buffer + NULL); // What really happened + + if (lReturn != ERROR_SUCCESS) { + lReturn = RegCreateKeyEx(HKEY_LOCAL_MACHINE, // Handle of an open key + szInfo, // Address of subkey name + (DWORD) 0, // Reserved value + NULL, // Address of class name + (DWORD) 0, // Special options flags + GENERIC_READ, // Desired security access + NULL, // Key security descriptor + &hGlobalKey, // Opened handle buffer + NULL); // What really happened + if (lReturn != ERROR_SUCCESS) { + DbgLog((LOG_ERROR,1,TEXT("Could not access GLOBAL module key"))); + } + return; + } + + DbgInitKeyLevels(hGlobalKey, fTakeMax); + RegCloseKey(hGlobalKey); +} + + +/* This sets the debugging log levels for the different categories. We start + by opening (or creating if not already available) the SOFTWARE\Debug key + that all these settings live under. We then look at the global values + set under SOFTWARE\Debug\Global which apply on top of the individual + module settings. We then load the individual module registry settings */ + +void WINAPI DbgInitModuleSettings(bool fTakeMax) +{ + LONG lReturn; // Create key return value + TCHAR szInfo[iDEBUGINFO]; // Constructs key names + HKEY hModuleKey; // Module key handle + + /* Construct the base key name */ + (void)StringCchPrintf(szInfo,NUMELMS(szInfo),TEXT("%s\\%s"),pBaseKey,m_ModuleName); + + /* Create or open the key for this module */ + lReturn = RegCreateKeyEx(HKEY_LOCAL_MACHINE, // Handle of an open key + szInfo, // Address of subkey name + (DWORD) 0, // Reserved value + NULL, // Address of class name + (DWORD) 0, // Special options flags + GENERIC_READ | GENERIC_WRITE, // Desired security access + NULL, // Key security descriptor + &hModuleKey, // Opened handle buffer + NULL); // What really happened + + if (lReturn != ERROR_SUCCESS) { + lReturn = RegCreateKeyEx(HKEY_LOCAL_MACHINE, // Handle of an open key + szInfo, // Address of subkey name + (DWORD) 0, // Reserved value + NULL, // Address of class name + (DWORD) 0, // Special options flags + GENERIC_READ, // Desired security access + NULL, // Key security descriptor + &hModuleKey, // Opened handle buffer + NULL); // What really happened + if (lReturn != ERROR_SUCCESS) { + DbgLog((LOG_ERROR,1,TEXT("Could not access module key"))); + } + return; + } + + DbgInitLogTo(hModuleKey); + DbgInitKeyLevels(hModuleKey, fTakeMax); + RegCloseKey(hModuleKey); +} + + +/* Initialise the module file name */ + +void WINAPI DbgInitModuleName() +{ + TCHAR FullName[iDEBUGINFO]; // Load the full path and module name + LPTSTR pName; // Searches from the end for a backslash + + GetModuleFileName(m_hInst,FullName,iDEBUGINFO); + pName = _tcsrchr(FullName,'\\'); + if (pName == NULL) { + pName = FullName; + } else { + pName++; + } + (void)StringCchCopy(m_ModuleName,NUMELMS(m_ModuleName), pName); +} + +struct MsgBoxMsg +{ + HWND hwnd; + LPCTSTR szTitle; + LPCTSTR szMessage; + DWORD dwFlags; + INT iResult; +}; + +// +// create a thread to call MessageBox(). calling MessageBox() on +// random threads at bad times can confuse the host (eg IE). +// +DWORD WINAPI MsgBoxThread( + __inout LPVOID lpParameter // thread data + ) +{ + MsgBoxMsg *pmsg = (MsgBoxMsg *)lpParameter; + pmsg->iResult = MessageBox( + pmsg->hwnd, + pmsg->szTitle, + pmsg->szMessage, + pmsg->dwFlags); + + return 0; +} + +INT MessageBoxOtherThread( + HWND hwnd, + LPCTSTR szTitle, + LPCTSTR szMessage, + DWORD dwFlags) +{ + if(g_fDbgInDllEntryPoint) + { + // can't wait on another thread because we have the loader + // lock held in the dll entry point. + // This can crash sometimes so just skip it + // return MessageBox(hwnd, szTitle, szMessage, dwFlags); + return IDCANCEL; + } + else + { + MsgBoxMsg msg = {hwnd, szTitle, szMessage, dwFlags, 0}; + DWORD dwid; + HANDLE hThread = CreateThread( + 0, // security + 0, // stack size + MsgBoxThread, + (void *)&msg, // arg + 0, // flags + &dwid); + if(hThread) + { + WaitForSingleObject(hThread, INFINITE); + CloseHandle(hThread); + return msg.iResult; + } + + // break into debugger on failure. + return IDCANCEL; + } +} + +/* Displays a message box if the condition evaluated to FALSE */ + +void WINAPI DbgAssert(LPCTSTR pCondition,LPCTSTR pFileName,INT iLine) +{ + if(g_fUseKASSERT) + { + DbgKernelAssert(pCondition, pFileName, iLine); + } + else + { + + TCHAR szInfo[iDEBUGINFO]; + + (void)StringCchPrintf(szInfo, NUMELMS(szInfo),TEXT("%s \nAt line %d of %s\nContinue? (Cancel to debug)"), + pCondition, iLine, pFileName); + + INT MsgId = MessageBoxOtherThread(NULL,szInfo,TEXT("ASSERT Failed"), + MB_SYSTEMMODAL | + MB_ICONHAND | + MB_YESNOCANCEL | + MB_SETFOREGROUND); + switch (MsgId) + { + case IDNO: /* Kill the application */ + + FatalAppExit(FALSE, TEXT("Application terminated")); + break; + + case IDCANCEL: /* Break into the debugger */ + + DebugBreak(); + break; + + case IDYES: /* Ignore assertion continue execution */ + break; + } + } +} + +/* Displays a message box at a break point */ + +void WINAPI DbgBreakPoint(LPCTSTR pCondition,LPCTSTR pFileName,INT iLine) +{ + if(g_fUseKASSERT) + { + DbgKernelAssert(pCondition, pFileName, iLine); + } + else + { + TCHAR szInfo[iDEBUGINFO]; + + (void)StringCchPrintf(szInfo, NUMELMS(szInfo),TEXT("%s \nAt line %d of %s\nContinue? (Cancel to debug)"), + pCondition, iLine, pFileName); + + INT MsgId = MessageBoxOtherThread(NULL,szInfo,TEXT("Hard coded break point"), + MB_SYSTEMMODAL | + MB_ICONHAND | + MB_YESNOCANCEL | + MB_SETFOREGROUND); + switch (MsgId) + { + case IDNO: /* Kill the application */ + + FatalAppExit(FALSE, TEXT("Application terminated")); + break; + + case IDCANCEL: /* Break into the debugger */ + + DebugBreak(); + break; + + case IDYES: /* Ignore break point continue execution */ + break; + } + } +} + +void WINAPI DbgBreakPoint(LPCTSTR pFileName,INT iLine,__format_string LPCTSTR szFormatString,...) +{ + // A debug break point message can have at most 2000 characters if + // ANSI or UNICODE characters are being used. A debug break point message + // can have between 1000 and 2000 double byte characters in it. If a + // particular message needs more characters, then the value of this constant + // should be increased. + const DWORD MAX_BREAK_POINT_MESSAGE_SIZE = 2000; + + TCHAR szBreakPointMessage[MAX_BREAK_POINT_MESSAGE_SIZE]; + + va_list va; + va_start( va, szFormatString ); + + HRESULT hr = StringCchVPrintf( szBreakPointMessage, NUMELMS(szBreakPointMessage), szFormatString, va ); + + va_end(va); + + if( FAILED(hr) ) { + DbgBreak( "ERROR in DbgBreakPoint(). The variable length debug message could not be displayed because StringCchVPrintf() failed." ); + return; + } + + ::DbgBreakPoint( szBreakPointMessage, pFileName, iLine ); +} + + +/* When we initialised the library we stored in the m_Levels array the current + debug output level for this module for each of the five categories. When + some debug logging is sent to us it can be sent with a combination of the + categories (if it is applicable to many for example) in which case we map + the type's categories into their current debug levels and see if any of + them can be accepted. The function looks at each bit position in turn from + the input type field and then compares it's debug level with the modules. + + A level of 0 means that output is always sent to the debugger. This is + due to producing output if the input level is <= m_Levels. +*/ + + +BOOL WINAPI DbgCheckModuleLevel(DWORD Type,DWORD Level) +{ + if(g_fAutoRefreshLevels) + { + // re-read the registry every second. We cannot use RegNotify() to + // notice registry changes because it's not available on win9x. + static DWORD g_dwLastRefresh = 0; + DWORD dwTime = timeGetTime(); + if(dwTime - g_dwLastRefresh > 1000) { + g_dwLastRefresh = dwTime; + + // there's a race condition: multiple threads could update the + // values. plus read and write not synchronized. no harm + // though. + DbgInitModuleSettings(false); + } + } + + + DWORD Mask = 0x01; + + // If no valid bits are set return FALSE + if ((Type & ((1<m_szName = szObjectName; + pObject->m_wszName = wszObjectName; + pObject->m_dwCookie = ++m_dwNextCookie; + pObject->m_pNext = pListHead; + + pListHead = pObject; + m_dwObjectCount++; + + DWORD ObjectCookie = pObject->m_dwCookie; + ASSERT(ObjectCookie); + + if(wszObjectName) { + DbgLog((LOG_MEMORY,2,TEXT("Object created %d (%ls) %d Active"), + pObject->m_dwCookie, wszObjectName, m_dwObjectCount)); + } else { + DbgLog((LOG_MEMORY,2,TEXT("Object created %d (%hs) %d Active"), + pObject->m_dwCookie, szObjectName, m_dwObjectCount)); + } + + LeaveCriticalSection(&m_CSDebug); + return ObjectCookie; +} + + +/* This is called by the CBaseObject destructor when an object is about to be + destroyed, we are passed the cookie we returned during construction that + identifies this object. We scan the object list for a matching cookie and + remove the object if successful. We also update the active object count */ + +BOOL WINAPI DbgRegisterObjectDestruction(DWORD dwCookie) +{ + /* Grab the list critical section */ + EnterCriticalSection(&m_CSDebug); + + ObjectDesc *pObject = pListHead; + ObjectDesc *pPrevious = NULL; + + /* Scan the object list looking for a cookie match */ + + while (pObject) { + if (pObject->m_dwCookie == dwCookie) { + break; + } + pPrevious = pObject; + pObject = pObject->m_pNext; + } + + if (pObject == NULL) { + DbgBreak("Apparently destroying a bogus object"); + LeaveCriticalSection(&m_CSDebug); + return FALSE; + } + + /* Is the object at the head of the list */ + + if (pPrevious == NULL) { + pListHead = pObject->m_pNext; + } else { + pPrevious->m_pNext = pObject->m_pNext; + } + + /* Delete the object and update the housekeeping information */ + + m_dwObjectCount--; + + if(pObject->m_wszName) { + DbgLog((LOG_MEMORY,2,TEXT("Object destroyed %d (%ls) %d Active"), + pObject->m_dwCookie, pObject->m_wszName, m_dwObjectCount)); + } else { + DbgLog((LOG_MEMORY,2,TEXT("Object destroyed %d (%hs) %d Active"), + pObject->m_dwCookie, pObject->m_szName, m_dwObjectCount)); + } + + delete pObject; + LeaveCriticalSection(&m_CSDebug); + return TRUE; +} + + +/* This runs through the active object list displaying their details */ + +void WINAPI DbgDumpObjectRegister() +{ + TCHAR szInfo[iDEBUGINFO]; + + /* Grab the list critical section */ + + EnterCriticalSection(&m_CSDebug); + ObjectDesc *pObject = pListHead; + + /* Scan the object list displaying the name and cookie */ + + DbgLog((LOG_MEMORY,2,TEXT(""))); + DbgLog((LOG_MEMORY,2,TEXT(" ID Object Description"))); + DbgLog((LOG_MEMORY,2,TEXT(""))); + + while (pObject) { + if(pObject->m_wszName) { + (void)StringCchPrintf(szInfo,NUMELMS(szInfo),TEXT("%5d (%p) %30ls"),pObject->m_dwCookie, &pObject, pObject->m_wszName); + } else { + (void)StringCchPrintf(szInfo,NUMELMS(szInfo),TEXT("%5d (%p) %30hs"),pObject->m_dwCookie, &pObject, pObject->m_szName); + } + DbgLog((LOG_MEMORY,2,szInfo)); + pObject = pObject->m_pNext; + } + + (void)StringCchPrintf(szInfo,NUMELMS(szInfo),TEXT("Total object count %5d"),m_dwObjectCount); + DbgLog((LOG_MEMORY,2,TEXT(""))); + DbgLog((LOG_MEMORY,1,szInfo)); + LeaveCriticalSection(&m_CSDebug); +} + +/* Debug infinite wait stuff */ +DWORD WINAPI DbgWaitForSingleObject(HANDLE h) +{ + DWORD dwWaitResult; + do { + dwWaitResult = WaitForSingleObject(h, dwWaitTimeout); + ASSERT(dwWaitResult == WAIT_OBJECT_0); + } while (dwWaitResult == WAIT_TIMEOUT); + return dwWaitResult; +} +DWORD WINAPI DbgWaitForMultipleObjects(DWORD nCount, + __in_ecount(nCount) CONST HANDLE *lpHandles, + BOOL bWaitAll) +{ + DWORD dwWaitResult; + do { + dwWaitResult = WaitForMultipleObjects(nCount, + lpHandles, + bWaitAll, + dwWaitTimeout); + ASSERT((DWORD)(dwWaitResult - WAIT_OBJECT_0) < MAXIMUM_WAIT_OBJECTS); + } while (dwWaitResult == WAIT_TIMEOUT); + return dwWaitResult; +} + +void WINAPI DbgSetWaitTimeout(DWORD dwTimeout) +{ + dwWaitTimeout = dwTimeout; +} + +#endif /* DEBUG */ + +#ifdef _OBJBASE_H_ + + /* Stuff for printing out our GUID names */ + + GUID_STRING_ENTRY g_GuidNames[] = { + #define OUR_GUID_ENTRY(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + { #name, { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } } }, + #include + }; + + CGuidNameList GuidNames; + int g_cGuidNames = sizeof(g_GuidNames) / sizeof(g_GuidNames[0]); + + char *CGuidNameList::operator [] (const GUID &guid) + { + for (int i = 0; i < g_cGuidNames; i++) { + if (g_GuidNames[i].guid == guid) { + return g_GuidNames[i].szName; + } + } + if (guid == GUID_NULL) { + return "GUID_NULL"; + } + + // !!! add something to print FOURCC guids? + + // shouldn't this print the hex CLSID? + return "Unknown GUID Name"; + } + +#endif /* _OBJBASE_H_ */ + +/* CDisp class - display our data types */ + +// clashes with REFERENCE_TIME +CDisp::CDisp(LONGLONG ll, int Format) +{ + // note: this could be combined with CDisp(LONGLONG) by + // introducing a default format of CDISP_REFTIME + LARGE_INTEGER li; + li.QuadPart = ll; + switch (Format) { + case CDISP_DEC: + { + TCHAR temp[20]; + int pos=20; + temp[--pos] = 0; + int digit; + // always output at least one digit + do { + // Get the rightmost digit - we only need the low word + digit = li.LowPart % 10; + li.QuadPart /= 10; + temp[--pos] = (TCHAR) digit+L'0'; + } while (li.QuadPart); + (void)StringCchCopy(m_String, NUMELMS(m_String), temp+pos); + break; + } + case CDISP_HEX: + default: + (void)StringCchPrintf(m_String, NUMELMS(m_String), TEXT("0x%X%8.8X"), li.HighPart, li.LowPart); + } +}; + +CDisp::CDisp(REFCLSID clsid) +{ +#ifdef UNICODE + (void)StringFromGUID2(clsid, m_String, NUMELMS(m_String)); +#else + WCHAR wszTemp[50]; + (void)StringFromGUID2(clsid, wszTemp, NUMELMS(wszTemp)); + (void)StringCchPrintf(m_String, NUMELMS(m_String), TEXT("%S"), wszTemp); +#endif +}; + +#ifdef __STREAMS__ +/* Display stuff */ +CDisp::CDisp(CRefTime llTime) +{ + LONGLONG llDiv; + if (llTime < 0) { + llTime = -llTime; + (void)StringCchCopy(m_String, NUMELMS(m_String), TEXT("-")); + } + llDiv = (LONGLONG)24 * 3600 * 10000000; + if (llTime >= llDiv) { + (void)StringCchPrintf(m_String + lstrlen(m_String), NUMELMS(m_String) - lstrlen(m_String), TEXT("%d days "), (LONG)(llTime / llDiv)); + llTime = llTime % llDiv; + } + llDiv = (LONGLONG)3600 * 10000000; + if (llTime >= llDiv) { + (void)StringCchPrintf(m_String + lstrlen(m_String), NUMELMS(m_String) - lstrlen(m_String), TEXT("%d hrs "), (LONG)(llTime / llDiv)); + llTime = llTime % llDiv; + } + llDiv = (LONGLONG)60 * 10000000; + if (llTime >= llDiv) { + (void)StringCchPrintf(m_String + lstrlen(m_String), NUMELMS(m_String) - lstrlen(m_String), TEXT("%d mins "), (LONG)(llTime / llDiv)); + llTime = llTime % llDiv; + } + (void)StringCchPrintf(m_String + lstrlen(m_String), NUMELMS(m_String) - lstrlen(m_String), TEXT("%d.%3.3d sec"), + (LONG)llTime / 10000000, + (LONG)((llTime % 10000000) / 10000)); +}; + +#endif // __STREAMS__ + + +/* Display pin */ +CDisp::CDisp(IPin *pPin) +{ + PIN_INFO pi; + TCHAR str[MAX_PIN_NAME]; + CLSID clsid; + + if (pPin) { + pPin->QueryPinInfo(&pi); + pi.pFilter->GetClassID(&clsid); + QueryPinInfoReleaseFilter(pi); + #ifndef UNICODE + WideCharToMultiByte(GetACP(), 0, pi.achName, lstrlenW(pi.achName) + 1, + str, MAX_PIN_NAME, NULL, NULL); + #else + (void)StringCchCopy(str, NUMELMS(str), pi.achName); + #endif + } else { + (void)StringCchCopy(str, NUMELMS(str), TEXT("NULL IPin")); + } + + m_pString = (PTCHAR) new TCHAR[lstrlen(str)+64]; + if (!m_pString) { + return; + } + + (void)StringCchPrintf(m_pString, lstrlen(str) + 64, TEXT("%hs(%s)"), GuidNames[clsid], str); +} + +/* Display filter or pin */ +CDisp::CDisp(IUnknown *pUnk) +{ + IBaseFilter *pf; + HRESULT hr = pUnk->QueryInterface(IID_IBaseFilter, (void **)&pf); + if(SUCCEEDED(hr)) + { + FILTER_INFO fi; + hr = pf->QueryFilterInfo(&fi); + if(SUCCEEDED(hr)) + { + QueryFilterInfoReleaseGraph(fi); + + size_t len = lstrlenW(fi.achName) + 1; + + m_pString = new TCHAR[len]; + if(m_pString) + { +#ifdef UNICODE + (void)StringCchCopy(m_pString, len, fi.achName); +#else + (void)StringCchPrintf(m_pString, len, "%S", fi.achName); +#endif + } + } + + pf->Release(); + + return; + } + + IPin *pp; + hr = pUnk->QueryInterface(IID_IPin, (void **)&pp); + if(SUCCEEDED(hr)) + { + CDisp::CDisp(pp); + pp->Release(); + return; + } +} + + +CDisp::~CDisp() +{ +} + +CDispBasic::~CDispBasic() +{ + if (m_pString != m_String) { + delete [] m_pString; + } +} + +CDisp::CDisp(double d) +{ + (void)StringCchPrintf(m_String, NUMELMS(m_String), TEXT("%d.%03d"), (int) d, (int) ((d - (int) d) * 1000)); +} + + +/* If built for debug this will display the media type details. We convert the + major and subtypes into strings and also ask the base classes for a string + description of the subtype, so MEDIASUBTYPE_RGB565 becomes RGB 565 16 bit + We also display the fields in the BITMAPINFOHEADER structure, this should + succeed as we do not accept input types unless the format is big enough */ + +#ifdef DEBUG +void WINAPI DisplayType(LPCTSTR label, const AM_MEDIA_TYPE *pmtIn) +{ + + /* Dump the GUID types and a short description */ + + DbgLog((LOG_TRACE,5,TEXT(""))); + DbgLog((LOG_TRACE,2,TEXT("%s M type %hs S type %hs"), label, + GuidNames[pmtIn->majortype], + GuidNames[pmtIn->subtype])); + DbgLog((LOG_TRACE,5,TEXT("Subtype description %s"),GetSubtypeName(&pmtIn->subtype))); + + /* Dump the generic media types */ + + if (pmtIn->bTemporalCompression) { + DbgLog((LOG_TRACE,5,TEXT("Temporally compressed"))); + } else { + DbgLog((LOG_TRACE,5,TEXT("Not temporally compressed"))); + } + + if (pmtIn->bFixedSizeSamples) { + DbgLog((LOG_TRACE,5,TEXT("Sample size %d"),pmtIn->lSampleSize)); + } else { + DbgLog((LOG_TRACE,5,TEXT("Variable size samples"))); + } + + if (pmtIn->formattype == FORMAT_VideoInfo) { + + VIDEOINFOHEADER *pVideoInfo = (VIDEOINFOHEADER *)pmtIn->pbFormat; + + DisplayRECT(TEXT("Source rectangle"),pVideoInfo->rcSource); + DisplayRECT(TEXT("Target rectangle"),pVideoInfo->rcTarget); + DisplayBITMAPINFO(HEADER(pmtIn->pbFormat)); + + } if (pmtIn->formattype == FORMAT_VideoInfo2) { + + VIDEOINFOHEADER2 *pVideoInfo2 = (VIDEOINFOHEADER2 *)pmtIn->pbFormat; + + DisplayRECT(TEXT("Source rectangle"),pVideoInfo2->rcSource); + DisplayRECT(TEXT("Target rectangle"),pVideoInfo2->rcTarget); + DbgLog((LOG_TRACE, 5, TEXT("Aspect Ratio: %d:%d"), + pVideoInfo2->dwPictAspectRatioX, + pVideoInfo2->dwPictAspectRatioY)); + DisplayBITMAPINFO(&pVideoInfo2->bmiHeader); + + } else if (pmtIn->majortype == MEDIATYPE_Audio) { + DbgLog((LOG_TRACE,2,TEXT(" Format type %hs"), + GuidNames[pmtIn->formattype])); + DbgLog((LOG_TRACE,2,TEXT(" Subtype %hs"), + GuidNames[pmtIn->subtype])); + + if ((pmtIn->subtype != MEDIASUBTYPE_MPEG1Packet) + && (pmtIn->cbFormat >= sizeof(PCMWAVEFORMAT))) + { + /* Dump the contents of the WAVEFORMATEX type-specific format structure */ + + WAVEFORMATEX *pwfx = (WAVEFORMATEX *) pmtIn->pbFormat; + DbgLog((LOG_TRACE,2,TEXT("wFormatTag %u"), pwfx->wFormatTag)); + DbgLog((LOG_TRACE,2,TEXT("nChannels %u"), pwfx->nChannels)); + DbgLog((LOG_TRACE,2,TEXT("nSamplesPerSec %lu"), pwfx->nSamplesPerSec)); + DbgLog((LOG_TRACE,2,TEXT("nAvgBytesPerSec %lu"), pwfx->nAvgBytesPerSec)); + DbgLog((LOG_TRACE,2,TEXT("nBlockAlign %u"), pwfx->nBlockAlign)); + DbgLog((LOG_TRACE,2,TEXT("wBitsPerSample %u"), pwfx->wBitsPerSample)); + + /* PCM uses a WAVEFORMAT and does not have the extra size field */ + + if (pmtIn->cbFormat >= sizeof(WAVEFORMATEX)) { + DbgLog((LOG_TRACE,2,TEXT("cbSize %u"), pwfx->cbSize)); + } + } else { + } + + } else { + DbgLog((LOG_TRACE,2,TEXT(" Format type %hs"), + GuidNames[pmtIn->formattype])); + } +} + + +void DisplayBITMAPINFO(const BITMAPINFOHEADER* pbmi) +{ + DbgLog((LOG_TRACE,5,TEXT("Size of BITMAPINFO structure %d"),pbmi->biSize)); + if (pbmi->biCompression < 256) { + DbgLog((LOG_TRACE,2,TEXT("%dx%dx%d bit (%d)"), + pbmi->biWidth, pbmi->biHeight, + pbmi->biBitCount, pbmi->biCompression)); + } else { + DbgLog((LOG_TRACE,2,TEXT("%dx%dx%d bit '%4.4hs'"), + pbmi->biWidth, pbmi->biHeight, + pbmi->biBitCount, &pbmi->biCompression)); + } + + DbgLog((LOG_TRACE,2,TEXT("Image size %d"),pbmi->biSizeImage)); + DbgLog((LOG_TRACE,5,TEXT("Planes %d"),pbmi->biPlanes)); + DbgLog((LOG_TRACE,5,TEXT("X Pels per metre %d"),pbmi->biXPelsPerMeter)); + DbgLog((LOG_TRACE,5,TEXT("Y Pels per metre %d"),pbmi->biYPelsPerMeter)); + DbgLog((LOG_TRACE,5,TEXT("Colours used %d"),pbmi->biClrUsed)); +} + + +void DisplayRECT(LPCTSTR szLabel, const RECT& rc) +{ + DbgLog((LOG_TRACE,5,TEXT("%s (Left %d Top %d Right %d Bottom %d)"), + szLabel, + rc.left, + rc.top, + rc.right, + rc.bottom)); +} + + +void WINAPI DumpGraph(IFilterGraph *pGraph, DWORD dwLevel) +{ + if( !pGraph ) + { + return; + } + + IEnumFilters *pFilters; + + DbgLog((LOG_TRACE,dwLevel,TEXT("DumpGraph [%x]"), pGraph)); + + if (FAILED(pGraph->EnumFilters(&pFilters))) { + DbgLog((LOG_TRACE,dwLevel,TEXT("EnumFilters failed!"))); + } + + IBaseFilter *pFilter; + ULONG n; + while (pFilters->Next(1, &pFilter, &n) == S_OK) { + FILTER_INFO info; + + if (FAILED(pFilter->QueryFilterInfo(&info))) { + DbgLog((LOG_TRACE,dwLevel,TEXT(" Filter [%p] -- failed QueryFilterInfo"), pFilter)); + } else { + QueryFilterInfoReleaseGraph(info); + + // !!! should QueryVendorInfo here! + + DbgLog((LOG_TRACE,dwLevel,TEXT(" Filter [%p] '%ls'"), pFilter, info.achName)); + + IEnumPins *pins; + + if (FAILED(pFilter->EnumPins(&pins))) { + DbgLog((LOG_TRACE,dwLevel,TEXT("EnumPins failed!"))); + } else { + + IPin *pPin; + while (pins->Next(1, &pPin, &n) == S_OK) { + PIN_INFO pinInfo; + + if (FAILED(pPin->QueryPinInfo(&pinInfo))) { + DbgLog((LOG_TRACE,dwLevel,TEXT(" Pin [%x] -- failed QueryPinInfo"), pPin)); + } else { + QueryPinInfoReleaseFilter(pinInfo); + + IPin *pPinConnected = NULL; + + HRESULT hr = pPin->ConnectedTo(&pPinConnected); + + if (pPinConnected) { + DbgLog((LOG_TRACE,dwLevel,TEXT(" Pin [%p] '%ls' [%sput]") + TEXT(" Connected to pin [%p]"), + pPin, pinInfo.achName, + pinInfo.dir == PINDIR_INPUT ? TEXT("In") : TEXT("Out"), + pPinConnected)); + + pPinConnected->Release(); + + // perhaps we should really dump the type both ways as a sanity + // check? + if (pinInfo.dir == PINDIR_OUTPUT) { + AM_MEDIA_TYPE mt; + + hr = pPin->ConnectionMediaType(&mt); + + if (SUCCEEDED(hr)) { + DisplayType(TEXT("Connection type"), &mt); + + FreeMediaType(mt); + } + } + } else { + DbgLog((LOG_TRACE,dwLevel, + TEXT(" Pin [%x] '%ls' [%sput]"), + pPin, pinInfo.achName, + pinInfo.dir == PINDIR_INPUT ? TEXT("In") : TEXT("Out"))); + + } + } + + pPin->Release(); + + } + + pins->Release(); + } + + } + + pFilter->Release(); + } + + pFilters->Release(); + +} + +#endif + diff --git a/src/baseclasses/wxdebug.h b/src/baseclasses/wxdebug.h new file mode 100644 index 0000000..62efffb --- /dev/null +++ b/src/baseclasses/wxdebug.h @@ -0,0 +1,359 @@ +//------------------------------------------------------------------------------ +// File: WXDebug.h +// +// Desc: DirectShow base classes - provides debugging facilities. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#ifndef __WXDEBUG__ +#define __WXDEBUG__ + +// This library provides fairly straight forward debugging functionality, this +// is split into two main sections. The first is assertion handling, there are +// three types of assertions provided here. The most commonly used one is the +// ASSERT(condition) macro which will pop up a message box including the file +// and line number if the condition evaluates to FALSE. Then there is the +// EXECUTE_ASSERT macro which is the same as ASSERT except the condition will +// still be executed in NON debug builds. The final type of assertion is the +// KASSERT macro which is more suitable for pure (perhaps kernel) filters as +// the condition is printed onto the debugger rather than in a message box. +// +// The other part of the debug module facilties is general purpose logging. +// This is accessed by calling DbgLog(). The function takes a type and level +// field which define the type of informational string you are presenting and +// it's relative importance. The type field can be a combination (one or more) +// of LOG_TIMING, LOG_TRACE, LOG_MEMORY, LOG_LOCKING and LOG_ERROR. The level +// is a DWORD value where zero defines highest important. Use of zero as the +// debug logging level is to be encouraged ONLY for major errors or events as +// they will ALWAYS be displayed on the debugger. Other debug output has it's +// level matched against the current debug output level stored in the registry +// for this module and if less than the current setting it will be displayed. +// +// Each module or executable has it's own debug output level for each of the +// five types. These are read in when the DbgInitialise function is called +// for DLLs linking to STRMBASE.LIB this is done automatically when the DLL +// is loaded, executables must call it explicitely with the module instance +// handle given to them through the WINMAIN entry point. An executable must +// also call DbgTerminate when they have finished to clean up the resources +// the debug library uses, once again this is done automatically for DLLs + +// These are the five different categories of logging information + +enum { LOG_TIMING = 0x01, // Timing and performance measurements + LOG_TRACE = 0x02, // General step point call tracing + LOG_MEMORY = 0x04, // Memory and object allocation/destruction + LOG_LOCKING = 0x08, // Locking/unlocking of critical sections + LOG_ERROR = 0x10, // Debug error notification + LOG_CUSTOM1 = 0x20, + LOG_CUSTOM2 = 0x40, + LOG_CUSTOM3 = 0x80, + LOG_CUSTOM4 = 0x100, + LOG_CUSTOM5 = 0x200, +}; + +#define LOG_FORCIBLY_SET 0x80000000 + +enum { CDISP_HEX = 0x01, + CDISP_DEC = 0x02}; + +// For each object created derived from CBaseObject (in debug builds) we +// create a descriptor that holds it's name (statically allocated memory) +// and a cookie we assign it. We keep a list of all the active objects +// we have registered so that we can dump a list of remaining objects + +typedef struct tag_ObjectDesc { + LPCSTR m_szName; + LPCWSTR m_wszName; + DWORD m_dwCookie; + tag_ObjectDesc *m_pNext; +} ObjectDesc; + +#define DLLIMPORT __declspec(dllimport) +#define DLLEXPORT __declspec(dllexport) + +#ifdef DEBUG + + #define NAME(x) TEXT(x) + + // These are used internally by the debug library (PRIVATE) + + void WINAPI DbgInitKeyLevels(HKEY hKey, bool fTakeMax); + void WINAPI DbgInitGlobalSettings(bool fTakeMax); + void WINAPI DbgInitModuleSettings(bool fTakeMax); + void WINAPI DbgInitModuleName(); + DWORD WINAPI DbgRegisterObjectCreation( + LPCSTR szObjectName, LPCWSTR wszObjectName); + + BOOL WINAPI DbgRegisterObjectDestruction(DWORD dwCookie); + + // These are the PUBLIC entry points + + BOOL WINAPI DbgCheckModuleLevel(DWORD Type,DWORD Level); + void WINAPI DbgSetModuleLevel(DWORD Type,DWORD Level); + void WINAPI DbgSetAutoRefreshLevels(bool fAuto); + + // Initialise the library with the module handle + + void WINAPI DbgInitialise(HINSTANCE hInst); + void WINAPI DbgTerminate(); + + void WINAPI DbgDumpObjectRegister(); + + // Display error and logging to the user + + void WINAPI DbgAssert(LPCTSTR pCondition,LPCTSTR pFileName,INT iLine); + void WINAPI DbgBreakPoint(LPCTSTR pCondition,LPCTSTR pFileName,INT iLine); + void WINAPI DbgBreakPoint(LPCTSTR pFileName,INT iLine,__format_string LPCTSTR szFormatString,...); + + void WINAPI DbgKernelAssert(LPCTSTR pCondition,LPCTSTR pFileName,INT iLine); + void WINAPI DbgLogInfo(DWORD Type,DWORD Level,__format_string LPCTSTR pFormat,...); +#ifdef UNICODE + void WINAPI DbgLogInfo(DWORD Type,DWORD Level,__format_string LPCSTR pFormat,...); + void WINAPI DbgAssert(LPCSTR pCondition,LPCSTR pFileName,INT iLine); + void WINAPI DbgBreakPoint(LPCSTR pCondition,LPCSTR pFileName,INT iLine); + void WINAPI DbgKernelAssert(LPCSTR pCondition,LPCSTR pFileName,INT iLine); +#endif + void WINAPI DbgOutString(LPCTSTR psz); + + // Debug infinite wait stuff + DWORD WINAPI DbgWaitForSingleObject(HANDLE h); + DWORD WINAPI DbgWaitForMultipleObjects(DWORD nCount, + __in_ecount(nCount) CONST HANDLE *lpHandles, + BOOL bWaitAll); + void WINAPI DbgSetWaitTimeout(DWORD dwTimeout); + +#ifdef __strmif_h__ + // Display a media type: Terse at level 2, verbose at level 5 + void WINAPI DisplayType(LPCTSTR label, const AM_MEDIA_TYPE *pmtIn); + + // Dump lots of information about a filter graph + void WINAPI DumpGraph(IFilterGraph *pGraph, DWORD dwLevel); +#endif + + #define KASSERT(_x_) if (!(_x_)) \ + DbgKernelAssert(TEXT(#_x_),TEXT(__FILE__),__LINE__) + + // Break on the debugger without putting up a message box + // message goes to debugger instead + + #define KDbgBreak(_x_) \ + DbgKernelAssert(TEXT(#_x_),TEXT(__FILE__),__LINE__) + + // We chose a common name for our ASSERT macro, MFC also uses this name + // So long as the implementation evaluates the condition and handles it + // then we will be ok. Rather than override the behaviour expected we + // will leave whatever first defines ASSERT as the handler (i.e. MFC) + #ifndef ASSERT + #define ASSERT(_x_) if (!(_x_)) \ + DbgAssert(TEXT(#_x_),TEXT(__FILE__),__LINE__) + #endif + + #define DbgAssertAligned( _ptr_, _alignment_ ) ASSERT( ((DWORD_PTR) (_ptr_)) % (_alignment_) == 0) + + // Put up a message box informing the user of a halt + // condition in the program + + #define DbgBreak(_x_) \ + DbgBreakPoint(TEXT(#_x_),TEXT(__FILE__),__LINE__) + + #define EXECUTE_ASSERT(_x_) ASSERT(_x_) + #define DbgLog(_x_) DbgLogInfo _x_ + // MFC style trace macros + + #define NOTE(_x_) DbgLog((LOG_TRACE,5,TEXT(_x_))) + #define NOTE1(_x_,a) DbgLog((LOG_TRACE,5,TEXT(_x_),a)) + #define NOTE2(_x_,a,b) DbgLog((LOG_TRACE,5,TEXT(_x_),a,b)) + #define NOTE3(_x_,a,b,c) DbgLog((LOG_TRACE,5,TEXT(_x_),a,b,c)) + #define NOTE4(_x_,a,b,c,d) DbgLog((LOG_TRACE,5,TEXT(_x_),a,b,c,d)) + #define NOTE5(_x_,a,b,c,d,e) DbgLog((LOG_TRACE,5,TEXT(_x_),a,b,c,d,e)) + +#else + + // Retail builds make public debug functions inert - WARNING the source + // files do not define or build any of the entry points in debug builds + // (public entry points compile to nothing) so if you go trying to call + // any of the private entry points in your source they won't compile + + #define NAME(_x_) ((LPTSTR) NULL) + + #define DbgInitialise(hInst) + #define DbgTerminate() + #define DbgLog(_x_) 0 + #define DbgOutString(psz) + #define DbgAssertAligned( _ptr_, _alignment_ ) 0 + + #define DbgRegisterObjectCreation(pObjectName) + #define DbgRegisterObjectDestruction(dwCookie) + #define DbgDumpObjectRegister() + + #define DbgCheckModuleLevel(Type,Level) + #define DbgSetModuleLevel(Type,Level) + #define DbgSetAutoRefreshLevels(fAuto) + + #define DbgWaitForSingleObject(h) WaitForSingleObject(h, INFINITE) + #define DbgWaitForMultipleObjects(nCount, lpHandles, bWaitAll) \ + WaitForMultipleObjects(nCount, lpHandles, bWaitAll, INFINITE) + #define DbgSetWaitTimeout(dwTimeout) + + #define KDbgBreak(_x_) + #define DbgBreak(_x_) + + #define KASSERT(_x_) ((void)0) + #ifndef ASSERT + #define ASSERT(_x_) ((void)0) + #endif + #define EXECUTE_ASSERT(_x_) ((void)(_x_)) + + // MFC style trace macros + + #define NOTE(_x_) ((void)0) + #define NOTE1(_x_,a) ((void)0) + #define NOTE2(_x_,a,b) ((void)0) + #define NOTE3(_x_,a,b,c) ((void)0) + #define NOTE4(_x_,a,b,c,d) ((void)0) + #define NOTE5(_x_,a,b,c,d,e) ((void)0) + + #define DisplayType(label, pmtIn) ((void)0) + #define DumpGraph(pGraph, label) ((void)0) +#endif + + +// Checks a pointer which should be non NULL - can be used as follows. + +#define CheckPointer(p,ret) {if((p)==NULL) return (ret);} + +// HRESULT Foo(VOID *pBar) +// { +// CheckPointer(pBar,E_INVALIDARG) +// } +// +// Or if the function returns a boolean +// +// BOOL Foo(VOID *pBar) +// { +// CheckPointer(pBar,FALSE) +// } + +#define ValidateReadPtr(p,cb) 0 +#define ValidateWritePtr(p,cb) 0 +#define ValidateReadWritePtr(p,cb) 0 +#define ValidateStringPtr(p) 0 +#define ValidateStringPtrA(p) 0 +#define ValidateStringPtrW(p) 0 + + +#ifdef _OBJBASE_H_ + + // Outputting GUID names. If you want to include the name + // associated with a GUID (eg CLSID_...) then + // + // GuidNames[yourGUID] + // + // Returns the name defined in uuids.h as a string + + typedef struct { + CHAR *szName; + GUID guid; + } GUID_STRING_ENTRY; + + class CGuidNameList { + public: + CHAR *operator [] (const GUID& guid); + }; + + extern CGuidNameList GuidNames; + +#endif + +#ifndef REMIND + // REMIND macro - generates warning as reminder to complete coding + // (eg) usage: + // + // #pragma message (REMIND("Add automation support")) + + + #define QUOTE(x) #x + #define QQUOTE(y) QUOTE(y) + #define REMIND(str) __FILE__ "(" QQUOTE(__LINE__) ") : " str +#endif + +// Method to display objects in a useful format +// +// eg If you want to display a LONGLONG ll in a debug string do (eg) +// +// DbgLog((LOG_TRACE, n, TEXT("Value is %s"), (LPCTSTR)CDisp(ll, CDISP_HEX))); + + +class CDispBasic +{ +public: + CDispBasic() { m_pString = m_String; }; + ~CDispBasic(); +protected: + PTCHAR m_pString; // normally points to m_String... unless too much data + TCHAR m_String[50]; +}; +class CDisp : public CDispBasic +{ +public: + CDisp(LONGLONG ll, int Format = CDISP_HEX); // Display a LONGLONG in CDISP_HEX or CDISP_DEC form + CDisp(REFCLSID clsid); // Display a GUID + CDisp(double d); // Display a floating point number +#ifdef __strmif_h__ +#ifdef __STREAMS__ + CDisp(CRefTime t); // Display a Reference Time +#endif + CDisp(IPin *pPin); // Display a pin as {filter clsid}(pin name) + CDisp(IUnknown *pUnk); // Display a filter or pin +#endif // __strmif_h__ + ~CDisp(); + + // Implement cast to (LPCTSTR) as parameter to logger + operator LPCTSTR() + { + return (LPCTSTR)m_pString; + }; +}; + + +#if defined(DEBUG) +class CAutoTrace +{ +private: + LPCTSTR _szBlkName; + const int _level; + static const TCHAR _szEntering[]; + static const TCHAR _szLeaving[]; +public: + CAutoTrace(LPCTSTR szBlkName, const int level = 15) + : _szBlkName(szBlkName), _level(level) + {DbgLog((LOG_TRACE, _level, _szEntering, _szBlkName));} + + ~CAutoTrace() + {DbgLog((LOG_TRACE, _level, _szLeaving, _szBlkName));} +}; + +#if defined (__FUNCTION__) + +#define AMTRACEFN() CAutoTrace __trace(TEXT(__FUNCTION__)) +#define AMTRACE(_x_) CAutoTrace __trace(TEXT(__FUNCTION__)) + +#else + +#define AMTRACE(_x_) CAutoTrace __trace _x_ +#define AMTRACEFN() + +#endif + +#else + +#define AMTRACE(_x_) +#define AMTRACEFN() + +#endif + +#endif // __WXDEBUG__ + + diff --git a/src/baseclasses/wxlist.cpp b/src/baseclasses/wxlist.cpp new file mode 100644 index 0000000..07be7e1 --- /dev/null +++ b/src/baseclasses/wxlist.cpp @@ -0,0 +1,891 @@ +//------------------------------------------------------------------------------ +// File: WXList.cpp +// +// Desc: DirectShow base classes - implements a non-MFC based generic list +// template class. +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +/* A generic list of pointers to objects. + Objectives: avoid using MFC libraries in ndm kernel mode and + provide a really useful list type. + + The class is thread safe in that separate threads may add and + delete items in the list concurrently although the application + must ensure that constructor and destructor access is suitably + synchronised. + + The list name must not conflict with MFC classes as an + application may use both + + The nodes form a doubly linked, NULL terminated chain with an anchor + block (the list object per se) holding pointers to the first and last + nodes and a count of the nodes. + There is a node cache to reduce the allocation and freeing overhead. + It optionally (determined at construction time) has an Event which is + set whenever the list becomes non-empty and reset whenever it becomes + empty. + It optionally (determined at construction time) has a Critical Section + which is entered during the important part of each operation. (About + all you can do outside it is some parameter checking). + + The node cache is a repository of nodes that are NOT in the list to speed + up storage allocation. Each list has its own cache to reduce locking and + serialising. The list accesses are serialised anyway for a given list - a + common cache would mean that we would have to separately serialise access + of all lists within the cache. Because the cache only stores nodes that are + not in the list, releasing the cache does not release any list nodes. This + means that list nodes can be copied or rechained from one list to another + without danger of creating a dangling reference if the original cache goes + away. + + Questionable design decisions: + 1. Retaining the warts for compatibility + 2. Keeping an element count -i.e. counting whenever we do anything + instead of only when we want the count. + 3. Making the chain pointers NULL terminated. If the list object + itself looks just like a node and the list is kept as a ring then + it reduces the number of special cases. All inserts look the same. +*/ + + +#include + +/* set cursor to the position of each element of list in turn */ +#define INTERNALTRAVERSELIST(list, cursor) \ +for ( cursor = (list).GetHeadPositionI() \ + ; cursor!=NULL \ + ; cursor = (list).Next(cursor) \ + ) + + +/* set cursor to the position of each element of list in turn + in reverse order +*/ +#define INTERNALREVERSETRAVERSELIST(list, cursor) \ +for ( cursor = (list).GetTailPositionI() \ + ; cursor!=NULL \ + ; cursor = (list).Prev(cursor) \ + ) + +/* Constructor calls a separate initialisation function that + creates a node cache, optionally creates a lock object + and optionally creates a signaling object. + + By default we create a locking object, a DEFAULTCACHE sized + cache but no event object so the list cannot be used in calls + to WaitForSingleObject +*/ +CBaseList::CBaseList(__in_opt LPCTSTR pName, // Descriptive list name + INT iItems) : // Node cache size +#ifdef DEBUG + CBaseObject(pName), +#endif + m_pFirst(NULL), + m_pLast(NULL), + m_Count(0), + m_Cache(iItems) +{ +} // constructor + +CBaseList::CBaseList(__in_opt LPCTSTR pName) : // Descriptive list name +#ifdef DEBUG + CBaseObject(pName), +#endif + m_pFirst(NULL), + m_pLast(NULL), + m_Count(0), + m_Cache(DEFAULTCACHE) +{ +} // constructor + +#ifdef UNICODE +CBaseList::CBaseList(__in_opt LPCSTR pName, // Descriptive list name + INT iItems) : // Node cache size +#ifdef DEBUG + CBaseObject(pName), +#endif + m_pFirst(NULL), + m_pLast(NULL), + m_Count(0), + m_Cache(iItems) +{ +} // constructor + +CBaseList::CBaseList(__in_opt LPCSTR pName) : // Descriptive list name +#ifdef DEBUG + CBaseObject(pName), +#endif + m_pFirst(NULL), + m_pLast(NULL), + m_Count(0), + m_Cache(DEFAULTCACHE) +{ +} // constructor + +#endif + +/* The destructor enumerates all the node objects in the list and + in the cache deleting each in turn. We do not do any processing + on the objects that the list holds (i.e. points to) so if they + represent interfaces for example the creator of the list should + ensure that each of them is released before deleting us +*/ +CBaseList::~CBaseList() +{ + /* Delete all our list nodes */ + + RemoveAll(); + +} // destructor + +/* Remove all the nodes from the list but don't do anything + with the objects that each node looks after (this is the + responsibility of the creator). + Aa a last act we reset the signalling event + (if available) to indicate to clients that the list + does not have any entries in it. +*/ +void CBaseList::RemoveAll() +{ + /* Free up all the CNode objects NOTE we don't bother putting the + deleted nodes into the cache as this method is only really called + in serious times of change such as when we are being deleted at + which point the cache will be deleted anway */ + + CNode *pn = m_pFirst; + while (pn) { + CNode *op = pn; + pn = pn->Next(); + delete op; + } + + /* Reset the object count and the list pointers */ + + m_Count = 0; + m_pFirst = m_pLast = NULL; + +} // RemoveAll + + + +/* Return a position enumerator for the entire list. + A position enumerator is a pointer to a node object cast to a + transparent type so all we do is return the head/tail node + pointer in the list. + WARNING because the position is a pointer to a node there is + an implicit assumption for users a the list class that after + deleting an object from the list that any other position + enumerators that you have may be invalid (since the node + may be gone). +*/ +__out_opt POSITION CBaseList::GetHeadPositionI() const +{ + return (POSITION) m_pFirst; +} // GetHeadPosition + + + +__out_opt POSITION CBaseList::GetTailPositionI() const +{ + return (POSITION) m_pLast; +} // GetTailPosition + + + +/* Get the number of objects in the list, + Get the lock before accessing the count. + Locking may not be entirely necessary but it has the side effect + of making sure that all operations are complete before we get it. + So for example if a list is being added to this list then that + will have completed in full before we continue rather than seeing + an intermediate albeit valid state +*/ +int CBaseList::GetCountI() const +{ + return m_Count; +} // GetCount + + + +/* Return the object at rp, update rp to the next object from + the list or NULL if you have moved over the last object. + You may still call this function once we return NULL but + we will continue to return a NULL position value +*/ +__out void *CBaseList::GetNextI(__inout POSITION& rp) const +{ + /* have we reached the end of the list */ + + if (rp == NULL) { + return NULL; + } + + /* Lock the object before continuing */ + + void *pObject; + + /* Copy the original position then step on */ + + CNode *pn = (CNode *) rp; + ASSERT(pn != NULL); + rp = (POSITION) pn->Next(); + + /* Get the object at the original position from the list */ + + pObject = pn->GetData(); + // ASSERT(pObject != NULL); // NULL pointers in the list are allowed. + return pObject; +} //GetNext + + + +/* Return the object at p. + Asking for the object at NULL ASSERTs then returns NULL + The object is NOT locked. The list is not being changed + in any way. If another thread is busy deleting the object + then locking would only result in a change from one bad + behaviour to another. +*/ +__out_opt void *CBaseList::GetI(__in_opt POSITION p) const +{ + if (p == NULL) { + return NULL; + } + + CNode * pn = (CNode *) p; + void *pObject = pn->GetData(); + // ASSERT(pObject != NULL); // NULL pointers in the list are allowed. + return pObject; +} //Get + +__out void *CBaseList::GetValidI(__in POSITION p) const +{ + CNode * pn = (CNode *) p; + void *pObject = pn->GetData(); + // ASSERT(pObject != NULL); // NULL pointers in the list are allowed. + return pObject; +} //Get + + +/* Return the first position in the list which holds the given pointer. + Return NULL if it's not found. +*/ +__out_opt POSITION CBaseList::FindI( __in void * pObj) const +{ + POSITION pn; + INTERNALTRAVERSELIST(*this, pn){ + if (GetI(pn)==pObj) { + return pn; + } + } + return NULL; +} // Find + + + +/* Remove the first node in the list (deletes the pointer to its object + from the list, does not free the object itself). + Return the pointer to its object or NULL if empty +*/ +__out_opt void *CBaseList::RemoveHeadI() +{ + /* All we do is get the head position and ask for that to be deleted. + We could special case this since some of the code path checking + in Remove() is redundant as we know there is no previous + node for example but it seems to gain little over the + added complexity + */ + + return RemoveI((POSITION)m_pFirst); +} // RemoveHead + + + +/* Remove the last node in the list (deletes the pointer to its object + from the list, does not free the object itself). + Return the pointer to its object or NULL if empty +*/ +__out_opt void *CBaseList::RemoveTailI() +{ + /* All we do is get the tail position and ask for that to be deleted. + We could special case this since some of the code path checking + in Remove() is redundant as we know there is no previous + node for example but it seems to gain little over the + added complexity + */ + + return RemoveI((POSITION)m_pLast); +} // RemoveTail + + + +/* Remove the pointer to the object in this position from the list. + Deal with all the chain pointers + Return a pointer to the object removed from the list. + The node object that is freed as a result + of this operation is added to the node cache where + it can be used again. + Remove(NULL) is a harmless no-op - but probably is a wart. +*/ +__out_opt void *CBaseList::RemoveI(__in_opt POSITION pos) +{ + /* Lock the critical section before continuing */ + + // ASSERT (pos!=NULL); // Removing NULL is to be harmless! + if (pos==NULL) return NULL; + + + CNode *pCurrent = (CNode *) pos; + ASSERT(pCurrent != NULL); + + /* Update the previous node */ + + CNode *pNode = pCurrent->Prev(); + if (pNode == NULL) { + m_pFirst = pCurrent->Next(); + } else { + pNode->SetNext(pCurrent->Next()); + } + + /* Update the following node */ + + pNode = pCurrent->Next(); + if (pNode == NULL) { + m_pLast = pCurrent->Prev(); + } else { + pNode->SetPrev(pCurrent->Prev()); + } + + /* Get the object this node was looking after */ + + void *pObject = pCurrent->GetData(); + + // ASSERT(pObject != NULL); // NULL pointers in the list are allowed. + + /* Try and add the node object to the cache - + a NULL return code from the cache means we ran out of room. + The cache size is fixed by a constructor argument when the + list is created and defaults to DEFAULTCACHE. + This means that the cache will have room for this many + node objects. So if you have a list of media samples + and you know there will never be more than five active at + any given time of them for example then override the default + constructor + */ + + m_Cache.AddToCache(pCurrent); + + /* If the list is empty then reset the list event */ + + --m_Count; + ASSERT(m_Count >= 0); + return pObject; +} // Remove + + + +/* Add this object to the tail end of our list + Return the new tail position. +*/ + +__out_opt POSITION CBaseList::AddTailI(__in void *pObject) +{ + /* Lock the critical section before continuing */ + + CNode *pNode; + // ASSERT(pObject); // NULL pointers in the list are allowed. + + /* If there is a node objects in the cache then use + that otherwise we will have to create a new one */ + + pNode = (CNode *) m_Cache.RemoveFromCache(); + if (pNode == NULL) { + pNode = new CNode; + } + + /* Check we have a valid object */ + + if (pNode == NULL) { + return NULL; + } + + /* Initialise all the CNode object + just in case it came from the cache + */ + + pNode->SetData(pObject); + pNode->SetNext(NULL); + pNode->SetPrev(m_pLast); + + if (m_pLast == NULL) { + m_pFirst = pNode; + } else { + m_pLast->SetNext(pNode); + } + + /* Set the new last node pointer and also increment the number + of list entries, the critical section is unlocked when we + exit the function + */ + + m_pLast = pNode; + ++m_Count; + + return (POSITION) pNode; +} // AddTail(object) + + + +/* Add this object to the head end of our list + Return the new head position. +*/ +__out_opt POSITION CBaseList::AddHeadI(__in void *pObject) +{ + CNode *pNode; + // ASSERT(pObject); // NULL pointers in the list are allowed. + + /* If there is a node objects in the cache then use + that otherwise we will have to create a new one */ + + pNode = (CNode *) m_Cache.RemoveFromCache(); + if (pNode == NULL) { + pNode = new CNode; + } + + /* Check we have a valid object */ + + if (pNode == NULL) { + return NULL; + } + + /* Initialise all the CNode object + just in case it came from the cache + */ + + pNode->SetData(pObject); + + /* chain it in (set four pointers) */ + pNode->SetPrev(NULL); + pNode->SetNext(m_pFirst); + + if (m_pFirst == NULL) { + m_pLast = pNode; + } else { + m_pFirst->SetPrev(pNode); + } + m_pFirst = pNode; + + ++m_Count; + + return (POSITION) pNode; +} // AddHead(object) + + + +/* Add all the elements in *pList to the tail of this list. + Return TRUE if it all worked, FALSE if it didn't. + If it fails some elements may have been added. +*/ +BOOL CBaseList::AddTail(__in CBaseList *pList) +{ + /* lock the object before starting then enumerate + each entry in the source list and add them one by one to + our list (while still holding the object lock) + Lock the other list too. + */ + POSITION pos = pList->GetHeadPositionI(); + + while (pos) { + if (NULL == AddTailI(pList->GetNextI(pos))) { + return FALSE; + } + } + return TRUE; +} // AddTail(list) + + + +/* Add all the elements in *pList to the head of this list. + Return TRUE if it all worked, FALSE if it didn't. + If it fails some elements may have been added. +*/ +BOOL CBaseList::AddHead(__in CBaseList *pList) +{ + /* lock the object before starting then enumerate + each entry in the source list and add them one by one to + our list (while still holding the object lock) + Lock the other list too. + + To avoid reversing the list, traverse it backwards. + */ + + POSITION pos; + + INTERNALREVERSETRAVERSELIST(*pList, pos) { + if (NULL== AddHeadI(pList->GetValidI(pos))){ + return FALSE; + } + } + return TRUE; +} // AddHead(list) + + + +/* Add the object after position p + p is still valid after the operation. + AddAfter(NULL,x) adds x to the start - same as AddHead + Return the position of the new object, NULL if it failed +*/ +__out_opt POSITION CBaseList::AddAfterI(__in_opt POSITION pos, __in void * pObj) +{ + if (pos==NULL) + return AddHeadI(pObj); + + /* As someone else might be furkling with the list - + Lock the critical section before continuing + */ + CNode *pAfter = (CNode *) pos; + ASSERT(pAfter != NULL); + if (pAfter==m_pLast) + return AddTailI(pObj); + + /* set pnode to point to a new node, preferably from the cache */ + + CNode *pNode = (CNode *) m_Cache.RemoveFromCache(); + if (pNode == NULL) { + pNode = new CNode; + } + + /* Check we have a valid object */ + + if (pNode == NULL) { + return NULL; + } + + /* Initialise all the CNode object + just in case it came from the cache + */ + + pNode->SetData(pObj); + + /* It is to be added to the middle of the list - there is a before + and after node. Chain it after pAfter, before pBefore. + */ + CNode * pBefore = pAfter->Next(); + ASSERT(pBefore != NULL); + + /* chain it in (set four pointers) */ + pNode->SetPrev(pAfter); + pNode->SetNext(pBefore); + pBefore->SetPrev(pNode); + pAfter->SetNext(pNode); + + ++m_Count; + + return (POSITION) pNode; + +} // AddAfter(object) + + + +BOOL CBaseList::AddAfter(__in_opt POSITION p, __in CBaseList *pList) +{ + POSITION pos; + INTERNALTRAVERSELIST(*pList, pos) { + /* p follows along the elements being added */ + p = AddAfterI(p, pList->GetValidI(pos)); + if (p==NULL) return FALSE; + } + return TRUE; +} // AddAfter(list) + + + +/* Mirror images: + Add the element or list after position p. + p is still valid after the operation. + AddBefore(NULL,x) adds x to the end - same as AddTail +*/ +__out_opt POSITION CBaseList::AddBeforeI(__in_opt POSITION pos, __in void * pObj) +{ + if (pos==NULL) + return AddTailI(pObj); + + /* set pnode to point to a new node, preferably from the cache */ + + CNode *pBefore = (CNode *) pos; + ASSERT(pBefore != NULL); + if (pBefore==m_pFirst) + return AddHeadI(pObj); + + CNode * pNode = (CNode *) m_Cache.RemoveFromCache(); + if (pNode == NULL) { + pNode = new CNode; + } + + /* Check we have a valid object */ + + if (pNode == NULL) { + return NULL; + } + + /* Initialise all the CNode object + just in case it came from the cache + */ + + pNode->SetData(pObj); + + /* It is to be added to the middle of the list - there is a before + and after node. Chain it after pAfter, before pBefore. + */ + + CNode * pAfter = pBefore->Prev(); + ASSERT(pAfter != NULL); + + /* chain it in (set four pointers) */ + pNode->SetPrev(pAfter); + pNode->SetNext(pBefore); + pBefore->SetPrev(pNode); + pAfter->SetNext(pNode); + + ++m_Count; + + return (POSITION) pNode; + +} // Addbefore(object) + + + +BOOL CBaseList::AddBefore(__in_opt POSITION p, __in CBaseList *pList) +{ + POSITION pos; + INTERNALREVERSETRAVERSELIST(*pList, pos) { + /* p follows along the elements being added */ + p = AddBeforeI(p, pList->GetValidI(pos)); + if (p==NULL) return FALSE; + } + return TRUE; +} // AddBefore(list) + + + +/* Split *this after position p in *this + Retain as *this the tail portion of the original *this + Add the head portion to the tail end of *pList + Return TRUE if it all worked, FALSE if it didn't. + + e.g. + foo->MoveToTail(foo->GetHeadPosition(), bar); + moves one element from the head of foo to the tail of bar + foo->MoveToTail(NULL, bar); + is a no-op + foo->MoveToTail(foo->GetTailPosition, bar); + concatenates foo onto the end of bar and empties foo. + + A better, except excessively long name might be + MoveElementsFromHeadThroughPositionToOtherTail +*/ +BOOL CBaseList::MoveToTail + (__in_opt POSITION pos, __in CBaseList *pList) +{ + /* Algorithm: + Note that the elements (including their order) in the concatenation + of *pList to the head of *this is invariant. + 1. Count elements to be moved + 2. Join *pList onto the head of this to make one long chain + 3. Set first/Last pointers in *this and *pList + 4. Break the chain at the new place + 5. Adjust counts + 6. Set/Reset any events + */ + + if (pos==NULL) return TRUE; // no-op. Eliminates special cases later. + + + /* Make cMove the number of nodes to move */ + CNode * p = (CNode *)pos; + int cMove = 0; // number of nodes to move + while(p!=NULL) { + p = p->Prev(); + ++cMove; + } + + + /* Join the two chains together */ + if (pList->m_pLast!=NULL) + pList->m_pLast->SetNext(m_pFirst); + if (m_pFirst!=NULL) + m_pFirst->SetPrev(pList->m_pLast); + + + /* set first and last pointers */ + p = (CNode *)pos; + + if (pList->m_pFirst==NULL) + pList->m_pFirst = m_pFirst; + m_pFirst = p->Next(); + if (m_pFirst==NULL) + m_pLast = NULL; + pList->m_pLast = p; + + + /* Break the chain after p to create the new pieces */ + if (m_pFirst!=NULL) + m_pFirst->SetPrev(NULL); + p->SetNext(NULL); + + + /* Adjust the counts */ + m_Count -= cMove; + pList->m_Count += cMove; + + return TRUE; + +} // MoveToTail + + + +/* Mirror image of MoveToTail: + Split *this before position p in *this. + Retain in *this the head portion of the original *this + Add the tail portion to the start (i.e. head) of *pList + Return TRUE if it all worked, FALSE if it didn't. + + e.g. + foo->MoveToHead(foo->GetTailPosition(), bar); + moves one element from the tail of foo to the head of bar + foo->MoveToHead(NULL, bar); + is a no-op + foo->MoveToHead(foo->GetHeadPosition, bar); + concatenates foo onto the start of bar and empties foo. +*/ +BOOL CBaseList::MoveToHead + (__in_opt POSITION pos, __in CBaseList *pList) +{ + + /* See the comments on the algorithm in MoveToTail */ + + if (pos==NULL) return TRUE; // no-op. Eliminates special cases later. + + /* Make cMove the number of nodes to move */ + CNode * p = (CNode *)pos; + int cMove = 0; // number of nodes to move + while(p!=NULL) { + p = p->Next(); + ++cMove; + } + + + /* Join the two chains together */ + if (pList->m_pFirst!=NULL) + pList->m_pFirst->SetPrev(m_pLast); + if (m_pLast!=NULL) + m_pLast->SetNext(pList->m_pFirst); + + + /* set first and last pointers */ + p = (CNode *)pos; + + + if (pList->m_pLast==NULL) + pList->m_pLast = m_pLast; + + m_pLast = p->Prev(); + if (m_pLast==NULL) + m_pFirst = NULL; + pList->m_pFirst = p; + + + /* Break the chain after p to create the new pieces */ + if (m_pLast!=NULL) + m_pLast->SetNext(NULL); + p->SetPrev(NULL); + + + /* Adjust the counts */ + m_Count -= cMove; + pList->m_Count += cMove; + + return TRUE; + +} // MoveToHead + + + +/* Reverse the order of the [pointers to] objects in *this +*/ +void CBaseList::Reverse() +{ + /* algorithm: + The obvious booby trap is that you flip pointers around and lose + addressability to the node that you are going to process next. + The easy way to avoid this is do do one chain at a time. + + Run along the forward chain, + For each node, set the reverse pointer to the one ahead of us. + The reverse chain is now a copy of the old forward chain, including + the NULL termination. + + Run along the reverse chain (i.e. old forward chain again) + For each node set the forward pointer of the node ahead to point back + to the one we're standing on. + The first node needs special treatment, + it's new forward pointer is NULL. + Finally set the First/Last pointers + + */ + CNode * p; + + // Yes we COULD use a traverse, but it would look funny! + p = m_pFirst; + while (p!=NULL) { + CNode * q; + q = p->Next(); + p->SetNext(p->Prev()); + p->SetPrev(q); + p = q; + } + + p = m_pFirst; + m_pFirst = m_pLast; + m_pLast = p; + + +#if 0 // old version + + if (m_pFirst==NULL) return; // empty list + if (m_pFirst->Next()==NULL) return; // single node list + + + /* run along forward chain */ + for ( p = m_pFirst + ; p!=NULL + ; p = p->Next() + ){ + p->SetPrev(p->Next()); + } + + + /* special case first element */ + m_pFirst->SetNext(NULL); // fix the old first element + + + /* run along new reverse chain i.e. old forward chain again */ + for ( p = m_pFirst // start at the old first element + ; p->Prev()!=NULL // while there's a node still to be set + ; p = p->Prev() // work in the same direction as before + ){ + p->Prev()->SetNext(p); + } + + + /* fix forward and reverse pointers + - the triple XOR swap would work but all the casts look hideous */ + p = m_pFirst; + m_pFirst = m_pLast; + m_pLast = p; +#endif + +} // Reverse diff --git a/src/baseclasses/wxlist.h b/src/baseclasses/wxlist.h new file mode 100644 index 0000000..931893d --- /dev/null +++ b/src/baseclasses/wxlist.h @@ -0,0 +1,553 @@ +//------------------------------------------------------------------------------ +// File: WXList.h +// +// Desc: DirectShow base classes - defines a non-MFC generic template list +// class. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +/* A generic list of pointers to objects. + No storage management or copying is done on the objects pointed to. + Objectives: avoid using MFC libraries in ndm kernel mode and + provide a really useful list type. + + The class is thread safe in that separate threads may add and + delete items in the list concurrently although the application + must ensure that constructor and destructor access is suitably + synchronised. An application can cause deadlock with operations + which use two lists by simultaneously calling + list1->Operation(list2) and list2->Operation(list1). So don't! + + The names must not conflict with MFC classes as an application + may use both. + */ + +#ifndef __WXLIST__ +#define __WXLIST__ + + /* A POSITION represents (in some fashion that's opaque) a cursor + on the list that can be set to identify any element. NULL is + a valid value and several operations regard NULL as the position + "one step off the end of the list". (In an n element list there + are n+1 places to insert and NULL is that "n+1-th" value). + The POSITION of an element in the list is only invalidated if + that element is deleted. Move operations may mean that what + was a valid POSITION in one list is now a valid POSITION in + a different list. + + Some operations which at first sight are illegal are allowed as + harmless no-ops. For instance RemoveHead is legal on an empty + list and it returns NULL. This allows an atomic way to test if + there is an element there, and if so, get it. The two operations + AddTail and RemoveHead thus implement a MONITOR (See Hoare's paper). + + Single element operations return POSITIONs, non-NULL means it worked. + whole list operations return a BOOL. TRUE means it all worked. + + This definition is the same as the POSITION type for MFCs, so we must + avoid defining it twice. + */ +#ifndef __AFX_H__ +struct __POSITION { int unused; }; +typedef __POSITION* POSITION; +#endif + +const int DEFAULTCACHE = 10; /* Default node object cache size */ + +/* A class representing one node in a list. + Each node knows a pointer to it's adjacent nodes and also a pointer + to the object that it looks after. + All of these pointers can be retrieved or set through member functions. +*/ +class CBaseList +#ifdef DEBUG + : public CBaseObject +#endif +{ + /* Making these classes inherit from CBaseObject does nothing + functionally but it allows us to check there are no memory + leaks in debug builds. + */ + +public: + +#ifdef DEBUG + class CNode : public CBaseObject { +#else + class CNode { +#endif + + CNode *m_pPrev; /* Previous node in the list */ + CNode *m_pNext; /* Next node in the list */ + void *m_pObject; /* Pointer to the object */ + + public: + + /* Constructor - initialise the object's pointers */ + CNode() +#ifdef DEBUG + : CBaseObject(NAME("List node")) +#endif + { + }; + + + /* Return the previous node before this one */ + __out CNode *Prev() const { return m_pPrev; }; + + + /* Return the next node after this one */ + __out CNode *Next() const { return m_pNext; }; + + + /* Set the previous node before this one */ + void SetPrev(__in_opt CNode *p) { m_pPrev = p; }; + + + /* Set the next node after this one */ + void SetNext(__in_opt CNode *p) { m_pNext = p; }; + + + /* Get the pointer to the object for this node */ + __out void *GetData() const { return m_pObject; }; + + + /* Set the pointer to the object for this node */ + void SetData(__in void *p) { m_pObject = p; }; + }; + + class CNodeCache + { + public: + CNodeCache(INT iCacheSize) : m_iCacheSize(iCacheSize), + m_pHead(NULL), + m_iUsed(0) + {}; + ~CNodeCache() { + CNode *pNode = m_pHead; + while (pNode) { + CNode *pCurrent = pNode; + pNode = pNode->Next(); + delete pCurrent; + } + }; + void AddToCache(__inout CNode *pNode) + { + if (m_iUsed < m_iCacheSize) { + pNode->SetNext(m_pHead); + m_pHead = pNode; + m_iUsed++; + } else { + delete pNode; + } + }; + CNode *RemoveFromCache() + { + CNode *pNode = m_pHead; + if (pNode != NULL) { + m_pHead = pNode->Next(); + m_iUsed--; + ASSERT(m_iUsed >= 0); + } else { + ASSERT(m_iUsed == 0); + } + return pNode; + }; + private: + INT m_iCacheSize; + INT m_iUsed; + CNode *m_pHead; + }; + +protected: + + CNode* m_pFirst; /* Pointer to first node in the list */ + CNode* m_pLast; /* Pointer to the last node in the list */ + LONG m_Count; /* Number of nodes currently in the list */ + +private: + + CNodeCache m_Cache; /* Cache of unused node pointers */ + +private: + + /* These override the default copy constructor and assignment + operator for all list classes. They are in the private class + declaration section so that anybody trying to pass a list + object by value will generate a compile time error of + "cannot access the private member function". If these were + not here then the compiler will create default constructors + and assignment operators which when executed first take a + copy of all member variables and then during destruction + delete them all. This must not be done for any heap + allocated data. + */ + CBaseList(const CBaseList &refList); + CBaseList &operator=(const CBaseList &refList); + +public: + + CBaseList(__in_opt LPCTSTR pName, + INT iItems); + + CBaseList(__in_opt LPCTSTR pName); +#ifdef UNICODE + CBaseList(__in_opt LPCSTR pName, + INT iItems); + + CBaseList(__in_opt LPCSTR pName); +#endif + ~CBaseList(); + + /* Remove all the nodes from *this i.e. make the list empty */ + void RemoveAll(); + + + /* Return a cursor which identifies the first element of *this */ + __out_opt POSITION GetHeadPositionI() const; + + + /* Return a cursor which identifies the last element of *this */ + __out_opt POSITION GetTailPositionI() const; + + + /* Return the number of objects in *this */ + int GetCountI() const; + +protected: + /* Return the pointer to the object at rp, + Update rp to the next node in *this + but make it NULL if it was at the end of *this. + This is a wart retained for backwards compatibility. + GetPrev is not implemented. + Use Next, Prev and Get separately. + */ + __out void *GetNextI(__inout POSITION& rp) const; + + + /* Return a pointer to the object at p + Asking for the object at NULL will return NULL harmlessly. + */ + __out_opt void *GetI(__in_opt POSITION p) const; + __out void *GetValidI(__in POSITION p) const; + +public: + /* return the next / prev position in *this + return NULL when going past the end/start. + Next(NULL) is same as GetHeadPosition() + Prev(NULL) is same as GetTailPosition() + An n element list therefore behaves like a n+1 element + cycle with NULL at the start/end. + + !!WARNING!! - This handling of NULL is DIFFERENT from GetNext. + + Some reasons are: + 1. For a list of n items there are n+1 positions to insert + These are conveniently encoded as the n POSITIONs and NULL. + 2. If you are keeping a list sorted (fairly common) and you + search forward for an element to insert before and don't + find it you finish up with NULL as the element before which + to insert. You then want that NULL to be a valid POSITION + so that you can insert before it and you want that insertion + point to mean the (n+1)-th one that doesn't have a POSITION. + (symmetrically if you are working backwards through the list). + 3. It simplifies the algebra which the methods generate. + e.g. AddBefore(p,x) is identical to AddAfter(Prev(p),x) + in ALL cases. All the other arguments probably are reflections + of the algebraic point. + */ + __out_opt POSITION Next(__in_opt POSITION pos) const + { + if (pos == NULL) { + return (POSITION) m_pFirst; + } + CNode *pn = (CNode *) pos; + return (POSITION) pn->Next(); + } //Next + + // See Next + __out_opt POSITION Prev(__in_opt POSITION pos) const + { + if (pos == NULL) { + return (POSITION) m_pLast; + } + CNode *pn = (CNode *) pos; + return (POSITION) pn->Prev(); + } //Prev + + + /* Return the first position in *this which holds the given + pointer. Return NULL if the pointer was not not found. + */ +protected: + __out_opt POSITION FindI( __in void * pObj) const; + + // ??? Should there be (or even should there be only) + // ??? POSITION FindNextAfter(void * pObj, POSITION p) + // ??? And of course FindPrevBefore too. + // ??? List.Find(&Obj) then becomes List.FindNextAfter(&Obj, NULL) + + + /* Remove the first node in *this (deletes the pointer to its + object from the list, does not free the object itself). + Return the pointer to its object. + If *this was already empty it will harmlessly return NULL. + */ + __out_opt void *RemoveHeadI(); + + + /* Remove the last node in *this (deletes the pointer to its + object from the list, does not free the object itself). + Return the pointer to its object. + If *this was already empty it will harmlessly return NULL. + */ + __out_opt void *RemoveTailI(); + + + /* Remove the node identified by p from the list (deletes the pointer + to its object from the list, does not free the object itself). + Asking to Remove the object at NULL will harmlessly return NULL. + Return the pointer to the object removed. + */ + __out_opt void *RemoveI(__in_opt POSITION p); + + /* Add single object *pObj to become a new last element of the list. + Return the new tail position, NULL if it fails. + If you are adding a COM objects, you might want AddRef it first. + Other existing POSITIONs in *this are still valid + */ + __out_opt POSITION AddTailI(__in void * pObj); +public: + + + /* Add all the elements in *pList to the tail of *this. + This duplicates all the nodes in *pList (i.e. duplicates + all its pointers to objects). It does not duplicate the objects. + If you are adding a list of pointers to a COM object into the list + it's a good idea to AddRef them all it when you AddTail it. + Return TRUE if it all worked, FALSE if it didn't. + If it fails some elements may have been added. + Existing POSITIONs in *this are still valid + + If you actually want to MOVE the elements, use MoveToTail instead. + */ + BOOL AddTail(__in CBaseList *pList); + + + /* Mirror images of AddHead: */ + + /* Add single object to become a new first element of the list. + Return the new head position, NULL if it fails. + Existing POSITIONs in *this are still valid + */ +protected: + __out_opt POSITION AddHeadI(__in void * pObj); +public: + + /* Add all the elements in *pList to the head of *this. + Same warnings apply as for AddTail. + Return TRUE if it all worked, FALSE if it didn't. + If it fails some of the objects may have been added. + + If you actually want to MOVE the elements, use MoveToHead instead. + */ + BOOL AddHead(__in CBaseList *pList); + + + /* Add the object *pObj to *this after position p in *this. + AddAfter(NULL,x) adds x to the start - equivalent to AddHead + Return the position of the object added, NULL if it failed. + Existing POSITIONs in *this are undisturbed, including p. + */ +protected: + __out_opt POSITION AddAfterI(__in_opt POSITION p, __in void * pObj); +public: + + /* Add the list *pList to *this after position p in *this + AddAfter(NULL,x) adds x to the start - equivalent to AddHead + Return TRUE if it all worked, FALSE if it didn't. + If it fails, some of the objects may be added + Existing POSITIONs in *this are undisturbed, including p. + */ + BOOL AddAfter(__in_opt POSITION p, __in CBaseList *pList); + + + /* Mirror images: + Add the object *pObj to this-List after position p in *this. + AddBefore(NULL,x) adds x to the end - equivalent to AddTail + Return the position of the new object, NULL if it fails + Existing POSITIONs in *this are undisturbed, including p. + */ + protected: + __out_opt POSITION AddBeforeI(__in_opt POSITION p, __in void * pObj); + public: + + /* Add the list *pList to *this before position p in *this + AddAfter(NULL,x) adds x to the start - equivalent to AddHead + Return TRUE if it all worked, FALSE if it didn't. + If it fails, some of the objects may be added + Existing POSITIONs in *this are undisturbed, including p. + */ + BOOL AddBefore(__in_opt POSITION p, __in CBaseList *pList); + + + /* Note that AddAfter(p,x) is equivalent to AddBefore(Next(p),x) + even in cases where p is NULL or Next(p) is NULL. + Similarly for mirror images etc. + This may make it easier to argue about programs. + */ + + + + /* The following operations do not copy any elements. + They move existing blocks of elements around by switching pointers. + They are fairly efficient for long lists as for short lists. + (Alas, the Count slows things down). + + They split the list into two parts. + One part remains as the original list, the other part + is appended to the second list. There are eight possible + variations: + Split the list {after/before} a given element + keep the {head/tail} portion in the original list + append the rest to the {head/tail} of the new list. + + Since After is strictly equivalent to Before Next + we are not in serious need of the Before/After variants. + That leaves only four. + + If you are processing a list left to right and dumping + the bits that you have processed into another list as + you go, the Tail/Tail variant gives the most natural result. + If you are processing in reverse order, Head/Head is best. + + By using NULL positions and empty lists judiciously either + of the other two can be built up in two operations. + + The definition of NULL (see Next/Prev etc) means that + degenerate cases include + "move all elements to new list" + "Split a list into two lists" + "Concatenate two lists" + (and quite a few no-ops) + + !!WARNING!! The type checking won't buy you much if you get list + positions muddled up - e.g. use a POSITION that's in a different + list and see what a mess you get! + */ + + /* Split *this after position p in *this + Retain as *this the tail portion of the original *this + Add the head portion to the tail end of *pList + Return TRUE if it all worked, FALSE if it didn't. + + e.g. + foo->MoveToTail(foo->GetHeadPosition(), bar); + moves one element from the head of foo to the tail of bar + foo->MoveToTail(NULL, bar); + is a no-op, returns NULL + foo->MoveToTail(foo->GetTailPosition, bar); + concatenates foo onto the end of bar and empties foo. + + A better, except excessively long name might be + MoveElementsFromHeadThroughPositionToOtherTail + */ + BOOL MoveToTail(__in_opt POSITION pos, __in CBaseList *pList); + + + /* Mirror image: + Split *this before position p in *this. + Retain in *this the head portion of the original *this + Add the tail portion to the start (i.e. head) of *pList + + e.g. + foo->MoveToHead(foo->GetTailPosition(), bar); + moves one element from the tail of foo to the head of bar + foo->MoveToHead(NULL, bar); + is a no-op, returns NULL + foo->MoveToHead(foo->GetHeadPosition, bar); + concatenates foo onto the start of bar and empties foo. + */ + BOOL MoveToHead(__in_opt POSITION pos, __in CBaseList *pList); + + + /* Reverse the order of the [pointers to] objects in *this + */ + void Reverse(); + + + /* set cursor to the position of each element of list in turn */ + #define TRAVERSELIST(list, cursor) \ + for ( cursor = (list).GetHeadPosition() \ + ; cursor!=NULL \ + ; cursor = (list).Next(cursor) \ + ) + + + /* set cursor to the position of each element of list in turn + in reverse order + */ + #define REVERSETRAVERSELIST(list, cursor) \ + for ( cursor = (list).GetTailPosition() \ + ; cursor!=NULL \ + ; cursor = (list).Prev(cursor) \ + ) + +}; // end of class declaration + +template class CGenericList : public CBaseList +{ +public: + CGenericList(__in_opt LPCTSTR pName, + INT iItems, + BOOL bLock = TRUE, + BOOL bAlert = FALSE) : + CBaseList(pName, iItems) { + UNREFERENCED_PARAMETER(bAlert); + UNREFERENCED_PARAMETER(bLock); + }; + CGenericList(__in_opt LPCTSTR pName) : + CBaseList(pName) { + }; + + __out_opt POSITION GetHeadPosition() const { return (POSITION)m_pFirst; } + __out_opt POSITION GetTailPosition() const { return (POSITION)m_pLast; } + int GetCount() const { return m_Count; } + + __out OBJECT *GetNext(__inout POSITION& rp) const { return (OBJECT *) GetNextI(rp); } + + __out_opt OBJECT *Get(__in_opt POSITION p) const { return (OBJECT *) GetI(p); } + __out OBJECT *GetValid(__in POSITION p) const { return (OBJECT *) GetValidI(p); } + __out_opt OBJECT *GetHead() const { return Get(GetHeadPosition()); } + + __out_opt OBJECT *RemoveHead() { return (OBJECT *) RemoveHeadI(); } + + __out_opt OBJECT *RemoveTail() { return (OBJECT *) RemoveTailI(); } + + __out_opt OBJECT *Remove(__in_opt POSITION p) { return (OBJECT *) RemoveI(p); } + __out_opt POSITION AddBefore(__in_opt POSITION p, __in OBJECT * pObj) { return AddBeforeI(p, pObj); } + __out_opt POSITION AddAfter(__in_opt POSITION p, __in OBJECT * pObj) { return AddAfterI(p, pObj); } + __out_opt POSITION AddHead(__in OBJECT * pObj) { return AddHeadI(pObj); } + __out_opt POSITION AddTail(__in OBJECT * pObj) { return AddTailI(pObj); } + BOOL AddTail(__in CGenericList *pList) + { return CBaseList::AddTail((CBaseList *) pList); } + BOOL AddHead(__in CGenericList *pList) + { return CBaseList::AddHead((CBaseList *) pList); } + BOOL AddAfter(__in_opt POSITION p, __in CGenericList *pList) + { return CBaseList::AddAfter(p, (CBaseList *) pList); }; + BOOL AddBefore(__in_opt POSITION p, __in CGenericList *pList) + { return CBaseList::AddBefore(p, (CBaseList *) pList); }; + __out_opt POSITION Find( __in OBJECT * pObj) const { return FindI(pObj); } +}; // end of class declaration + + + +/* These define the standard list types */ + +typedef CGenericList CBaseObjectList; +typedef CGenericList CBaseInterfaceList; + +#endif /* __WXLIST__ */ + diff --git a/src/baseclasses/wxutil.cpp b/src/baseclasses/wxutil.cpp new file mode 100644 index 0000000..b93bafd --- /dev/null +++ b/src/baseclasses/wxutil.cpp @@ -0,0 +1,611 @@ +//------------------------------------------------------------------------------ +// File: WXUtil.cpp +// +// Desc: DirectShow base classes - implements helper classes for building +// multimedia filters. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#include +#define STRSAFE_NO_DEPRECATE +#include + + +// --- CAMEvent ----------------------- +CAMEvent::CAMEvent(BOOL fManualReset, __inout_opt HRESULT *phr) +{ + m_hEvent = CreateEvent(NULL, fManualReset, FALSE, NULL); + if (NULL == m_hEvent) { + if (NULL != phr && SUCCEEDED(*phr)) { + *phr = E_OUTOFMEMORY; + } + } +} + +CAMEvent::CAMEvent(__inout_opt HRESULT *phr) +{ + m_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + if (NULL == m_hEvent) { + if (NULL != phr && SUCCEEDED(*phr)) { + *phr = E_OUTOFMEMORY; + } + } +} + +CAMEvent::~CAMEvent() +{ + if (m_hEvent) { + EXECUTE_ASSERT(CloseHandle(m_hEvent)); + } +} + + +// --- CAMMsgEvent ----------------------- +// One routine. The rest is handled in CAMEvent + +CAMMsgEvent::CAMMsgEvent(__inout_opt HRESULT *phr) : CAMEvent(FALSE, phr) +{ +} + +BOOL CAMMsgEvent::WaitMsg(DWORD dwTimeout) +{ + // wait for the event to be signalled, or for the + // timeout (in MS) to expire. allow SENT messages + // to be processed while we wait + DWORD dwWait; + DWORD dwStartTime; + + // set the waiting period. + DWORD dwWaitTime = dwTimeout; + + // the timeout will eventually run down as we iterate + // processing messages. grab the start time so that + // we can calculate elapsed times. + if (dwWaitTime != INFINITE) { + dwStartTime = timeGetTime(); + } + + do { + dwWait = MsgWaitForMultipleObjects(1,&m_hEvent,FALSE, dwWaitTime, QS_SENDMESSAGE); + if (dwWait == WAIT_OBJECT_0 + 1) { + MSG Message; + PeekMessage(&Message,NULL,0,0,PM_NOREMOVE); + + // If we have an explicit length of time to wait calculate + // the next wake up point - which might be now. + // If dwTimeout is INFINITE, it stays INFINITE + if (dwWaitTime != INFINITE) { + + DWORD dwElapsed = timeGetTime()-dwStartTime; + + dwWaitTime = + (dwElapsed >= dwTimeout) + ? 0 // wake up with WAIT_TIMEOUT + : dwTimeout-dwElapsed; + } + } + } while (dwWait == WAIT_OBJECT_0 + 1); + + // return TRUE if we woke on the event handle, + // FALSE if we timed out. + return (dwWait == WAIT_OBJECT_0); +} + +// --- CAMThread ---------------------- + + +CAMThread::CAMThread(__inout_opt HRESULT *phr) + : m_EventSend(TRUE, phr), // must be manual-reset for CheckRequest() + m_EventComplete(FALSE, phr) +{ + m_hThread = NULL; +} + +CAMThread::~CAMThread() { + Close(); +} + + +// when the thread starts, it calls this function. We unwrap the 'this' +//pointer and call ThreadProc. +DWORD WINAPI +CAMThread::InitialThreadProc(__inout LPVOID pv) +{ + HRESULT hrCoInit = CAMThread::CoInitializeHelper(); + if(FAILED(hrCoInit)) { + DbgLog((LOG_ERROR, 1, TEXT("CoInitializeEx failed."))); + } + + CAMThread * pThread = (CAMThread *) pv; + + HRESULT hr = pThread->ThreadProc(); + + if(SUCCEEDED(hrCoInit)) { + CoUninitialize(); + } + + return hr; +} + +BOOL +CAMThread::Create() +{ + DWORD threadid; + + CAutoLock lock(&m_AccessLock); + + if (ThreadExists()) { + return FALSE; + } + + m_hThread = CreateThread( + NULL, + 0, + CAMThread::InitialThreadProc, + this, + 0, + &threadid); + + if (!m_hThread) { + return FALSE; + } + + return TRUE; +} + +DWORD +CAMThread::CallWorker(DWORD dwParam) +{ + // lock access to the worker thread for scope of this object + CAutoLock lock(&m_AccessLock); + + if (!ThreadExists()) { + return (DWORD) E_FAIL; + } + + // set the parameter + m_dwParam = dwParam; + + // signal the worker thread + m_EventSend.Set(); + + // wait for the completion to be signalled + m_EventComplete.Wait(); + + // done - this is the thread's return value + return m_dwReturnVal; +} + +// Wait for a request from the client +DWORD +CAMThread::GetRequest() +{ + m_EventSend.Wait(); + return m_dwParam; +} + +// is there a request? +BOOL +CAMThread::CheckRequest(__out_opt DWORD * pParam) +{ + if (!m_EventSend.Check()) { + return FALSE; + } else { + if (pParam) { + *pParam = m_dwParam; + } + return TRUE; + } +} + +// reply to the request +void +CAMThread::Reply(DWORD dw) +{ + m_dwReturnVal = dw; + + // The request is now complete so CheckRequest should fail from + // now on + // + // This event should be reset BEFORE we signal the client or + // the client may Set it before we reset it and we'll then + // reset it (!) + + m_EventSend.Reset(); + + // Tell the client we're finished + + m_EventComplete.Set(); +} + +HRESULT CAMThread::CoInitializeHelper() +{ + // call CoInitializeEx and tell OLE not to create a window (this + // thread probably won't dispatch messages and will hang on + // broadcast msgs o/w). + // + // If CoInitEx is not available, threads that don't call CoCreate + // aren't affected. Threads that do will have to handle the + // failure. Perhaps we should fall back to CoInitialize and risk + // hanging? + // + + // older versions of ole32.dll don't have CoInitializeEx + + HRESULT hr = E_FAIL; + HINSTANCE hOle = GetModuleHandle(TEXT("ole32.dll")); + if(hOle) + { + typedef HRESULT (STDAPICALLTYPE *PCoInitializeEx)( + LPVOID pvReserved, DWORD dwCoInit); + PCoInitializeEx pCoInitializeEx = + (PCoInitializeEx)(GetProcAddress(hOle, "CoInitializeEx")); + if(pCoInitializeEx) + { + hr = (*pCoInitializeEx)(0, COINIT_DISABLE_OLE1DDE ); + } + } + else + { + // caller must load ole32.dll + DbgBreak("couldn't locate ole32.dll"); + } + + return hr; +} + +// Helper function - convert int to WSTR +void WINAPI IntToWstr(int i, __out_ecount(12) LPWSTR wstr) +{ +#ifdef UNICODE + if (FAILED(StringCchPrintf(wstr, 12, L"%d", i))) { + wstr[0] = 0; + } +#else + TCHAR temp[12]; + if (FAILED(StringCchPrintf(temp, NUMELMS(temp), "%d", i))) { + wstr[0] = 0; + } else { + MultiByteToWideChar(CP_ACP, 0, temp, -1, wstr, 12); + } +#endif +} // IntToWstr + + +#define MEMORY_ALIGNMENT 4 +#define MEMORY_ALIGNMENT_LOG2 2 +#define MEMORY_ALIGNMENT_MASK MEMORY_ALIGNMENT - 1 + +void * __stdcall memmoveInternal(void * dst, const void * src, size_t count) +{ + void * ret = dst; + +#ifdef _X86_ + if (dst <= src || (char *)dst >= ((char *)src + count)) { + + /* + * Non-Overlapping Buffers + * copy from lower addresses to higher addresses + */ + _asm { + mov esi,src + mov edi,dst + mov ecx,count + cld + mov edx,ecx + and edx,MEMORY_ALIGNMENT_MASK + shr ecx,MEMORY_ALIGNMENT_LOG2 + rep movsd + or ecx,edx + jz memmove_done + rep movsb +memmove_done: + } + } + else { + + /* + * Overlapping Buffers + * copy from higher addresses to lower addresses + */ + _asm { + mov esi,src + mov edi,dst + mov ecx,count + std + add esi,ecx + add edi,ecx + dec esi + dec edi + rep movsb + cld + } + } +#else + MoveMemory(dst, src, count); +#endif + + return ret; +} + +HRESULT AMSafeMemMoveOffset( + __in_bcount(dst_size) void * dst, + __in size_t dst_size, + __in DWORD cb_dst_offset, + __in_bcount(src_size) const void * src, + __in size_t src_size, + __in DWORD cb_src_offset, + __in size_t count) +{ + // prevent read overruns + if( count + cb_src_offset < count || // prevent integer overflow + count + cb_src_offset > src_size) // prevent read overrun + { + return E_INVALIDARG; + } + + // prevent write overruns + if( count + cb_dst_offset < count || // prevent integer overflow + count + cb_dst_offset > dst_size) // prevent write overrun + { + return E_INVALIDARG; + } + + memmoveInternal( (BYTE *)dst+cb_dst_offset, (BYTE *)src+cb_src_offset, count); + return S_OK; +} + + +#ifdef DEBUG +/******************************Public*Routine******************************\ +* Debug CCritSec helpers +* +* We provide debug versions of the Constructor, destructor, Lock and Unlock +* routines. The debug code tracks who owns each critical section by +* maintaining a depth count. +* +* History: +* +\**************************************************************************/ + +CCritSec::CCritSec() +{ + InitializeCriticalSection(&m_CritSec); + m_currentOwner = m_lockCount = 0; + m_fTrace = FALSE; +} + +CCritSec::~CCritSec() +{ + DeleteCriticalSection(&m_CritSec); +} + +void CCritSec::Lock() +{ + UINT tracelevel=3; + DWORD us = GetCurrentThreadId(); + DWORD currentOwner = m_currentOwner; + if (currentOwner && (currentOwner != us)) { + // already owned, but not by us + if (m_fTrace) { + DbgLog((LOG_LOCKING, 2, TEXT("Thread %d about to wait for lock %x owned by %d"), + GetCurrentThreadId(), &m_CritSec, currentOwner)); + tracelevel=2; + // if we saw the message about waiting for the critical + // section we ensure we see the message when we get the + // critical section + } + } + EnterCriticalSection(&m_CritSec); + if (0 == m_lockCount++) { + // we now own it for the first time. Set owner information + m_currentOwner = us; + + if (m_fTrace) { + DbgLog((LOG_LOCKING, tracelevel, TEXT("Thread %d now owns lock %x"), m_currentOwner, &m_CritSec)); + } + } +} + +void CCritSec::Unlock() { + if (0 == --m_lockCount) { + // about to be unowned + if (m_fTrace) { + DbgLog((LOG_LOCKING, 3, TEXT("Thread %d releasing lock %x"), m_currentOwner, &m_CritSec)); + } + + m_currentOwner = 0; + } + LeaveCriticalSection(&m_CritSec); +} + +void WINAPI DbgLockTrace(CCritSec * pcCrit, BOOL fTrace) +{ + pcCrit->m_fTrace = fTrace; +} + +BOOL WINAPI CritCheckIn(CCritSec * pcCrit) +{ + return (GetCurrentThreadId() == pcCrit->m_currentOwner); +} + +BOOL WINAPI CritCheckIn(const CCritSec * pcCrit) +{ + return (GetCurrentThreadId() == pcCrit->m_currentOwner); +} + +BOOL WINAPI CritCheckOut(CCritSec * pcCrit) +{ + return (GetCurrentThreadId() != pcCrit->m_currentOwner); +} + +BOOL WINAPI CritCheckOut(const CCritSec * pcCrit) +{ + return (GetCurrentThreadId() != pcCrit->m_currentOwner); +} +#endif + + +STDAPI WriteBSTR(__deref_out BSTR *pstrDest, LPCWSTR szSrc) +{ + *pstrDest = SysAllocString( szSrc ); + if( !(*pstrDest) ) return E_OUTOFMEMORY; + return NOERROR; +} + + +STDAPI FreeBSTR(__deref_in BSTR* pstr) +{ + if( (PVOID)*pstr == NULL ) return S_FALSE; + SysFreeString( *pstr ); + return NOERROR; +} + + +// Return a wide string - allocating memory for it +// Returns: +// S_OK - no error +// E_POINTER - ppszReturn == NULL +// E_OUTOFMEMORY - can't allocate memory for returned string +STDAPI AMGetWideString(LPCWSTR psz, __deref_out LPWSTR *ppszReturn) +{ + CheckPointer(ppszReturn, E_POINTER); + ValidateReadWritePtr(ppszReturn, sizeof(LPWSTR)); + *ppszReturn = NULL; + size_t nameLen; + HRESULT hr = StringCbLengthW(psz, 100000, &nameLen); + if (FAILED(hr)) { + return hr; + } + *ppszReturn = (LPWSTR)CoTaskMemAlloc(nameLen + sizeof(WCHAR)); + if (*ppszReturn == NULL) { + return E_OUTOFMEMORY; + } + CopyMemory(*ppszReturn, psz, nameLen + sizeof(WCHAR)); + return NOERROR; +} + +// Waits for the HANDLE hObject. While waiting messages sent +// to windows on our thread by SendMessage will be processed. +// Using this function to do waits and mutual exclusion +// avoids some deadlocks in objects with windows. +// Return codes are the same as for WaitForSingleObject +DWORD WINAPI WaitDispatchingMessages( + HANDLE hObject, + DWORD dwWait, + HWND hwnd, + UINT uMsg, + HANDLE hEvent) +{ + BOOL bPeeked = FALSE; + DWORD dwResult; + DWORD dwStart; + DWORD dwThreadPriority; + + static UINT uMsgId = 0; + + HANDLE hObjects[2] = { hObject, hEvent }; + if (dwWait != INFINITE && dwWait != 0) { + dwStart = GetTickCount(); + } + for (; ; ) { + DWORD nCount = NULL != hEvent ? 2 : 1; + + // Minimize the chance of actually dispatching any messages + // by seeing if we can lock immediately. + dwResult = WaitForMultipleObjects(nCount, hObjects, FALSE, 0); + if (dwResult < WAIT_OBJECT_0 + nCount) { + break; + } + + DWORD dwTimeOut = dwWait; + if (dwTimeOut > 10) { + dwTimeOut = 10; + } + dwResult = MsgWaitForMultipleObjects( + nCount, + hObjects, + FALSE, + dwTimeOut, + hwnd == NULL ? QS_SENDMESSAGE : + QS_SENDMESSAGE + QS_POSTMESSAGE); + if (dwResult == WAIT_OBJECT_0 + nCount || + dwResult == WAIT_TIMEOUT && dwTimeOut != dwWait) { + MSG msg; + if (hwnd != NULL) { + while (PeekMessage(&msg, hwnd, uMsg, uMsg, PM_REMOVE)) { + DispatchMessage(&msg); + } + } + // Do this anyway - the previous peek doesn't flush out the + // messages + PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE); + + if (dwWait != INFINITE && dwWait != 0) { + DWORD dwNow = GetTickCount(); + + // Working with differences handles wrap-around + DWORD dwDiff = dwNow - dwStart; + if (dwDiff > dwWait) { + dwWait = 0; + } else { + dwWait -= dwDiff; + } + dwStart = dwNow; + } + if (!bPeeked) { + // Raise our priority to prevent our message queue + // building up + dwThreadPriority = GetThreadPriority(GetCurrentThread()); + if (dwThreadPriority < THREAD_PRIORITY_HIGHEST) { + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST); + } + bPeeked = TRUE; + } + } else { + break; + } + } + if (bPeeked) { + SetThreadPriority(GetCurrentThread(), dwThreadPriority); + if (HIWORD(GetQueueStatus(QS_POSTMESSAGE)) & QS_POSTMESSAGE) { + if (uMsgId == 0) { + uMsgId = RegisterWindowMessage(TEXT("AMUnblock")); + } + if (uMsgId != 0) { + MSG msg; + // Remove old ones + while (PeekMessage(&msg, (HWND)-1, uMsgId, uMsgId, PM_REMOVE)) { + } + } + PostThreadMessage(GetCurrentThreadId(), uMsgId, 0, 0); + } + } + return dwResult; +} + +HRESULT AmGetLastErrorToHResult() +{ + DWORD dwLastError = GetLastError(); + if(dwLastError != 0) + { + return HRESULT_FROM_WIN32(dwLastError); + } + else + { + return E_FAIL; + } +} + +IUnknown* QzAtlComPtrAssign(__deref_inout_opt IUnknown** pp, __in_opt IUnknown* lp) +{ + if (lp != NULL) + lp->AddRef(); + if (*pp) + (*pp)->Release(); + *pp = lp; + return lp; +} + diff --git a/src/baseclasses/wxutil.h b/src/baseclasses/wxutil.h new file mode 100644 index 0000000..8397f18 --- /dev/null +++ b/src/baseclasses/wxutil.h @@ -0,0 +1,529 @@ +//------------------------------------------------------------------------------ +// File: WXUtil.h +// +// Desc: DirectShow base classes - defines helper classes and functions for +// building multimedia filters. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#ifndef __WXUTIL__ +#define __WXUTIL__ + +// eliminate spurious "statement has no effect" warnings. +#pragma warning(disable: 4705) + +// wrapper for whatever critical section we have +class CCritSec { + + // make copy constructor and assignment operator inaccessible + + CCritSec(const CCritSec &refCritSec); + CCritSec &operator=(const CCritSec &refCritSec); + + CRITICAL_SECTION m_CritSec; + +#ifdef DEBUG +public: + DWORD m_currentOwner; + DWORD m_lockCount; + BOOL m_fTrace; // Trace this one +public: + CCritSec(); + ~CCritSec(); + void Lock(); + void Unlock(); +#else + +public: + CCritSec() { + InitializeCriticalSection(&m_CritSec); + }; + + ~CCritSec() { + DeleteCriticalSection(&m_CritSec); + }; + + void Lock() { + EnterCriticalSection(&m_CritSec); + }; + + void Unlock() { + LeaveCriticalSection(&m_CritSec); + }; +#endif +}; + +// +// To make deadlocks easier to track it is useful to insert in the +// code an assertion that says whether we own a critical section or +// not. We make the routines that do the checking globals to avoid +// having different numbers of member functions in the debug and +// retail class implementations of CCritSec. In addition we provide +// a routine that allows usage of specific critical sections to be +// traced. This is NOT on by default - there are far too many. +// + +#ifdef DEBUG + BOOL WINAPI CritCheckIn(CCritSec * pcCrit); + BOOL WINAPI CritCheckIn(const CCritSec * pcCrit); + BOOL WINAPI CritCheckOut(CCritSec * pcCrit); + BOOL WINAPI CritCheckOut(const CCritSec * pcCrit); + void WINAPI DbgLockTrace(CCritSec * pcCrit, BOOL fTrace); +#else + #define CritCheckIn(x) TRUE + #define CritCheckOut(x) TRUE + #define DbgLockTrace(pc, fT) +#endif + + +// locks a critical section, and unlocks it automatically +// when the lock goes out of scope +class CAutoLock { + + // make copy constructor and assignment operator inaccessible + + CAutoLock(const CAutoLock &refAutoLock); + CAutoLock &operator=(const CAutoLock &refAutoLock); + +protected: + CCritSec * m_pLock; + +public: + CAutoLock(CCritSec * plock) + { + m_pLock = plock; + m_pLock->Lock(); + }; + + ~CAutoLock() { + m_pLock->Unlock(); + }; +}; + + + +// wrapper for event objects +class CAMEvent +{ + + // make copy constructor and assignment operator inaccessible + + CAMEvent(const CAMEvent &refEvent); + CAMEvent &operator=(const CAMEvent &refEvent); + +protected: + HANDLE m_hEvent; +public: + CAMEvent(BOOL fManualReset = FALSE, __inout_opt HRESULT *phr = NULL); + CAMEvent(__inout_opt HRESULT *phr); + ~CAMEvent(); + + // Cast to HANDLE - we don't support this as an lvalue + operator HANDLE () const { return m_hEvent; }; + + void Set() {EXECUTE_ASSERT(SetEvent(m_hEvent));}; + BOOL Wait(DWORD dwTimeout = INFINITE) { + return (WaitForSingleObject(m_hEvent, dwTimeout) == WAIT_OBJECT_0); + }; + void Reset() { ResetEvent(m_hEvent); }; + BOOL Check() { return Wait(0); }; +}; + + +// wrapper for event objects that do message processing +// This adds ONE method to the CAMEvent object to allow sent +// messages to be processed while waiting + +class CAMMsgEvent : public CAMEvent +{ + +public: + + CAMMsgEvent(__inout_opt HRESULT *phr = NULL); + + // Allow SEND messages to be processed while waiting + BOOL WaitMsg(DWORD dwTimeout = INFINITE); +}; + +// old name supported for the time being +#define CTimeoutEvent CAMEvent + +// support for a worker thread + +#ifdef AM_NOVTABLE +// simple thread class supports creation of worker thread, synchronization +// and communication. Can be derived to simplify parameter passing +class AM_NOVTABLE CAMThread { + + // make copy constructor and assignment operator inaccessible + + CAMThread(const CAMThread &refThread); + CAMThread &operator=(const CAMThread &refThread); + + CAMEvent m_EventSend; + CAMEvent m_EventComplete; + + DWORD m_dwParam; + DWORD m_dwReturnVal; + +protected: + HANDLE m_hThread; + + // thread will run this function on startup + // must be supplied by derived class + virtual DWORD ThreadProc() = 0; + +public: + CAMThread(__inout_opt HRESULT *phr = NULL); + virtual ~CAMThread(); + + CCritSec m_AccessLock; // locks access by client threads + CCritSec m_WorkerLock; // locks access to shared objects + + // thread initially runs this. param is actually 'this'. function + // just gets this and calls ThreadProc + static DWORD WINAPI InitialThreadProc(__inout LPVOID pv); + + // start thread running - error if already running + BOOL Create(); + + // signal the thread, and block for a response + // + DWORD CallWorker(DWORD); + + // accessor thread calls this when done with thread (having told thread + // to exit) + void Close() { + + // Disable warning: Conversion from LONG to PVOID of greater size +#pragma warning(push) +#pragma warning(disable: 4312) + HANDLE hThread = (HANDLE)InterlockedExchangePointer(&m_hThread, 0); +#pragma warning(pop) + + if (hThread) { + WaitForSingleObject(hThread, INFINITE); + CloseHandle(hThread); + } + }; + + // ThreadExists + // Return TRUE if the thread exists. FALSE otherwise + BOOL ThreadExists(void) const + { + if (m_hThread == 0) { + return FALSE; + } else { + return TRUE; + } + } + + // wait for the next request + DWORD GetRequest(); + + // is there a request? + BOOL CheckRequest(__out_opt DWORD * pParam); + + // reply to the request + void Reply(DWORD); + + // If you want to do WaitForMultipleObjects you'll need to include + // this handle in your wait list or you won't be responsive + HANDLE GetRequestHandle() const { return m_EventSend; }; + + // Find out what the request was + DWORD GetRequestParam() const { return m_dwParam; }; + + // call CoInitializeEx (COINIT_DISABLE_OLE1DDE) if + // available. S_FALSE means it's not available. + static HRESULT CoInitializeHelper(); +}; +#endif // AM_NOVTABLE + + +// CQueue +// +// Implements a simple Queue ADT. The queue contains a finite number of +// objects, access to which is controlled by a semaphore. The semaphore +// is created with an initial count (N). Each time an object is added +// a call to WaitForSingleObject is made on the semaphore's handle. When +// this function returns a slot has been reserved in the queue for the new +// object. If no slots are available the function blocks until one becomes +// available. Each time an object is removed from the queue ReleaseSemaphore +// is called on the semaphore's handle, thus freeing a slot in the queue. +// If no objects are present in the queue the function blocks until an +// object has been added. + +#define DEFAULT_QUEUESIZE 2 + +template class CQueue { +private: + HANDLE hSemPut; // Semaphore controlling queue "putting" + HANDLE hSemGet; // Semaphore controlling queue "getting" + CRITICAL_SECTION CritSect; // Thread seriallization + int nMax; // Max objects allowed in queue + int iNextPut; // Array index of next "PutMsg" + int iNextGet; // Array index of next "GetMsg" + T *QueueObjects; // Array of objects (ptr's to void) + + void Initialize(int n) { + iNextPut = iNextGet = 0; + nMax = n; + InitializeCriticalSection(&CritSect); + hSemPut = CreateSemaphore(NULL, n, n, NULL); + hSemGet = CreateSemaphore(NULL, 0, n, NULL); + QueueObjects = new T[n]; + } + + +public: + CQueue(int n) { + Initialize(n); + } + + CQueue() { + Initialize(DEFAULT_QUEUESIZE); + } + + ~CQueue() { + delete [] QueueObjects; + DeleteCriticalSection(&CritSect); + CloseHandle(hSemPut); + CloseHandle(hSemGet); + } + + T GetQueueObject() { + int iSlot; + T Object; + LONG lPrevious; + + // Wait for someone to put something on our queue, returns straight + // away is there is already an object on the queue. + // + WaitForSingleObject(hSemGet, INFINITE); + + EnterCriticalSection(&CritSect); + iSlot = iNextGet++ % nMax; + Object = QueueObjects[iSlot]; + LeaveCriticalSection(&CritSect); + + // Release anyone waiting to put an object onto our queue as there + // is now space available in the queue. + // + ReleaseSemaphore(hSemPut, 1L, &lPrevious); + return Object; + } + + void PutQueueObject(T Object) { + int iSlot; + LONG lPrevious; + + // Wait for someone to get something from our queue, returns straight + // away is there is already an empty slot on the queue. + // + WaitForSingleObject(hSemPut, INFINITE); + + EnterCriticalSection(&CritSect); + iSlot = iNextPut++ % nMax; + QueueObjects[iSlot] = Object; + LeaveCriticalSection(&CritSect); + + // Release anyone waiting to remove an object from our queue as there + // is now an object available to be removed. + // + ReleaseSemaphore(hSemGet, 1L, &lPrevious); + } +}; + +// Ensures that memory is not read past the length source buffer +// and that memory is not written past the length of the dst buffer +// dst - buffer to copy to +// dst_size - total size of destination buffer +// cb_dst_offset - offset, first byte copied to dst+cb_dst_offset +// src - buffer to copy from +// src_size - total size of source buffer +// cb_src_offset - offset, first byte copied from src+cb_src_offset +// count - number of bytes to copy +// +// Returns: +// S_OK - no error +// E_INVALIDARG - values passed would lead to overrun +HRESULT AMSafeMemMoveOffset( + __in_bcount(dst_size) void * dst, + __in size_t dst_size, + __in DWORD cb_dst_offset, + __in_bcount(src_size) const void * src, + __in size_t src_size, + __in DWORD cb_src_offset, + __in size_t count); + +extern "C" +void * __stdcall memmoveInternal(void *, const void *, size_t); + +inline void * __cdecl memchrInternal(const void *buf, int chr, size_t cnt) +{ +#ifdef _X86_ + void *pRet = NULL; + + _asm { + cld // make sure we get the direction right + mov ecx, cnt // num of bytes to scan + mov edi, buf // pointer byte stream + mov eax, chr // byte to scan for + repne scasb // look for the byte in the byte stream + jnz exit_memchr // Z flag set if byte found + dec edi // scasb always increments edi even when it + // finds the required byte + mov pRet, edi +exit_memchr: + } + return pRet; + +#else + while ( cnt && (*(unsigned char *)buf != (unsigned char)chr) ) { + buf = (unsigned char *)buf + 1; + cnt--; + } + + return(cnt ? (void *)buf : NULL); +#endif +} + +void WINAPI IntToWstr(int i, __out_ecount(12) LPWSTR wstr); + +#define WstrToInt(sz) _wtoi(sz) +#define atoiW(sz) _wtoi(sz) +#define atoiA(sz) atoi(sz) + +// These are available to help managing bitmap VIDEOINFOHEADER media structures + +extern const DWORD bits555[3]; +extern const DWORD bits565[3]; +extern const DWORD bits888[3]; + +// These help convert between VIDEOINFOHEADER and BITMAPINFO structures + +STDAPI_(const GUID) GetTrueColorType(const BITMAPINFOHEADER *pbmiHeader); +STDAPI_(const GUID) GetBitmapSubtype(const BITMAPINFOHEADER *pbmiHeader); +STDAPI_(WORD) GetBitCount(const GUID *pSubtype); + +// strmbase.lib implements this for compatibility with people who +// managed to link to this directly. we don't want to advertise it. +// +// STDAPI_(/* T */ CHAR *) GetSubtypeName(const GUID *pSubtype); + +STDAPI_(CHAR *) GetSubtypeNameA(const GUID *pSubtype); +STDAPI_(WCHAR *) GetSubtypeNameW(const GUID *pSubtype); + +#ifdef UNICODE +#define GetSubtypeName GetSubtypeNameW +#else +#define GetSubtypeName GetSubtypeNameA +#endif + +STDAPI_(LONG) GetBitmapFormatSize(const BITMAPINFOHEADER *pHeader); +STDAPI_(DWORD) GetBitmapSize(const BITMAPINFOHEADER *pHeader); + +#ifdef __AMVIDEO__ +STDAPI_(BOOL) ContainsPalette(const VIDEOINFOHEADER *pVideoInfo); +STDAPI_(const RGBQUAD *) GetBitmapPalette(const VIDEOINFOHEADER *pVideoInfo); +#endif // __AMVIDEO__ + + +// Compares two interfaces and returns TRUE if they are on the same object +BOOL WINAPI IsEqualObject(IUnknown *pFirst, IUnknown *pSecond); + +// This is for comparing pins +#define EqualPins(pPin1, pPin2) IsEqualObject(pPin1, pPin2) + + +// Arithmetic helper functions + +// Compute (a * b + rnd) / c +LONGLONG WINAPI llMulDiv(LONGLONG a, LONGLONG b, LONGLONG c, LONGLONG rnd); +LONGLONG WINAPI Int64x32Div32(LONGLONG a, LONG b, LONG c, LONG rnd); + + +// Avoids us dyna-linking to SysAllocString to copy BSTR strings +STDAPI WriteBSTR(__deref_out BSTR * pstrDest, LPCWSTR szSrc); +STDAPI FreeBSTR(__deref_in BSTR* pstr); + +// Return a wide string - allocating memory for it +// Returns: +// S_OK - no error +// E_POINTER - ppszReturn == NULL +// E_OUTOFMEMORY - can't allocate memory for returned string +STDAPI AMGetWideString(LPCWSTR pszString, __deref_out LPWSTR *ppszReturn); + +// Special wait for objects owning windows +DWORD WINAPI WaitDispatchingMessages( + HANDLE hObject, + DWORD dwWait, + HWND hwnd = NULL, + UINT uMsg = 0, + HANDLE hEvent = NULL); + +// HRESULT_FROM_WIN32 converts ERROR_SUCCESS to a success code, but in +// our use of HRESULT_FROM_WIN32, it typically means a function failed +// to call SetLastError(), and we still want a failure code. +// +#define AmHresultFromWin32(x) (MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, x)) + +// call GetLastError and return an HRESULT value that will fail the +// SUCCEEDED() macro. +HRESULT AmGetLastErrorToHResult(void); + +// duplicate of ATL's CComPtr to avoid linker conflicts. + +IUnknown* QzAtlComPtrAssign(__deref_inout_opt IUnknown** pp, __in_opt IUnknown* lp); + +template +class QzCComPtr +{ +public: + typedef T _PtrClass; + QzCComPtr() {p=NULL;} + QzCComPtr(T* lp) + { + if ((p = lp) != NULL) + p->AddRef(); + } + QzCComPtr(const QzCComPtr& lp) + { + if ((p = lp.p) != NULL) + p->AddRef(); + } + ~QzCComPtr() {if (p) p->Release();} + void Release() {if (p) p->Release(); p=NULL;} + operator T*() {return (T*)p;} + T& operator*() {ASSERT(p!=NULL); return *p; } + //The assert on operator& usually indicates a bug. If this is really + //what is needed, however, take the address of the p member explicitly. + T** operator&() { ASSERT(p==NULL); return &p; } + T* operator->() { ASSERT(p!=NULL); return p; } + T* operator=(T* lp){return (T*)QzAtlComPtrAssign((IUnknown**)&p, lp);} + T* operator=(const QzCComPtr& lp) + { + return (T*)QzAtlComPtrAssign((IUnknown**)&p, lp.p); + } +#if _MSC_VER>1020 + bool operator!(){return (p == NULL);} +#else + BOOL operator!(){return (p == NULL) ? TRUE : FALSE;} +#endif + T* p; +}; + +// Helper to replace lstrcpmi +__inline int lstrcmpiLocaleIndependentW(LPCWSTR lpsz1, LPCWSTR lpsz2) +{ + return CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, lpsz1, -1, lpsz2, -1) - CSTR_EQUAL; +} +__inline int lstrcmpiLocaleIndependentA(LPCSTR lpsz1, LPCSTR lpsz2) +{ + return CompareStringA(LOCALE_INVARIANT, NORM_IGNORECASE, lpsz1, -1, lpsz2, -1) - CSTR_EQUAL; +} + +#endif /* __WXUTIL__ */ diff --git a/src/softcam/softcam.cpp b/src/softcam/softcam.cpp new file mode 100644 index 0000000..5b52889 --- /dev/null +++ b/src/softcam/softcam.cpp @@ -0,0 +1,158 @@ +#include "softcam.h" + +#include +#include + +#include +#include + + +// {AEF3B972-5FA5-4647-9571-358EB472BC9E} +DEFINE_GUID(CLSID_DShowSoftcam, +0xaef3b972, 0x5fa5, 0x4647, 0x95, 0x71, 0x35, 0x8e, 0xb4, 0x72, 0xbc, 0x9e); + + +namespace { + +// Setup data + +const wchar_t FILTER_NAME[] = L"DirectShow Softcam"; +const GUID &FILTER_CLASSID = CLSID_DShowSoftcam; + +const AMOVIESETUP_MEDIATYPE s_pin_types[] = +{ + { + &MEDIATYPE_Video, // Major type + &MEDIASUBTYPE_NULL // Minor type + } +}; + +const AMOVIESETUP_PIN s_pins[] = +{ + { + L"Output", // Pin string name + FALSE, // Is it rendered + TRUE, // Is it an output + FALSE, // Can we have none + FALSE, // Can we have many + &CLSID_NULL, // Connects to filter + NULL, // Connects to pin + 1, // Number of types + s_pin_types // Pin details + } +}; + +const REGFILTER2 s_reg_filter2 = +{ + 1, + MERIT_DO_NOT_USE, + 1, + s_pins +}; + +CUnknown * WINAPI CreateSoftcamInstance(LPUNKNOWN lpunk, HRESULT *phr) +{ + return softcam::Softcam::CreateInstance(lpunk, FILTER_CLASSID, phr); +} + +} // namespace + +// COM global table of objects in this dll + +CFactoryTemplate g_Templates[] = +{ + { + FILTER_NAME, + &FILTER_CLASSID, + &CreateSoftcamInstance, + NULL, + nullptr + } +}; +int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]); + + +STDAPI DllRegisterServer() +{ + HRESULT hr = AMovieDllRegisterServer2(TRUE); + if (FAILED(hr)) + { + return hr; + } + IFilterMapper2 *pFM2 = nullptr; + hr = CoCreateInstance( + CLSID_FilterMapper2, nullptr, CLSCTX_INPROC_SERVER, + IID_IFilterMapper2, (void**)&pFM2); + if (FAILED(hr)) + { + return hr; + } + pFM2->UnregisterFilter( + &CLSID_VideoInputDeviceCategory, + 0, + FILTER_CLASSID); + hr = pFM2->RegisterFilter( + FILTER_CLASSID, + FILTER_NAME, + 0, + &CLSID_VideoInputDeviceCategory, + FILTER_NAME, + &s_reg_filter2); + pFM2->Release(); + return hr; +} + +STDAPI DllUnregisterServer() +{ + HRESULT hr = AMovieDllRegisterServer2(FALSE); + if (FAILED(hr)) + { + return hr; + } + IFilterMapper2 *pFM2 = nullptr; + hr = CoCreateInstance( + CLSID_FilterMapper2, nullptr, CLSCTX_INPROC_SERVER, + IID_IFilterMapper2, (void**)&pFM2); + if (FAILED(hr)) + { + return hr; + } + hr = pFM2->UnregisterFilter( + &CLSID_VideoInputDeviceCategory, + 0, + FILTER_CLASSID); + pFM2->Release(); + return hr; +} + +extern "C" BOOL WINAPI DllEntryPoint(HINSTANCE, ULONG, LPVOID); + +BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, LPVOID lpReserved) +{ + return DllEntryPoint((HINSTANCE)(hModule), dwReason, lpReserved); +} + + +// +// Softcam Sender API +// + +extern "C" scCamera scCreateCamera(int width, int height, float framerate) +{ + return softcam::sender::CreateCamera(width, height, framerate); +} + +extern "C" void scDeleteCamera(scCamera camera) +{ + return softcam::sender::DeleteCamera(camera); +} + +extern "C" void scSendFrame(scCamera camera, const void* image_bits) +{ + return softcam::sender::SendFrame(camera, image_bits); +} + +extern "C" bool scWaitForConnection(scCamera camera, float timeout) +{ + return softcam::sender::WaitForConnection(camera, timeout); +} diff --git a/src/softcam/softcam.def b/src/softcam/softcam.def new file mode 100644 index 0000000..1e3c756 --- /dev/null +++ b/src/softcam/softcam.def @@ -0,0 +1,12 @@ +LIBRARY softcam.dll + +EXPORTS + DllMain PRIVATE + DllGetClassObject PRIVATE + DllCanUnloadNow PRIVATE + DllRegisterServer PRIVATE + DllUnregisterServer PRIVATE + scCreateCamera + scDeleteCamera + scSendFrame + scWaitForConnection diff --git a/src/softcam/softcam.h b/src/softcam/softcam.h new file mode 100644 index 0000000..71cbc6c --- /dev/null +++ b/src/softcam/softcam.h @@ -0,0 +1,16 @@ +#pragma once + + +// +// Softcam Sender API +// + +extern "C" +{ + using scCamera = void*; + + scCamera scCreateCamera(int width, int height, float framerate = 60.0f); + void scDeleteCamera(scCamera camera); + void scSendFrame(scCamera camera, const void* image_bits); + bool scWaitForConnection(scCamera camera, float timeout = 0.0f); +} diff --git a/src/softcam/softcam.vcxproj b/src/softcam/softcam.vcxproj new file mode 100644 index 0000000..85c2f0a --- /dev/null +++ b/src/softcam/softcam.vcxproj @@ -0,0 +1,99 @@ + + + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {52968d29-aa5f-41a4-bf1e-4c50f1098373} + softcam + + + + DynamicLibrary + v142 + Unicode + + + DynamicLibrary + v142 + Unicode + + + + + + + + + + + + + + + + + + Level3 + true + WIN32;_CRT_SECURE_NO_WARNINGS;_DEBUG;%(PreprocessorDefinitions) + .;..;..\baseclasses;%(AdditionalIncludeDirectories) + true + MultiThreadedDebugDLL + Disabled + + + Console + true + softcam.def + winmm.lib;msvcrtd.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + Level3 + true + true + WIN32;_CRT_SECURE_NO_WARNINGS;NDEBUG;%(PreprocessorDefinitions) + .;..;..\baseclasses;%(AdditionalIncludeDirectories) + true + + + Console + true + true + true + softcam.def + winmm.lib;msvcrt.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + + + + + + + + {e8a3f6fa-ae1c-4c8e-a0b6-9c8480324eaa} + + + {df9d5a2d-3bed-4d1a-8484-22a654c9ad76} + + + + + + \ No newline at end of file diff --git a/src/softcam/softcam.vcxproj.filters b/src/softcam/softcam.vcxproj.filters new file mode 100644 index 0000000..e2e247a --- /dev/null +++ b/src/softcam/softcam.vcxproj.filters @@ -0,0 +1,23 @@ + + + + + {98ed6f85-a573-4d37-b3d3-0d7eb0cccb65} + + + + + src + + + + + src + + + + + src + + + \ No newline at end of file diff --git a/src/softcamcore/DShowSoftcam.cpp b/src/softcamcore/DShowSoftcam.cpp new file mode 100644 index 0000000..8915c43 --- /dev/null +++ b/src/softcamcore/DShowSoftcam.cpp @@ -0,0 +1,610 @@ +#include "DShowSoftcam.h" + +#include +#include +#include +#include +#include +#include +#include + + +namespace { + +//#define ENABLE_LOG +//#define LOG_FILE_PATH "C:\\my_temp\\debug_log.txt" + +#if defined(ENABLE_LOG) + +FILE* logfile = nullptr; +#define OPEN_LOGFILE() logfile = std::fopen(LOG_FILE_PATH, "a") +#define CLOSE_LOGFILE() [&]{ \ + if (logfile) std::fclose(logfile); \ + logfile = nullptr; \ + }() +#define NOW() []{ \ + auto tp = std::chrono::system_clock::now(); \ + auto t = std::chrono::system_clock::to_time_t(tp); \ + static char buff[128]; \ + std::strftime(buff, sizeof(buff), "%H:%M:%S", std::localtime(&t)); \ + return buff; \ + }() +std::string IID_TO_STR(REFIID riid) +{ + std::string s = + riid == IID_IPin ? "IPin" : + riid == IID_IBaseFilter ? "IBaseFilter" : + riid == IID_IAMovieSetup ? "IAMovieSetup" : + riid == IID_IQualityControl ? "IQualityControl" : + riid == IID_IAMStreamConfig ? "IAMStreamConfig" : + riid == IID_IKsPropertySet ? "IKsPropertySet" : + riid == IID_IAMFilterMiscFlags ? "IAMFilterMiscFlags" : + riid == IID_IPersistPropertyBag ? "IPersistPropertyBag" : + riid == IID_IReferenceClock ? "IReferenceClock" : + riid == IID_IMediaSeeking ? "IMediaSeeking" : + riid == IID_IAMDeviceRemoval ? "IAMDeviceRemoval" : + riid == IID_IAMOpenProgress ? "IAMOpenProgress" : + riid == IID_IMediaPosition ? "IMediaPosition" : + riid == IID_IMediaFilter ? "IMediaFilter" : + riid == IID_IBasicVideo ? "IBasicVideo" : + riid == IID_IBasicAudio ? "IBasicAudio" : + riid == IID_IVideoWindow ? "IVideoWindow" : + riid == IID_IUnknown ? "IUnknown" : + [&]() -> std::string + { + char buff[128]; + snprintf(buff, sizeof(buff), + "{%08lX-%04hX-%04hX-%02hhX%02hhX-%02hhX%02hhX%02hhX%02hhX%02hhX%02hhX}", + riid.Data1, riid.Data2, riid.Data3, + riid.Data4[0], riid.Data4[1], riid.Data4[2], riid.Data4[3], + riid.Data4[4], riid.Data4[5], riid.Data4[6], riid.Data4[7]); + return buff; + }(); + return s; +} +#define LOG(fmt, ...) [&,funcname=__func__]{ \ + if (logfile) std::fprintf(logfile, "%s: %s " fmt, NOW(), funcname, __VA_ARGS__); \ + std::printf("%s: %s " fmt, NOW(), funcname, __VA_ARGS__); \ + }() + +#else // ENABLE_LOG + +#define OPEN_LOGFILE() +#define CLOSE_LOGFILE() +#define LOG(...) + +#endif // ENABLE_LOG + + +AM_MEDIA_TYPE* allocateMediaType() +{ + BYTE *pbFormat = (BYTE*)CoTaskMemAlloc(sizeof(VIDEOINFOHEADER)); + if (!pbFormat) + { + return nullptr; + } + + AM_MEDIA_TYPE *amt = (AM_MEDIA_TYPE*)CoTaskMemAlloc(sizeof(AM_MEDIA_TYPE)); + if (!amt) + { + CoTaskMemFree(pbFormat); + return nullptr; + } + std::memset(amt, 0, sizeof(*amt)); + amt->pbFormat = pbFormat; + return amt; +} + +void fillMediaType(AM_MEDIA_TYPE* amt, int width, int height, float framerate) +{ + BYTE *pbFormat = amt->pbFormat; + + if (framerate <= 0.0f) + { + framerate = 60.0f; + } + const float bit_rate = width * height * 24 * framerate; + const float period = 10 * 1000 * 1000 / framerate; + + VIDEOINFOHEADER* pFormat = (VIDEOINFOHEADER*)pbFormat; + std::memset(pFormat, 0, sizeof(*pFormat)); + pFormat->dwBitRate = (uint32_t)(std::min)(bit_rate, (float)INT_MAX); + pFormat->dwBitErrorRate = 0; + pFormat->AvgTimePerFrame = (LONGLONG)std::round((std::min)(period, (float)LONG_MAX)); + pFormat->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + pFormat->bmiHeader.biWidth = width; + pFormat->bmiHeader.biHeight = height; + pFormat->bmiHeader.biPlanes = 1; + pFormat->bmiHeader.biBitCount = 24; + pFormat->bmiHeader.biCompression = BI_RGB; + pFormat->bmiHeader.biSizeImage = DIBSIZE(pFormat->bmiHeader); + + amt->majortype = MEDIATYPE_Video; + amt->subtype = MEDIASUBTYPE_RGB24; + amt->bFixedSizeSamples = TRUE; + amt->bTemporalCompression = FALSE; + amt->lSampleSize = DIBSIZE(pFormat->bmiHeader); + amt->formattype = FORMAT_VideoInfo; + amt->pUnk = nullptr; + amt->cbFormat = sizeof(VIDEOINFOHEADER); + amt->pbFormat = pbFormat; +} + +AM_MEDIA_TYPE* makeMediaType(int width, int height, float framerate) +{ + AM_MEDIA_TYPE *amt = allocateMediaType(); + if (!amt) + { + return nullptr; + } + fillMediaType(amt, width, height, framerate); + return amt; +} + +} //namespace + +namespace softcam { + + +CUnknown * Softcam::CreateInstance( + LPUNKNOWN lpunk, + const GUID& clsid, + HRESULT* phr) +{ + OPEN_LOGFILE(); + LOG("===== logging started =====\n"); + + return new Softcam(lpunk, clsid, phr); +} + +Softcam::Softcam(LPUNKNOWN lpunk, const GUID& clsid, HRESULT *phr) : + CSource(NAME("DirectShow Softcam"), lpunk, clsid), + m_frame_buffer(FrameBuffer::open()), + m_valid(m_frame_buffer ? true : false), + m_width(m_frame_buffer.width()), + m_height(m_frame_buffer.height()), + m_framerate(m_frame_buffer.framerate()) +{ + CAutoLock lock(&m_cStateLock); + + m_paStreams = (CSourceStream **)new SoftcamStream*[1]; + m_paStreams[0] = new SoftcamStream(phr, this, L"DirectShow Softcam Stream"); +} + + +STDMETHODIMP Softcam::NonDelegatingQueryInterface(REFIID riid, __deref_out void **ppv) +{ + if (riid == IID_IAMStreamConfig) + { + LOG("(Softcam) IAMStreamConfig -> S_OK\n"); + return GetInterface(static_cast(this), ppv); + } + else + { + auto result = CSource::NonDelegatingQueryInterface(riid, ppv); + LOG("(Softcam) %s -> %s\n", IID_TO_STR(riid).c_str(), result ? "(ERROR)" : "S_OK"); + return result; + } +} + +HRESULT +Softcam::SetFormat(AM_MEDIA_TYPE *mt) +{ + if (!mt) + { + LOG("-> E_POINTER\n"); + return E_POINTER; + } + if (!m_valid) + { + LOG("-> E_FAIL\n"); + return E_FAIL; + } + if (mt->majortype != MEDIATYPE_Video || + mt->subtype != MEDIASUBTYPE_RGB24) + { + LOG("-> E_FAIL (invalid media type)\n"); + return E_FAIL; + } + if (mt->formattype == FORMAT_VideoInfo && mt->pbFormat) + { + VIDEOINFOHEADER* pFormat = (VIDEOINFOHEADER*)mt->pbFormat; + if (pFormat->bmiHeader.biWidth != m_width || + pFormat->bmiHeader.biHeight != m_height) + { + LOG("-> E_FAIL (invalid dimension)\n"); + return E_FAIL; + } + if (pFormat->bmiHeader.biBitCount != 24 || + pFormat->bmiHeader.biCompression != BI_RGB) + { + LOG("-> E_FAIL (invalid color format)\n"); + return E_FAIL; + } + } + LOG("-> S_OK\n"); + return S_OK; +} + +HRESULT +Softcam::GetFormat(AM_MEDIA_TYPE **out_pmt) +{ + if (!out_pmt) + { + LOG("-> E_POINTER\n"); + return E_POINTER; + } + if (!m_valid) + { + LOG("-> E_FAIL\n"); + return E_FAIL; + } + AM_MEDIA_TYPE* mt = makeMediaType(m_width, m_height, m_framerate); + if (!mt) + { + LOG("-> E_OUTOFMEMORY\n"); + return E_OUTOFMEMORY; + } + *out_pmt = mt; + LOG("-> S_OK\n"); + return S_OK; +} + +HRESULT +Softcam::GetNumberOfCapabilities(int *out_count, int *out_size) +{ + if (!m_valid) + { + LOG("-> E_FAIL\n"); + return E_FAIL; + } + if (out_count) + { + *out_count = 1; + } + if (out_size) + { + *out_size = sizeof(VIDEO_STREAM_CONFIG_CAPS); + } + LOG("-> S_OK\n"); + return S_OK; +} + +HRESULT +Softcam::GetStreamCaps(int index, AM_MEDIA_TYPE **out_pmt, BYTE *out_scc) +{ + if (!out_pmt || !out_scc) + { + LOG("-> E_POINTER\n"); + return E_POINTER; + } + if (!m_valid) + { + LOG("-> E_FAIL\n"); + return E_FAIL; + } + if (index >= 1) + { + LOG("-> S_FALSE (invalid index)\n"); + return S_FALSE; + } + AM_MEDIA_TYPE *mt = makeMediaType(m_width, m_height, m_framerate); + if (!mt) + { + LOG("-> E_OUTOFMEMORY\n"); + return E_OUTOFMEMORY; + } + *out_pmt = mt; + std::memset(out_scc, 0, sizeof(VIDEO_STREAM_CONFIG_CAPS)); + VIDEOINFOHEADER* format = (VIDEOINFOHEADER*)mt->pbFormat; + VIDEO_STREAM_CONFIG_CAPS* scc = (VIDEO_STREAM_CONFIG_CAPS*)out_scc; + scc->guid = FORMAT_VideoInfo; + scc->InputSize = SIZE{m_width, m_height}; + scc->MinCroppingSize = SIZE{m_width, m_height}; + scc->MaxCroppingSize = SIZE{m_width, m_height}; + scc->CropGranularityX = 1; + scc->CropGranularityY = 1; + scc->CropAlignX = 1; + scc->CropAlignY = 1; + scc->MinOutputSize = SIZE{m_width, m_height}; + scc->MaxOutputSize = SIZE{m_width, m_height}; + scc->OutputGranularityX = 1; + scc->OutputGranularityY = 1; + scc->StretchTapsX = 0; + scc->StretchTapsY = 0; + scc->ShrinkTapsX = 0; + scc->ShrinkTapsY = 0; + scc->MinFrameInterval = format->AvgTimePerFrame; + scc->MaxFrameInterval = format->AvgTimePerFrame; + scc->MinBitsPerSecond = format->dwBitRate; + scc->MaxBitsPerSecond = format->dwBitRate; + LOG("-> S_OK\n"); + return S_OK; +} + + +FrameBuffer* Softcam::getFrameBuffer() +{ + if (!m_valid) + { + return nullptr; + } + + CAutoLock lock(&m_cStateLock); + if (!m_frame_buffer) + { + auto fb = FrameBuffer::open(); + if (fb && + fb.width() == m_width && + fb.height() == m_height) + { + m_frame_buffer = fb; + } + } + if (m_frame_buffer) + { + return &m_frame_buffer; + } + else + { + return nullptr; + } +} + +void +Softcam::releaseFrameBuffer() +{ + CAutoLock lock(&m_cStateLock); + m_frame_buffer.release(); +} + +SoftcamStream::SoftcamStream(HRESULT *phr, + Softcam *pParent, + LPCWSTR pPinName) : + CSourceStream(NAME("DirectShow Softcam Stream"), phr, pParent, pPinName), + m_valid(pParent->valid()), + m_width(pParent->width()), + m_height(pParent->height()) +{ +} + + +SoftcamStream::~SoftcamStream() +{ + LOG("===== logging finished =====\n"); + CLOSE_LOGFILE(); +} + + +STDMETHODIMP SoftcamStream::NonDelegatingQueryInterface(REFIID riid, __deref_out void **ppv) +{ + if (riid == IID_IKsPropertySet ) + { + LOG("(SoftcamStream) IKsPropertySet -> S_OK\n"); + return GetInterface(static_cast(this), ppv); + } + else if(riid == IID_IAMStreamConfig) + { + LOG("(SoftcamStream) IAMStreamConfig -> S_OK\n"); + return GetInterface(static_cast(this), ppv); + } + else + { + auto result = CSourceStream::NonDelegatingQueryInterface(riid, ppv); + LOG("(SoftcamStream) %s -> %s\n", IID_TO_STR(riid).c_str(), result ? "(ERROR)" : "S_OK"); + return result; + } +} + + +HRESULT SoftcamStream::FillBuffer(IMediaSample *pms) +{ + CheckPointer(pms,E_POINTER); + + BYTE *pData; + pms->GetPointer(&pData); + long lDataLen = pms->GetSize(); + ZeroMemory(pData, lDataLen); + { + if (auto fb = getFrameBuffer()) + { + fb->waitForNewFrame(m_frame_counter); + fb->transferToDIB(pData, &m_frame_counter); + + if (!fb->active()) + { + // The sender has deactivated this stream and stopped sending frames. + // We release this stream and will wait a new stream to be available. + getParent()->releaseFrameBuffer(); + } + } + else + { + // Waiting for a new stream. + m_frame_counter = 0; + Timer::sleep(0.100f); + } + + CAutoLock lock(&m_critsec); + CRefTime start = m_sample_time ; + m_sample_time += (LONG)m_interval_time; + pms->SetTime((REFERENCE_TIME*)&start,(REFERENCE_TIME*)&m_sample_time); + } + pms->SetSyncPoint(TRUE); + //LOG("-> NOERROR\n"); + return NOERROR; +} + + +STDMETHODIMP SoftcamStream::Notify(IBaseFilter * pSender, Quality q) +{ + CAutoLock lock(&m_critsec); + m_interval_time = m_interval_time * 1000 / (std::max)(q.Proportion, 1L); + m_interval_time = (std::min)((std::max)(m_interval_time, 10), 1000); + if (q.Late > 0) { + m_sample_time += q.Late; + } + //LOG("-> NOERROR\n"); + return NOERROR; +} + + +HRESULT SoftcamStream::GetMediaType(CMediaType *pmt) +{ + CheckPointer(pmt,E_POINTER); + + if (!m_valid) + { + LOG("-> E_FAIL\n"); + return E_FAIL; + } + + VIDEOINFOHEADER *pvi = (VIDEOINFOHEADER*)pmt->AllocFormatBuffer(sizeof(VIDEOINFOHEADER)); + if (NULL == pvi) + { + LOG("-> E_OUTOFMEMORY\n"); + return E_OUTOFMEMORY; + } + + fillMediaType(pmt, getParent()->width(), getParent()->height(), getParent()->framerate()); + + LOG("-> NOERROR\n"); + return NOERROR; +} + +HRESULT SoftcamStream::DecideBufferSize(IMemAllocator *pAlloc, + ALLOCATOR_PROPERTIES *pProperties) +{ + CheckPointer(pAlloc,E_POINTER); + CheckPointer(pProperties,E_POINTER); + + CAutoLock lock(m_pFilter->pStateLock()); + HRESULT hr = NOERROR; + + VIDEOINFO *pvi = (VIDEOINFO *)m_mt.Format(); + pProperties->cBuffers = 1; + pProperties->cbBuffer = pvi->bmiHeader.biSizeImage; + + ALLOCATOR_PROPERTIES actual; + hr = pAlloc->SetProperties(pProperties, &actual); + if (FAILED(hr)) + { + LOG("-> (FAILED)\n"); + return hr; + } + if (actual.cbBuffer < pProperties->cbBuffer) + { + LOG("-> E_FAIL\n"); + return E_FAIL; + } + LOG("-> NOERROR\n"); + return NOERROR; +} + +HRESULT SoftcamStream::OnThreadCreate() +{ + CAutoLock lock(&m_critsec); + m_sample_time = 0; + m_interval_time = 10; + + LOG("-> NOERROR\n"); + return NOERROR; +} + +HRESULT SoftcamStream::Set(REFGUID guidPropSet, DWORD dwPropID, + LPVOID pInstanceData, DWORD cbInstanceData, + LPVOID pPropData, DWORD cbPropData) +{ + LOG("-> E_NOTIMPL\n"); + return E_NOTIMPL; +} + +HRESULT SoftcamStream::Get(REFGUID guidPropSet, DWORD dwPropID, + LPVOID pInstanceData, DWORD cbInstanceData, + LPVOID pPropData, DWORD cbPropData, DWORD *pcbReturned) +{ + if (guidPropSet != AMPROPSETID_Pin) + { + LOG("-> E_PROP_SET_UNSUPPORTED\n"); + return E_PROP_SET_UNSUPPORTED; + } + if (dwPropID != AMPROPERTY_PIN_CATEGORY) + { + LOG("-> E_PROP_ID_UNSUPPORTED\n"); + return E_PROP_ID_UNSUPPORTED; + } + if (pPropData == NULL && pcbReturned == NULL) + { + LOG("-> E_POINTER\n"); + return E_POINTER; + } + if (pcbReturned) + { + *pcbReturned = sizeof(GUID); + } + if (pPropData) + { + if (cbPropData < sizeof(GUID)) + { + LOG("-> E_UNEXPECTED\n"); + return E_UNEXPECTED; + } + *(GUID*)pPropData = PIN_CATEGORY_CAPTURE; + } + LOG("-> S_OK\n"); + return S_OK; +} + +HRESULT SoftcamStream::QuerySupported(REFGUID guidPropSet, DWORD dwPropID, + DWORD *pTypeSupport) +{ + if (guidPropSet != AMPROPSETID_Pin) + { + LOG("-> E_PROP_SET_UNSUPPORTED\n"); + return E_PROP_SET_UNSUPPORTED; + } + if (dwPropID != AMPROPERTY_PIN_CATEGORY) + { + LOG("-> E_PROP_ID_UNSUPPORTED\n"); + return E_PROP_ID_UNSUPPORTED; + } + if (pTypeSupport) + { + *pTypeSupport = KSPROPERTY_SUPPORT_GET; + } + LOG("-> S_OK\n"); + return S_OK; +} + +HRESULT SoftcamStream::SetFormat(AM_MEDIA_TYPE *mt) +{ + return getParent()->SetFormat(mt); +} + +HRESULT SoftcamStream::GetFormat(AM_MEDIA_TYPE **out_pmt) +{ + return getParent()->GetFormat(out_pmt); +} + +HRESULT SoftcamStream::GetNumberOfCapabilities(int *out_count, int *out_size) +{ + return getParent()->GetNumberOfCapabilities(out_count, out_size); +} + +HRESULT SoftcamStream::GetStreamCaps(int index, AM_MEDIA_TYPE **out_pmt, BYTE *out_scc) +{ + return getParent()->GetStreamCaps(index, out_pmt, out_scc); +} + +FrameBuffer* SoftcamStream::getFrameBuffer() +{ + return getParent()->getFrameBuffer(); +} + +Softcam* SoftcamStream::getParent() +{ + return static_cast(m_pFilter); +} + + +} //namespace softcam diff --git a/src/softcamcore/DShowSoftcam.h b/src/softcamcore/DShowSoftcam.h new file mode 100644 index 0000000..0c5b3f2 --- /dev/null +++ b/src/softcamcore/DShowSoftcam.h @@ -0,0 +1,100 @@ +#pragma once + +#include +#include "FrameBuffer.h" + + +namespace softcam { + + +class Softcam : public CSource, public IAMStreamConfig +{ +public: + static CUnknown* CreateInstance( + LPUNKNOWN lpunk, + const GUID& clsid, + HRESULT* phr); + + // IUnknown Methods + DECLARE_IUNKNOWN + STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, __deref_out void **ppv); + + // IAMStreamConfig + HRESULT SetFormat(AM_MEDIA_TYPE *mt); + HRESULT GetFormat(AM_MEDIA_TYPE **out_pmt); + HRESULT GetNumberOfCapabilities(int *out_count, int *out_size); + HRESULT GetStreamCaps(int index, AM_MEDIA_TYPE **out_pmt, BYTE *out_scc); + + FrameBuffer* getFrameBuffer(); + bool valid() const { return m_valid; } + int width() const { return m_width; } + int height() const { return m_height; } + float framerate() const { return m_framerate; } + void releaseFrameBuffer(); + +private: + FrameBuffer m_frame_buffer; + const bool m_valid; + const int m_width; + const int m_height; + const float m_framerate; + + Softcam(LPUNKNOWN lpunk, const GUID& clsid, HRESULT *phr); +}; + + +class SoftcamStream : public CSourceStream, public IKsPropertySet, public IAMStreamConfig +{ + public: + SoftcamStream(HRESULT *phr, Softcam *pParent, LPCWSTR pPinName); + ~SoftcamStream(); + + // IUnknown + DECLARE_IUNKNOWN + STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, __deref_out void **ppv); + + // CBasePin + STDMETHODIMP Notify(IBaseFilter * pSender, Quality q); + + // CBaseOutputPin + // Ask for buffers of the size appropriate to the agreed media type + HRESULT DecideBufferSize(IMemAllocator *pIMemAlloc, + ALLOCATOR_PROPERTIES *pProperties); + + // CSourceStream + HRESULT FillBuffer(IMediaSample *pms); + HRESULT GetMediaType(CMediaType *pMediaType); + HRESULT OnThreadCreate(void); + + // IKsPropertySet + HRESULT Set(REFGUID guidPropSet, DWORD dwPropID, + LPVOID pInstanceData, DWORD cbInstanceData, + LPVOID pPropData, DWORD cbPropData); + HRESULT Get(REFGUID guidPropSet, DWORD dwPropID, + LPVOID pInstanceData, DWORD cbInstanceData, + LPVOID pPropData, DWORD cbPropData, DWORD *pcbReturned); + HRESULT QuerySupported(REFGUID guidPropSet, DWORD dwPropID, + DWORD *pTypeSupport); + + // IAMStreamConfig + HRESULT SetFormat(AM_MEDIA_TYPE *mt); + HRESULT GetFormat(AM_MEDIA_TYPE **out_pmt); + HRESULT GetNumberOfCapabilities(int *out_count, int *out_size); + HRESULT GetStreamCaps(int index, AM_MEDIA_TYPE **out_pmt, BYTE *out_scc); + +private: + const bool m_valid; + const int m_width; + const int m_height; + uint64_t m_frame_counter = 0; + + CCritSec m_critsec; + CRefTime m_sample_time; + int m_interval_time = 10; + + FrameBuffer* getFrameBuffer(); + Softcam* getParent(); +}; + + +} //namespace softcam diff --git a/src/softcamcore/FrameBuffer.cpp b/src/softcamcore/FrameBuffer.cpp new file mode 100644 index 0000000..3744a58 --- /dev/null +++ b/src/softcamcore/FrameBuffer.cpp @@ -0,0 +1,268 @@ +#include "FrameBuffer.h" + +#include +#include // lock_guard + + +namespace softcam { + + +const char NamedMutexName[] = "DirectShow Softcam/NamedMutex"; +const char SharedMemoryName[] = "DirectShow Softcam/SharedMemory"; + + +struct FrameBuffer::Header +{ + uint32_t m_image_offset; + uint16_t m_width; + uint16_t m_height; + float m_framerate; + uint8_t m_is_active; + uint8_t m_connected; + uint16_t m_unused_field; + uint64_t m_frame_counter; + + uint8_t* imageData(); +}; + + +uint8_t* FrameBuffer::Header::imageData() +{ + uint8_t *image = reinterpret_cast(this) + m_image_offset; + return image; +} + + +FrameBuffer FrameBuffer::create( + int width, + int height, + float framerate) +{ + FrameBuffer fb(NamedMutexName); + + if (!checkDimensions(width, height)) + { + return fb; + } + if (framerate < 0.0f) + { + return fb; + } + + auto shmem_size = calcMemorySize((uint16_t)width, (uint16_t)height); + fb.m_shmem = SharedMemory::create(SharedMemoryName, shmem_size); + if (fb.m_shmem) + { + std::lock_guard lock(fb.m_mutex); + + auto frame = fb.header(); + frame->m_image_offset = sizeof(Header); + frame->m_width = (uint16_t)width; + frame->m_height = (uint16_t)height; + frame->m_framerate = framerate; + frame->m_is_active = 1; + frame->m_connected = 0; + frame->m_frame_counter = 0; + } + return fb; +} + +FrameBuffer FrameBuffer::open() +{ + FrameBuffer fb(NamedMutexName); + + fb.m_shmem = SharedMemory::open(SharedMemoryName); + if (fb.m_shmem) + { + std::lock_guard lock(fb.m_mutex); + + auto size = fb.m_shmem.size(); + if (size < sizeof(Header)) + { + fb.m_shmem = {}; + return fb; + } + auto frame = fb.header(); + if (!checkDimensions(frame->m_width, frame->m_height) || + frame->m_framerate < 0.0f) + { + fb.m_shmem = {}; + return fb; + } + uint32_t image_size = (uint32_t)frame->m_width * (uint32_t)frame->m_height * 3; + if (size <= frame->m_image_offset || + size - frame->m_image_offset < image_size) + { + fb.m_shmem = {}; + return fb; + } + + frame->m_connected = 1; + } + + return fb; +} + +void* FrameBuffer::handle() const +{ + return const_cast(m_shmem.get()); +} + +int FrameBuffer::width() const +{ + std::lock_guard lock(m_mutex); + return m_shmem ? header()->m_width : 0; +} + +int FrameBuffer::height() const +{ + std::lock_guard lock(m_mutex); + return m_shmem ? header()->m_height : 0; +} + +float FrameBuffer::framerate() const +{ + std::lock_guard lock(m_mutex); + return m_shmem ? header()->m_framerate : 0.0f; +} + +uint64_t FrameBuffer::frameCounter() const +{ + std::lock_guard lock(m_mutex); + return m_shmem ? header()->m_frame_counter : 0; +} + +bool FrameBuffer::active() const +{ + std::lock_guard lock(m_mutex); + return m_shmem ? header()->m_is_active : false; +} + +bool FrameBuffer::connected() const +{ + std::lock_guard lock(m_mutex); + return m_shmem ? header()->m_connected : false; +} + +void FrameBuffer::deactivate() +{ + if (!m_shmem) return; + std::lock_guard lock(m_mutex); + header()->m_is_active = 0; +} + +void FrameBuffer::write(const void* image_bits) +{ + if (!m_shmem) return; + std::lock_guard lock(m_mutex); + auto frame = header(); + std::memcpy( + frame->imageData(), + image_bits, + 3 * frame->m_width * frame->m_height); + frame->m_frame_counter += 1; +} + +void FrameBuffer::transferToDIB(void* image_bits, uint64_t* out_frame_counter) +{ + if (!m_shmem) + { + *out_frame_counter = 0; + return; + } + std::lock_guard lock(m_mutex); + + auto frame = header(); + { + int w = frame->m_width; + int h = frame->m_height; + int gap = ((w * 3 + 3) & ~3) - w * 3; + const std::uint8_t* image = frame->imageData(); + std::uint8_t* dest = (std::uint8_t*)image_bits; + for (int y = 0; y < h; y++) + { + const std::uint8_t* src = image + 3 * w * (h - 1 - y); + std::memcpy(dest, src, 3 * w); + dest += 3 * w + gap; + } + *out_frame_counter = frame->m_frame_counter; + + if (!frame->m_is_active) + { + // Darken the image to indicate that the source is inactive + dest = (std::uint8_t*)image_bits; + for (int y = 0; y < h; y++) + { + for (int x = 0; x < w; x++) + { + std::uint8_t b = dest[0]; + std::uint8_t g = dest[1]; + std::uint8_t r = dest[2]; + dest[0] = b / 4; + dest[1] = g / 4; + dest[2] = r / 4; + dest += 3; + } + dest += gap; + } + } + } +} + +void FrameBuffer::waitForNewFrame(uint64_t frame_counter, float time_out) +{ + if (!m_shmem) return; + Timer timer; + while (active() && frameCounter() <= frame_counter) + { + Timer::sleep(0.001f); + if (0.0f < time_out && time_out <= timer.get()) + { + break; + } + } +} + +void FrameBuffer::release() +{ + m_shmem = SharedMemory{}; +} + +FrameBuffer::Header* FrameBuffer::header() +{ + return static_cast(m_shmem.get()); +} + +const FrameBuffer::Header* FrameBuffer::header() const +{ + return static_cast(m_shmem.get()); +} + +bool FrameBuffer::checkDimensions( + int width, + int height) +{ + // We reject big numbers and numbers not multiple of four. + // Too large pictures may exceed the 32-bit limit of DIBitmap and shared memory. + // Sizes which are not multiple of four tend to cause problems in the subsequent + // processes such as color conversion or compression in video applications. + if (width < 1 || width > 16384 || width % 4 != 0 || + height < 1 || height > 16384 || height % 4 != 0) + { + return false; + } + return true; +} + +uint32_t FrameBuffer::calcMemorySize( + uint16_t width, + uint16_t height) +{ + uint32_t header_size = sizeof(Header); + uint32_t image_size = (uint32_t)width * height * 3; + uint32_t shmem_size = header_size + image_size; + return shmem_size; +} + + +} //namespace softcam diff --git a/src/softcamcore/FrameBuffer.h b/src/softcamcore/FrameBuffer.h new file mode 100644 index 0000000..3f9a6a4 --- /dev/null +++ b/src/softcamcore/FrameBuffer.h @@ -0,0 +1,63 @@ +#pragma once + +#include +#include +#include "Misc.h" + + +namespace softcam { + + +using std::uint64_t; +using std::uint32_t; +using std::uint16_t; + + +/// Shared frame buffer between processes (sender and receiver) +class FrameBuffer +{ + public: + static FrameBuffer create( + int width, + int height, + float framerate = 0.0f); + static FrameBuffer open(); + + explicit operator bool() const { return handle() != nullptr; } + + void* handle() const; + int width() const; + int height() const; + float framerate() const; + uint64_t frameCounter() const; + bool active() const; + bool connected() const; + + void deactivate(); + void write(const void* image_bits); + void transferToDIB(void* image_bits, uint64_t* out_frame_counter); + void waitForNewFrame(uint64_t frame_counter, float time_out = 0.5f); + + void release(); + + private: + struct Header; + + mutable NamedMutex m_mutex; + SharedMemory m_shmem; + + explicit FrameBuffer(const char* mutex_name) : m_mutex(mutex_name) {} + + Header* header(); + const Header* header() const; + + static bool checkDimensions( + int width, + int height); + static uint32_t calcMemorySize( + uint16_t width, + uint16_t height); +}; + + +} //namespace softcam diff --git a/src/softcamcore/Misc.cpp b/src/softcamcore/Misc.cpp new file mode 100644 index 0000000..1316999 --- /dev/null +++ b/src/softcamcore/Misc.cpp @@ -0,0 +1,117 @@ +#include "Misc.h" + +#include +#include + + +namespace { + +const auto close = [](void* ptr) { if (ptr) CloseHandle(ptr); }; +const auto unmap = [](void* ptr) { if (ptr) UnmapViewOfFile(ptr); }; + +} //namespace + +namespace softcam { + + +Timer::Timer() +{ + QueryPerformanceCounter((LARGE_INTEGER*)&m_clock); + QueryPerformanceFrequency((LARGE_INTEGER*)&m_frequency); +} + +float Timer::get() +{ + std::uint64_t now; + QueryPerformanceCounter((LARGE_INTEGER*)&now); + float elapsed = (float)((double)int64_t(now - m_clock) / (double)m_frequency); + return elapsed; +} + +void Timer::rewind(float delta) +{ + uint64_t delta_clock = (uint64_t)std::round(delta * (double)m_frequency); + m_clock += delta_clock; +} + +void Timer::reset() +{ + QueryPerformanceCounter((LARGE_INTEGER*)&m_clock); +} + +void Timer::sleep(float seconds) +{ + Sleep((int)(seconds * 1000.0f)); +} + + +NamedMutex::NamedMutex(const char* name) : + m_handle(CreateMutexA(nullptr, false, name), close) +{ +} + +void NamedMutex::lock() +{ + WaitForSingleObject(m_handle.get(), INFINITE); +} + +void NamedMutex::unlock() +{ + ReleaseMutex(m_handle.get()); +} + + +SharedMemory +SharedMemory::create(const char* name, unsigned long size) +{ + return SharedMemory(name, size); +} + +SharedMemory +SharedMemory::open(const char* name) +{ + return SharedMemory(name); +} + +SharedMemory::SharedMemory(const char* name, unsigned long size) : + m_handle( + CreateFileMappingA(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0, size, name), + close), + m_address( + (m_handle && GetLastError() != ERROR_ALREADY_EXISTS) + ? MapViewOfFile(m_handle.get(), FILE_MAP_WRITE, 0, 0, 0) : nullptr, + unmap), + m_size(m_address ? size : (release(), 0)) +{ +} + +SharedMemory::SharedMemory(const char* name) : + m_handle( + OpenFileMappingA(FILE_MAP_WRITE, false, name), + close), + m_address( + m_handle + ? MapViewOfFile(m_handle.get(), FILE_MAP_WRITE, 0, 0, 0) : nullptr, + unmap) +{ + MEMORY_BASIC_INFORMATION meminfo; + if (m_address && 0 < VirtualQuery(m_address.get(), &meminfo, sizeof(meminfo))) + { + m_size = (unsigned long)meminfo.RegionSize; + } + else + { + release(); + } +} + +void +SharedMemory::release() +{ + m_size = 0; + m_address.reset(); + m_handle.reset(); +} + + +} //namespace softcam diff --git a/src/softcamcore/Misc.h b/src/softcamcore/Misc.h new file mode 100644 index 0000000..61e30ea --- /dev/null +++ b/src/softcamcore/Misc.h @@ -0,0 +1,67 @@ +#pragma once + +#include +#include + + +namespace softcam { + + +/// Time Measurement and Sleep +class Timer +{ + public: + Timer(); + + float get(); + void rewind(float delta); + void reset(); + + static void sleep(float seconds); + + private: + std::uint64_t m_clock; + std::uint64_t m_frequency; +}; + + +/// Inter-process Mutex +class NamedMutex +{ + public: + explicit NamedMutex(const char* name); + + void lock(); + void unlock(); + + private: + std::shared_ptr m_handle; +}; + + +/// Inter-process Shared Memory +class SharedMemory +{ + public: + SharedMemory() {} + static SharedMemory create(const char* name, unsigned long size); + static SharedMemory open(const char* name); + + explicit operator bool() const { return get() != nullptr; } + + unsigned long size() const { return m_size; } + void* get() { return m_address.get(); } + const void* get() const { return m_address.get(); } + + private: + std::shared_ptr m_handle; + std::shared_ptr m_address; + unsigned long m_size = 0; + + explicit SharedMemory(const char* name, unsigned long size); + explicit SharedMemory(const char* name); + void release(); +}; + + +} //namespace softcam diff --git a/src/softcamcore/SenderAPI.cpp b/src/softcamcore/SenderAPI.cpp new file mode 100644 index 0000000..eb0a026 --- /dev/null +++ b/src/softcamcore/SenderAPI.cpp @@ -0,0 +1,113 @@ +#include "SenderAPI.h" + +#include +#include + +#include "FrameBuffer.h" +#include "Misc.h" + + +namespace { + +struct Camera +{ + softcam::FrameBuffer m_frame_buffer; + softcam::Timer m_timer; +}; + +std::mutex s_mutex; +std::unique_ptr s_camera; + +} //namespace + + +namespace softcam { +namespace sender { + +CameraHandle CreateCamera(int width, int height, float framerate) +{ + std::lock_guard lock(s_mutex); + + if (!s_camera) + { + if (auto fb = FrameBuffer::create(width, height, framerate)) + { + s_camera.reset(new Camera{ fb, Timer() }); + return fb.handle(); + } + } + return nullptr; +} + +void DeleteCamera(CameraHandle camera) +{ + std::lock_guard lock(s_mutex); + + if (s_camera && s_camera->m_frame_buffer.handle() == camera) + { + s_camera->m_frame_buffer.deactivate(); + s_camera.reset(); + } +} + +void SendFrame(CameraHandle camera, const void* image_bits) +{ + std::lock_guard lock(s_mutex); + + if (s_camera && s_camera->m_frame_buffer.handle() == camera) + { + auto framerate = s_camera->m_frame_buffer.framerate(); + auto frame_counter = s_camera->m_frame_buffer.frameCounter(); + + // To deliver frames in the regular period, we sleep here a bit + // before we deliver the new frame if it's not the time yet. + // If it's already the time, we deliver it immediately and + // let the timer keep running so that if the next frame comes + // in time the constant delivery recovers. + // However if the delay grew too much (greater than 50 percent + // of the period), we reset the timer to avoid continuing + // irregular delivery. + if (0.0f < framerate && 0 < frame_counter) + { + auto ref_delta = 1.0f / framerate; + auto time = s_camera->m_timer.get(); + if (time < ref_delta) + { + Timer::sleep(ref_delta - time); + } + if (time < ref_delta * 1.5f) + { + s_camera->m_timer.rewind(ref_delta); + } + else + { + s_camera->m_timer.reset(); + } + } + + s_camera->m_frame_buffer.write(image_bits); + } +} + +bool WaitForConnection(CameraHandle camera, float timeout) +{ + std::lock_guard lock(s_mutex); + + if (s_camera && s_camera->m_frame_buffer.handle() == camera) + { + Timer timer; + while (!s_camera->m_frame_buffer.connected()) + { + if (0.0f < timeout && timeout <= timer.get()) + { + return false; + } + Timer::sleep(0.001f); + } + return true; + } + return false; +} + +} //namespace sender +} //namespace softcam diff --git a/src/softcamcore/SenderAPI.h b/src/softcamcore/SenderAPI.h new file mode 100644 index 0000000..d357c79 --- /dev/null +++ b/src/softcamcore/SenderAPI.h @@ -0,0 +1,15 @@ +#pragma once + + +namespace softcam { +namespace sender { + +using CameraHandle = void*; + +CameraHandle CreateCamera(int width, int height, float framerate = 60.0f); +void DeleteCamera(CameraHandle camera); +void SendFrame(CameraHandle camera, const void* image_bits); +bool WaitForConnection(CameraHandle camera, float timeout = 0.0f); + +} //namespace sender +} //namespace softcam diff --git a/src/softcamcore/softcamcore.vcxproj b/src/softcamcore/softcamcore.vcxproj new file mode 100644 index 0000000..b58dddf --- /dev/null +++ b/src/softcamcore/softcamcore.vcxproj @@ -0,0 +1,94 @@ + + + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {df9d5a2d-3bed-4d1a-8484-22a654c9ad76} + softcamcore + + + + StaticLibrary + Unicode + v142 + + + StaticLibrary + Unicode + v142 + + + + + + + + + + + + + + + true + + + false + + + + Level3 + true + WIN32;_CRT_SECURE_NO_WARNINGS;_DEBUG;%(PreprocessorDefinitions) + true + .;..;..\baseclasses;%(AdditionalIncludeDirectories) + Disabled + MultiThreadedDebugDLL + + + Console + true + + + + + Level3 + true + true + WIN32;_CRT_SECURE_NO_WARNINGS;NDEBUG;%(PreprocessorDefinitions) + true + .;..;..\baseclasses;%(AdditionalIncludeDirectories) + + + Console + true + true + true + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/softcamcore/softcamcore.vcxproj.filters b/src/softcamcore/softcamcore.vcxproj.filters new file mode 100644 index 0000000..342d33d --- /dev/null +++ b/src/softcamcore/softcamcore.vcxproj.filters @@ -0,0 +1,41 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/tests/core_tests/FrameBufferTest.cpp b/tests/core_tests/FrameBufferTest.cpp new file mode 100644 index 0000000..eadb413 --- /dev/null +++ b/tests/core_tests/FrameBufferTest.cpp @@ -0,0 +1,259 @@ +#include +#include + +#include +#include +#include + + +namespace { +namespace sc = softcam; + + +TEST(FrameBuffer, Basic1) { + auto fb = sc::FrameBuffer::create(320, 240, 60); + + EXPECT_TRUE( fb ); + EXPECT_NE( fb.handle(), nullptr ); + EXPECT_EQ( fb.width(), 320 ); + EXPECT_EQ( fb.height(), 240 ); + EXPECT_EQ( fb.framerate(), 60.0f ); + EXPECT_EQ( fb.active(), true ); + EXPECT_EQ( fb.connected(), false ); +} + +TEST(FrameBuffer, Basic2) { + auto sender = sc::FrameBuffer::create(320, 240, 60); + auto receiver = sc::FrameBuffer::open(); + + EXPECT_TRUE( sender ); + EXPECT_TRUE( receiver ); + EXPECT_NE( sender.handle(), nullptr ); + EXPECT_NE( receiver.handle(), nullptr ); + EXPECT_NE( sender.handle(), receiver.handle() ); + EXPECT_EQ( sender.width(), 320 ); + EXPECT_EQ( sender.height(), 240 ); + EXPECT_EQ( sender.framerate(), 60.0f ); + EXPECT_EQ( sender.active(), true ); + EXPECT_EQ( sender.connected(), true ); + EXPECT_EQ( receiver.width(), 320 ); + EXPECT_EQ( receiver.height(), 240 ); + EXPECT_EQ( receiver.framerate(), 60.0f ); + EXPECT_EQ( receiver.active(), true ); + EXPECT_EQ( receiver.connected(), true ); +} + +TEST(FrameBuffer, FramerateIsOptional) { + auto fb = sc::FrameBuffer::create(320, 240); + + EXPECT_TRUE( fb ); + EXPECT_NE( fb.handle(), nullptr ); + EXPECT_EQ( fb.framerate(), 0.0f ); +} + +TEST(FrameBuffer, InvalidArgs) { + { + auto fb = sc::FrameBuffer::create(0, 240); + EXPECT_FALSE( fb ); + EXPECT_EQ( fb.handle(), nullptr ); + EXPECT_EQ( fb.width(), 0 ); + EXPECT_EQ( fb.height(), 0 ); + EXPECT_EQ( fb.framerate(), 0.0f ); + EXPECT_EQ( fb.active(), false ); + }{ + auto fb = sc::FrameBuffer::create(320, 0); + EXPECT_FALSE( fb ); + EXPECT_EQ( fb.handle(), nullptr ); + }{ + auto fb = sc::FrameBuffer::create(0, 0); + EXPECT_FALSE( fb ); + EXPECT_EQ( fb.handle(), nullptr ); + }{ + auto fb = sc::FrameBuffer::create(-320, 240); + EXPECT_FALSE( fb ); + EXPECT_EQ( fb.handle(), nullptr ); + }{ + auto fb = sc::FrameBuffer::create(320, -240); + EXPECT_FALSE( fb ); + EXPECT_EQ( fb.handle(), nullptr ); + }{ + auto fb = sc::FrameBuffer::create(320, 240, -60); + EXPECT_FALSE( fb ); + EXPECT_EQ( fb.handle(), nullptr ); + } +} + +TEST(FrameBuffer, TooLarge) { + { + auto fb = sc::FrameBuffer::create(32000, 240); + EXPECT_FALSE( fb ); + EXPECT_EQ( fb.handle(), nullptr ); + }{ + auto fb = sc::FrameBuffer::create(320, 24000); + EXPECT_FALSE( fb ); + EXPECT_EQ( fb.handle(), nullptr ); + } +} + +TEST(FrameBuffer, OpenBeforeCreateFails) { + auto receiver = sc::FrameBuffer::open(); + auto sender = sc::FrameBuffer::create(320, 240); + + EXPECT_FALSE( receiver ); + EXPECT_TRUE( sender ); + EXPECT_EQ( receiver.handle(), nullptr ); + EXPECT_NE( sender.handle(), nullptr ); + EXPECT_EQ( sender.connected(), false ); +} + +TEST(FrameBuffer, MultipleCreateFails) { + auto fb1 = sc::FrameBuffer::create(320, 240, 60); + auto fb2 = sc::FrameBuffer::create(320, 240, 60); + + EXPECT_TRUE( fb1 ); + EXPECT_FALSE( fb2 ); + EXPECT_NE( fb1.handle(), nullptr ); + EXPECT_EQ( fb2.handle(), nullptr ); +} + +TEST(FrameBuffer, MultipleOpenSucceeds) { + auto sender = sc::FrameBuffer::create(320, 240, 60); + auto receiver1 = sc::FrameBuffer::open(); + auto receiver2 = sc::FrameBuffer::open(); + + EXPECT_TRUE( sender ); + EXPECT_TRUE( receiver1 ); + EXPECT_TRUE( receiver2 ); + EXPECT_NE( sender.handle(), nullptr ); + EXPECT_NE( receiver1.handle(), nullptr ); + EXPECT_NE( receiver2.handle(), nullptr ); + EXPECT_NE( sender.handle(), receiver1.handle() ); + EXPECT_NE( sender.handle(), receiver2.handle() ); + EXPECT_NE( receiver1.handle(), receiver2.handle() ); + EXPECT_EQ( sender.connected(), true ); +} + +TEST(FrameBuffer, WriteIncreasesFrameCounter) { + auto fb = sc::FrameBuffer::create(320, 240, 60); + EXPECT_EQ( fb.frameCounter(), 0 ); + + std::vector image(320 * 240 * 3, 255); + fb.write(image.data()); + EXPECT_EQ( fb.frameCounter(), 1 ); + + fb.write(image.data()); + EXPECT_EQ( fb.frameCounter(), 2 ); +} + +TEST(FrameBuffer, DeactivateTurnsActiveFlagOff) { + auto sender = sc::FrameBuffer::create(320, 240, 60); + auto receiver = sc::FrameBuffer::open(); + sender.deactivate(); + + EXPECT_TRUE( sender ); + EXPECT_NE( sender.handle(), nullptr ); + EXPECT_EQ( sender.width(), 320 ); + EXPECT_EQ( sender.height(), 240 ); + EXPECT_EQ( sender.framerate(), 60.0f ); + EXPECT_EQ( sender.frameCounter(), 0 ); + EXPECT_EQ( sender.active(), false ); + EXPECT_TRUE( receiver ); + EXPECT_EQ( receiver.active(), false ); +} + +TEST(FrameBuffer, WaitForNewFrameTimesOut) { + const float TIMEOUT_TIME = 0.3f; + auto fb = sc::FrameBuffer::create(320, 240, 60); + + std::atomic pos = 0; + std::thread th([&]{ + fb.waitForNewFrame(0, TIMEOUT_TIME); + pos = 1; + }); + + sc::Timer::sleep(0.01f); + EXPECT_EQ( pos, 0 ); + sc::Timer::sleep(TIMEOUT_TIME + 0.1f); + EXPECT_EQ( pos, 1 ); + th.join(); +} + +TEST(FrameBuffer, WaitForNewFrameStopsAfterNewFrameArrived) { + const float TIMEOUT_TIME = 2.0f; + auto fb = sc::FrameBuffer::create(320, 240, 60); + + auto frame_count = fb.frameCounter(); + std::atomic pos = 0; + std::thread th([&]{ + fb.waitForNewFrame(frame_count, TIMEOUT_TIME); + pos = 1; + }); + + sc::Timer::sleep(0.1f); + EXPECT_EQ( pos, 0 ); + + std::vector image(320 * 240 * 3, 255); + fb.write(image.data()); + sc::Timer::sleep(0.1f); + EXPECT_EQ( pos, 1 ); + th.join(); +} + +TEST(FrameBuffer, WaitForNewFrameStopsIfDeactivated) { + const float TIMEOUT_TIME = 2.0f; + auto fb = sc::FrameBuffer::create(320, 240, 60); + + auto frame_count = fb.frameCounter(); + std::atomic pos = 0; + std::thread th([&]{ + fb.waitForNewFrame(frame_count, TIMEOUT_TIME); + pos = 1; + }); + + sc::Timer::sleep(0.1f); + EXPECT_EQ( pos, 0 ); + + std::vector image(320 * 240 * 3, 255); + fb.deactivate(); + sc::Timer::sleep(0.1f); + EXPECT_EQ( pos, 1 ); + th.join(); +} + +TEST(FrameBuffer, ReleaseInvalidateItself) { + auto fb = sc::FrameBuffer::create(320, 240, 60); + fb.release(); + + EXPECT_FALSE( fb ); + EXPECT_EQ( fb.handle(), nullptr ); + EXPECT_EQ( fb.width(), 0 ); + EXPECT_EQ( fb.height(), 0 ); + EXPECT_EQ( fb.framerate(), 0.0f ); + EXPECT_EQ( fb.frameCounter(), 0 ); + EXPECT_EQ( fb.active(), false ); + EXPECT_EQ( fb.connected(), false ); +} + +TEST(FrameBuffer, ReleaseOnReceiverDisconnects) { + auto sender = sc::FrameBuffer::create(320, 240, 60); + auto receiver = sc::FrameBuffer::open(); + receiver.release(); + + EXPECT_FALSE( receiver ); + EXPECT_EQ( receiver.handle(), nullptr ); + EXPECT_EQ( receiver.width(), 0 ); + EXPECT_EQ( receiver.height(), 0 ); + EXPECT_EQ( receiver.framerate(), 0.0f ); + EXPECT_EQ( receiver.frameCounter(), 0 ); + EXPECT_EQ( receiver.active(), false ); + EXPECT_EQ( receiver.connected(), false ); + + EXPECT_TRUE( sender ); + EXPECT_NE( sender.handle(), nullptr ); + EXPECT_EQ( sender.width(), 320 ); + EXPECT_EQ( sender.height(), 240 ); + EXPECT_EQ( sender.framerate(), 60.0f ); + EXPECT_EQ( sender.active(), true ); +} + +} //namespace diff --git a/tests/core_tests/MiscTest.cpp b/tests/core_tests/MiscTest.cpp new file mode 100644 index 0000000..bf8714d --- /dev/null +++ b/tests/core_tests/MiscTest.cpp @@ -0,0 +1,134 @@ +#include +#include + + +namespace { +namespace sc = softcam; + +const char SHMEM_NAME[] = "shmemtest"; +const char ANOTHER_NAME[] = "shmemtest2"; +const unsigned long SHMEM_SIZE = 888; + + +TEST(Timer, Basic1) { + sc::Timer timer; + auto t1 = timer.get(); + auto t2 = timer.get(); + auto t3 = timer.get(); + + EXPECT_GE( t1, 0.0f ); + EXPECT_GE( t2, t1 ); + EXPECT_GE( t3, t2 ); +} + +TEST(Timer, Sleep) { + sc::Timer timer; + EXPECT_NO_THROW({ sc::Timer::sleep(0.2f); }); + auto t = timer.get(); + + EXPECT_GT( t, 0.0f ); +} + +TEST(Timer, Rewind) { + sc::Timer timer; + EXPECT_NO_THROW({ timer.rewind(0.2f); }); + auto t = timer.get(); + + EXPECT_LT( t, 0.0f ); +} + +TEST(Timer, Reset) { + sc::Timer timer; + timer.sleep(0.2f); + auto t1 = timer.get(); + timer.reset(); + auto t2 = timer.get(); + + EXPECT_GE( t2, 0.0f ); + EXPECT_LT( t2, t1 ); +} + + +TEST(SharedMemory, Basic1) { + auto shmem = sc::SharedMemory::create(SHMEM_NAME, SHMEM_SIZE); + + EXPECT_TRUE( shmem ); + EXPECT_NE( shmem.get(), nullptr ); + EXPECT_GE( shmem.size(), SHMEM_SIZE ); +} + +TEST(SharedMemory, Basic2) { + auto view1 = sc::SharedMemory::create(SHMEM_NAME, SHMEM_SIZE); + auto view2 = sc::SharedMemory::open(SHMEM_NAME); + + EXPECT_TRUE( view1 ); + EXPECT_TRUE( view2 ); + EXPECT_NE( view1.get(), nullptr ); + EXPECT_NE( view2.get(), nullptr ); + EXPECT_NE( view1.get(), view2.get() ); + EXPECT_GE( view1.size(), SHMEM_SIZE ); + EXPECT_GE( view2.size(), SHMEM_SIZE ); +} + +TEST(SharedMemory, InvalidArgs) { + { + auto shmem = sc::SharedMemory::create(SHMEM_NAME, 0); + EXPECT_FALSE( shmem ); + EXPECT_EQ( shmem.get(), nullptr ); + EXPECT_EQ( shmem.size(), 0 ); + }{ + auto shmem = sc::SharedMemory::create("\\", SHMEM_SIZE); + EXPECT_FALSE( shmem ); + EXPECT_EQ( shmem.get(), nullptr ); + EXPECT_EQ( shmem.size(), 0 ); + }{ + auto view1 = sc::SharedMemory::create(SHMEM_NAME, SHMEM_SIZE); + auto view2 = sc::SharedMemory::open(ANOTHER_NAME); + EXPECT_FALSE( view2 ); + EXPECT_EQ( view2.get(), nullptr ); + EXPECT_EQ( view2.size(), 0 ); + } +} + +TEST(SharedMemory, OpenBeforeCreateFails) { + auto view1 = sc::SharedMemory::open(SHMEM_NAME); + auto view2 = sc::SharedMemory::create(SHMEM_NAME, SHMEM_SIZE); + + EXPECT_FALSE( view1 ); + EXPECT_TRUE( view2 ); + EXPECT_EQ( view1.get(), nullptr ); + EXPECT_NE( view2.get(), nullptr ); + EXPECT_EQ( view1.size(), 0 ); + EXPECT_GE( view2.size(), SHMEM_SIZE ); +} + +TEST(SharedMemory, MultipleCreateFails) { + auto view1 = sc::SharedMemory::create(SHMEM_NAME, SHMEM_SIZE); + auto view2 = sc::SharedMemory::create(SHMEM_NAME, SHMEM_SIZE); + + EXPECT_TRUE( view1 ); + EXPECT_FALSE( view2 ); + EXPECT_EQ( view2.get(), nullptr ); + EXPECT_EQ( view2.size(), 0 ); +} + +TEST(SharedMemory, MultipleOpenSucceeds) { + auto view1 = sc::SharedMemory::create(SHMEM_NAME, SHMEM_SIZE); + auto view2 = sc::SharedMemory::open(SHMEM_NAME); + auto view3 = sc::SharedMemory::open(SHMEM_NAME); + + EXPECT_TRUE( view1 ); + EXPECT_TRUE( view2 ); + EXPECT_TRUE( view3 ); + EXPECT_NE( view1.get(), nullptr ); + EXPECT_NE( view2.get(), nullptr ); + EXPECT_NE( view3.get(), nullptr ); + EXPECT_NE( view1.get(), view2.get() ); + EXPECT_NE( view1.get(), view3.get() ); + EXPECT_NE( view2.get(), view3.get() ); + EXPECT_GE( view1.size(), SHMEM_SIZE ); + EXPECT_GE( view2.size(), SHMEM_SIZE ); + EXPECT_GE( view3.size(), SHMEM_SIZE ); +} + +} //namespace diff --git a/tests/core_tests/core_tests.vcxproj b/tests/core_tests/core_tests.vcxproj new file mode 100644 index 0000000..98bd7ec --- /dev/null +++ b/tests/core_tests/core_tests.vcxproj @@ -0,0 +1,87 @@ + + + + + Debug + x64 + + + Release + x64 + + + + {13b2ea6e-e43f-4b6a-9709-b25181cb8115} + Win32Proj + Application + v142 + Unicode + + + + + + + + + + + + + + + + + + + {e8a3f6fa-ae1c-4c8e-a0b6-9c8480324eaa} + + + {df9d5a2d-3bed-4d1a-8484-22a654c9ad76} + + + + + + + + + + Disabled + WIN32;X64;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebugDLL + Level3 + true + true + ..\..\src;%(AdditionalIncludeDirectories) + + + true + Console + + + + + WIN32;X64;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + MultiThreadedDLL + Level3 + true + true + ..\..\src;%(AdditionalIncludeDirectories) + true + + + true + Console + true + true + + + + + このプロジェクトは、このコンピューター上にない NuGet パッケージを参照しています。それらのパッケージをダウンロードするには、[NuGet パッケージの復元] を使用します。詳細については、http://go.microsoft.com/fwlink/?LinkID=322105 を参照してください。見つからないファイルは {0} です。 + + + + \ No newline at end of file diff --git a/tests/core_tests/packages.config b/tests/core_tests/packages.config new file mode 100644 index 0000000..6c6422e --- /dev/null +++ b/tests/core_tests/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/tests/dll_tests/dll_tests.vcxproj b/tests/dll_tests/dll_tests.vcxproj new file mode 100644 index 0000000..3f5b53a --- /dev/null +++ b/tests/dll_tests/dll_tests.vcxproj @@ -0,0 +1,83 @@ + + + + + Debug + x64 + + + Release + x64 + + + + {bf2211be-932a-4e5d-aa41-42304fb243fa} + Win32Proj + Application + v142 + Unicode + + + + + + + + + + + + + + + {52968d29-aa5f-41a4-bf1e-4c50f1098373} + + + + + + + + + + + + + Disabled + X64;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + MultiThreadedDebugDLL + Level3 + true + EnableFastChecks + true + ..\..\src;%(AdditionalIncludeDirectories) + + + true + Console + + + + + X64;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + MultiThreadedDLL + Level3 + true + true + true + ..\..\src;%(AdditionalIncludeDirectories) + + + true + Console + true + true + + + + + このプロジェクトは、このコンピューター上にない NuGet パッケージを参照しています。それらのパッケージをダウンロードするには、[NuGet パッケージの復元] を使用します。詳細については、http://go.microsoft.com/fwlink/?LinkID=322105 を参照してください。見つからないファイルは {0} です。 + + + + \ No newline at end of file diff --git a/tests/dll_tests/packages.config b/tests/dll_tests/packages.config new file mode 100644 index 0000000..6c6422e --- /dev/null +++ b/tests/dll_tests/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/tests/dll_tests/raw_api_test.cpp b/tests/dll_tests/raw_api_test.cpp new file mode 100644 index 0000000..432d3cd --- /dev/null +++ b/tests/dll_tests/raw_api_test.cpp @@ -0,0 +1,96 @@ +#include +#include + + +namespace { + +TEST(scCreateCamera, Basic) { + { + void* cam = scCreateCamera(320, 240, 60); + EXPECT_NE(cam, nullptr); + EXPECT_NO_THROW({ scDeleteCamera(cam); }); + }{ + void* cam = scCreateCamera(1920, 1080, 30); + EXPECT_NE(cam, nullptr); + EXPECT_NO_THROW({ scDeleteCamera(cam); }); + } +} + +TEST(scCreateCamera, MultipleInstancingFails) { + void* cam1 = scCreateCamera(320, 240, 60); + void* cam2 = scCreateCamera(320, 240, 60); + + EXPECT_NE(cam1, nullptr); + EXPECT_EQ(cam2, nullptr); + scDeleteCamera(cam2); + scDeleteCamera(cam1); +} + +TEST(scCreateCamera, FramerateIsOptional) { + void* cam = scCreateCamera(320, 240); + + EXPECT_NE(cam, nullptr); + scDeleteCamera(cam); +} + +TEST(scCreateCamera, ZeroMeansUnlimitedVariableFramerate) { + void* cam = scCreateCamera(320, 240, 0.0f); + + EXPECT_NE(cam, nullptr); + scDeleteCamera(cam); +} + +TEST(scCreateCamera, InvalidArgs) { + void* cam; + cam = scCreateCamera(0, 240, 60); + EXPECT_EQ(cam, nullptr); + scDeleteCamera(cam); + + cam = scCreateCamera(320, 0, 60); + EXPECT_EQ(cam, nullptr); + scDeleteCamera(cam); + + cam = scCreateCamera(0, 0, 60); + EXPECT_EQ(cam, nullptr); + scDeleteCamera(cam); + + cam = scCreateCamera(-320, 240, 60); + EXPECT_EQ(cam, nullptr); + scDeleteCamera(cam); + + cam = scCreateCamera(320, -240, 60); + EXPECT_EQ(cam, nullptr); + scDeleteCamera(cam); + + cam = scCreateCamera(320, 240, -60); + EXPECT_EQ(cam, nullptr); + scDeleteCamera(cam); +} + +TEST(scCreateCamera, TooLarge) { + void* cam; + cam = scCreateCamera(32000, 240, 60); + EXPECT_EQ(cam, nullptr); + scDeleteCamera(cam); + + cam = scCreateCamera(320, 24000, 60); + EXPECT_EQ(cam, nullptr); + scDeleteCamera(cam); +} + +TEST(scDeleteCamera, IgnoresNullPointer) { + scDeleteCamera(nullptr); +} + +TEST(scDeleteCamera, IgnoresDoubleFree) { + void* cam = scCreateCamera(320, 240, 60); + scDeleteCamera(cam); + scDeleteCamera(cam); +} + +TEST(scDeleteCamera, IgnoresInvalidPointer) { + int x = 0; + scDeleteCamera(&x); +} + +} //namespace