Jump to content

  • Log In with Google      Sign In   
  • Create Account


Hooking IDirect3DDevice9::EndScene to capture video: undesired overlay makes its way to the recorded video


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
1 reply to this topic

#1 mmozolin   Members   -  Reputation: 100

Like
0Likes
Like

Posted 23 April 2012 - 04:59 PM

Hello, all

maybe someone out there can throw in an idea to help me out with the following. I am hooking to an arbitrary D3D9 application, in most cases it is a game, and injecting my own code to mofify the behavior of the EndScene function. The backbuffer is copied into a surface which is set to point to a bitmap in a push source DirectShow filter. The filter samples the bitmaps at 25 fps and streams the video into an .avi file. There is a text overlay shown across the game's screen telling the user about a hot key combination that is supposed to stop gameplay capture. This overlay is not supposed to show up in the recoreded video. Everything works fast and beautiful except for one annoying fact. On a random occasion, a frame with the text overaly makes its way into the recoreded video. This is not a really desired artefact, the end user only wants to see his gameplay in the video and nothing else. I'd love to hear if anyone can share ideas of why this is happening. Here is the C# source code for the EndScene hook:

using System;
	using SlimDX;
	using SlimDX.Direct3D9;
	using System.Diagnostics;
	using DirectShowLib;
	using System.Runtime.InteropServices;
	[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
	[System.Security.SuppressUnmanagedCodeSecurity]
	[Guid("EA2829B9-F644-4341-B3CF-82FF92FD7C20")]
	public interface IScene
	{
		unsafe int PassMemoryPtr(void* ptr, bool noheaders);
		int SetBITMAPINFO([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)]byte[] ptr, bool noheaders);
	}
	public class Class1
	{
		object _lockRenderTarget = new object();
		public string StatusMess { get; set; }
		Surface _renderTarget;
		//points to image bytes
		unsafe void* bytesptr;
		//used to store headers AND image bytes
		byte[] bytes;
		IFilterGraph2 ifg2;
		ICaptureGraphBuilder2 icgb2;
		IBaseFilter push;
		IBaseFilter compressor;
		IScene scene;
		IBaseFilter mux;
		IFileSinkFilter sink;
		IMediaControl media;
		bool NeedRunGraphInit = true;
		bool NeedRunGraphClean = true;
		DataStream s;
		DataRectangle dr;
		unsafe int   EndSceneHook(IntPtr devicePtr)
		{
			int hr;
	  
			using (Device device = Device.FromPointer(devicePtr))
				{
			   try
				{
					lock (_lockRenderTarget)
					{
					  
						bool TimeToGrabFrame = false;
						//....
						//logic based on elapsed milliseconds deciding if it is time to grab another frame
		  
						if (TimeToGrabFrame)
						{
									
							//First ensure we have a Surface to render target data into
							//called only once
							if (_renderTarget == null)
							{
	  
								//Create offscreen surface to use as copy of render target data
								using (SwapChain sc = device.GetSwapChain(0))
								{
																	  
									//Att: created in system memory, not in video memory
									_renderTarget = Surface.CreateOffscreenPlain(device, sc.PresentParameters.BackBufferWidth, sc.PresentParameters.BackBufferHeight, sc.PresentParameters.BackBufferFormat, Pool.SystemMemory);
										
								} //end using
							} // end if
						  
							using (Surface backBuffer = device.GetBackBuffer(0, 0))
							{
								//The following line is where main action takes place:
								//Direct3D 9 back buffer gets copied to Surface _renderTarget,
								//which has been connected by references to DirectShow's
								//bitmap capture filter
								//Inside the filter ( code not shown in this listing) the bitmap is periodically
								//scanned to create a streaming video.
								device.GetRenderTargetData(backBuffer, _renderTarget);
							  
								if (NeedRunGraphInit) //ran only once
								{
									ifg2 = (IFilterGraph2)new FilterGraph();
									icgb2 = (ICaptureGraphBuilder2)new CaptureGraphBuilder2();
									icgb2.SetFiltergraph(ifg2);
									push = (IBaseFilter) new PushSourceFilter();
									scene = (IScene)push;
																	  
									//this way we get bitmapfile and bitmapinfo headers
									//ToStream is slow, but run it only once to get the headers
									s = Surface.ToStream(_renderTarget, ImageFileFormat.Bmp);
									bytes = new byte[s.Length];
									  
									s.Read(bytes, 0, (int)s.Length);
									hr = scene.SetBITMAPINFO(bytes, false);
									//we just supplied the header to the PushSource
									//filter. Let's pass reference to
									//just image bytes from LockRectangle
								
									dr = _renderTarget.LockRectangle(LockFlags.None);
									s = dr.Data;
									Result r = _renderTarget.UnlockRectangle();
									bytesptr = s.DataPointer.ToPointer();
									hr = scene.PassMemoryPtr(bytesptr, true);
															  
									//continue building graph
									ifg2.AddFilter(push, "MyPushSource");
										
									icgb2.SetOutputFileName(MediaSubType.Avi, "C:\foo.avi", out mux, out sink);
															  
									icgb2.RenderStream(null, null, push, null, mux);
																											  
									 media = (IMediaControl)ifg2;
									  
									 media.Run();
																	  
									 NeedRunGraphInit = false;
									 NeedRunGraphClean = true;
									 StatusMess = "now capturing, press shift-F11 to stop";
								  
								} //end if
																				
							} // end using backbuffer
						} //  end if Time to grab frame
					  
					} //end lock
				} // end  try
			  
				//It is usually thrown when the user makes game window inactive
				//or it is thrown deliberately when time is up, or the user pressed F11 and
				//it resulted in stopping a capture.
				//If it is thrown for another reason, it is still a good
				//idea to stop recording and free the graph
				catch (Exception ex)
				{
				   //..
				   //stop the DirectShow graph and cleanup
				  
				} // end catch
				//draw overlay
				using (SlimDX.Direct3D9.Font font = new SlimDX.Direct3D9.Font(device, new System.Drawing.Font("Times New Roman", 26.0f, FontStyle.Bold)))
				{
					font.DrawString(null, StatusMess, 20, 100, System.Drawing.Color.FromArgb(255, 255, 255, 255));
				}
		
				return device.EndScene().Code;
	
			} // end using device
  
		} //end EndSceneHook


Sponsor:

#2 mmozolin   Members   -  Reputation: 100

Like
0Likes
Like

Posted 04 May 2012 - 12:51 PM

As it happens sometimes, I finally found an answer to this question myself, if anyone is interested. It turned out that backbuffer in some Direct3D9 apps is not necessarily refreshed each time the hooked EndScene is called. Hence, occasionally the backbuffer with the text overlay from the previous EndScene hook call was passed to the DirectShow source filter responsible for collecting input frames. I started stamping each frame with a tiny overlay with known RGB values and checking if it was still present before passing it to the DirectShow filter. If it was there, the previously cached frame was passed instead of the current one. This approach effectively removed the text overlay from the video recorded in the DirectShow graph.




Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS