Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations derfloh on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Speeding Up FTP / Threads 2

Status
Not open for further replies.

ajbufort

Programmer
Apr 4, 2004
50
US
Hello All,

I am writing an app in Delphi that takes continuous captures of the client's screen and FTPs them to a server. I have one thread taking the captures and another FTPing them as they are taken. The speed of the captures far surpasses the speed ot the uploads (as one might expect). The ratio is currently something like 3 jpeg uploads for every 50 screen captures. I need to improve my upload rates so that another app can pick up the 'stream' and display them one after another in a reasonable time frame (this app is Flash MX, by the way).

These two functions, while not all-inclusive in terms of my code, represent my current approach:

Code:
function ProcessScreenCaptures(Ptr : Pointer) : LongInt; stdcall;
var
  jpeg: TJpegImage;
  fileName: string;
  x: Integer;
begin
  x := 0;

  while (KeepProcessing) do
  begin
    x := x + 1;
    fileName := 'screen_' + IntToStr(x) + '.jpg';
    jpeg := WindowsScreenCapture;
    jpeg.SaveToFile( fileName );
    jpeg.Free;
  end;
end;

function UploadCaptures(Ptr : Pointer) : LongInt; stdcall;
var
  fileName: string;
  x: Integer;
begin

  x := 0;

  while (KeepProcessing) do
  begin
    MakeFTPConnection();
    x := x + 1;
    fileName := 'screen_' + IntToStr(x) + '.jpg';
    UploadFile( fileName );
    CloseFTPConnection();
  end;
end;

I am a newbie to Windows-API programming, and especially threads, so I am struggling to make this work efficiently.

Yet another problem I am having is that mouse movement on the client becomes 'choppy' once the capture app is started. In an attempt to allieviate these problems, I started peppering my code with 'Application.ProcessMessages' lines. This seems very inelegant to me - I know there has to be a better solution out there. It gave me some performance gains, but not nearly what I need. I need the same skill that the pros have, where they can have an app working away in the background at some heavy task, with the user oblivious to the action all the while. That's what I am shooting for here. I thought threads would give me that, but so far they haven't. I know FTP is a notoriously slow protocol, but there's gotta be something out there that can speed up my app in that regard as well.

Can anyone help me on this? Any advice/schooling would be much appreciated!

Thanks,

-Tony
 
Hi Tony,

First up, the best way to make the mouse stay normal when you've got multiple threads happening, is to reduce the priority of your threads. You can do this after you create them and before you start them.

Why are you using FTP? FTP is a slow protocol because of the handshaking involved setting up a connection. If you can design your own server application to receive the screenshots, you will have better success. I've used Indy's TIdTCPServer with good success. The server component supports multiple incoming connections, which may not suit you if you need to keep the screen shots in order. Technically they should be, as each screen should take the same amount of time to send, but they may not when you're dealing with compressed streams. At any rate, you can limit incoming connections to one at a time.

Delphi has it's own thread object TThread set up for you to use. I recommend using that rather than creating and managing your own threads via the WinAPI if that's what you're currently doing.

Obviously, your bandwidth between your client and server (FTP) apps comes into play when your sending lots of information - I'm sure you've calculated for this.
 
Hey Griffyn,

Thanks very much for the informative (and quick) reply! I shall reduce my thread priorities and examine the results. That is obviously a big issue for my app - thanks for the suggestion!

Regarding how I am implementing my threads, I chose to interface directly with the WinAPI, figuring that, at that level, I would be minimizing any overhead associated with 'wrapper-type' APIs. Perhaps that consideration is off-base? What advantages does TThread offer besides ease of use? Just bought a Delphi 7 book, but haven't had time to absorb it all yet! :)

In terms of the FTP issues, bandwidth ought to be a non-factor, in that the client and server are assumed to have high bandwidth (this being an enterprise-level app). Why am I using FTP? Excellent question. :) Because I did not want to have to deal with a server-side component. But if I can't increase performance any other way, than I will have to get into that as well. It really doesn't help my FTPing any that I have to actually close my connection handle every time I make a transaction (upload a file)! I couldn't believe that when I read it in the WinAPI docs. Why should I have to, in effect, relogin every time! They said something about that, if a new handle is acquired fast enough, that the old one is somehow 'remembered', and things go faster. Not sure I understand why they implemented things that way. Sounds messy to me. But I'm still learning, so...

I am having a ball with Delphi, though. I am a Java programmer by trade, but getting my hands dirty in Delphi really makes me want to get into this stuff more.

-Tony
 
Take in account that you are probably using an inordinate amount of CPU in the JPG conversion. Transfering screens in JPG format only can be done at low framerates or with powerhorse machines.

The best way to do what you are doing is to transmit differential information in BMP format (TBitmap), as BMPs can be analized very quickly.

Let me see if I can explain how to make it:

1) Get your first screen (BMP) and transmit it (it will be long, but it is only the first one).

3) Prepare a new BMP (TBitmap) with the due size and fill it with white color. Lets name it Delta BMP.

3) Get your second screen and compare with the first one using line scanning (TBitmap.ScanLine; do NOT use TBitmap.Canvas.Pixels[x,y]). Store the BYTES changed between screenshots in the Delta BMP in the due positions.

4) Compress the Delta BMP with RLE and send it over the line. If you don't have a truckload of changes between screenshots the RLE Delta BMP will be thiny.

5) In the receiver end compare the Delta BMP with the last state and "stamp" the bytes which differs (using ScanLine again).

The better way is establishing a "thread pipeline". The first thread takes the shots, the 2nd thread creates the Delta BMP, the 3th thread makes the RLE and the 4th thread sends the info.

With this approach you are in some way trading upload time for CPU time, as you are (may be) transmitting more data but fastening the encoding process. JPG generation take a lot of time and math and the Delphi JPG components are far for optimized. But for quickly changing screenshots it will not work, as the RLE can actually *expand* the data.

The process can be optimized a little in the receiver end if a) you can define a color as "never used" and b) you can use a graphic format with one byte/color or one word/color relation.

And use the Delphi TThread without remorse. You are paying very little or nothing in efficience (and getting a very good OOPing).

HTH.
buho (A).



 
Disclaimer:

Last time I've worked in image transmission a "powerhorse machine" was a Pentium MMX 400 MHz. I started using paralel piping, having one thread getting the shots, *five* threads making the JPG conversion and another thread sending the info.

The results were a modest frame rate (20 FPS) and an insanelly high CPU load. And I think the JPG code I used was better than the Delphi JPG components.

So I resorted to Delta BMPs and got way better performance (the needed 50 FPS with very less CPU load).

May be today, with multi-GHz machines the JPG approach is feasible.

buho (A).
 
Thanks buho, I appreciate the image info very much! I am looking for all ways in which I can make things more efficient, and the means by which I was taking captures and dealing with them was on my list of things to improve. You wouldn't happen to be able to point me in the direction of some sample code which does approximately what you are talking about, would you? I am great at researching programming questions via Google, but I have a deadline to meet. Any shortcuts I can grab along the way would be very helpful. If not, don't worry - your tip alone was excellent!

-Tony
 
Nope, no idea about sample code. I've checked the work I refered to and found I can't disclose the source code until 2010 (I'm under a legal agreement with the client who paid for it).

But I can add some clues, as by my country laws intelectual property agreements don't cover ideas or scientific communications.

1) The most difficult part is the RLE compression if you have not sources available. ZLW compression will work very well size-wise (and you have ZLIB in the Delphi CD and lots of freeware components laying around in the 'net) but it is more expensive in CPU time.

2) The line scanning is very easy. TBitmap.ScanLine(n) returns a pointer to the nth line and you can index it as a byte array (doing some type casting). Don't let the definition trick you: the property (ScanLine) is read/only but the memory space pointed by the pointer is read/write.

3) The line width (in bytes) depends on the TBitmap graphic format and width, you can set the format with the TBitmap.PixelFormat property. The due way is setting the TBitmap height and width FIRST and the PixelFormat next. Warning: if the graphic hardware can not support the format you are setting, the property will revert to something else (pfCustom if memory serves).

4) The whole trick is not thinking in pixels but in bytes. For a delta comparisson you need to know nothing about pixels; you are comparing two memory spaces. Same for the delta "stamping" in the receiver end.

5) Do not waste your time writing assembler. Two years ago I wrote some alpha blend routines. As I was pressed for run time speed I made a pure Delphi version and an assembler version. With a 1 GHz CPU machine the assembler one was quickest but the differences was not so impressive. The Delphi compiler have very good optimization algoritms and with GHz clocked machines the differences tend to vanish (what was not true in the 30 MHz machines age).

Please, ask if you have any doubt. Sorry I can't disclose my sources (what is a very common issue due to the nature of my works).

buho (A).
 
Hey buho,

I sure do appreciate the tips - thanks!!

I have a very basic thread question. I am having trouble transmitting a flag to my threads to tell them to stop execution. I am trying to implement this like this:

Code:
function ProcessScreenCaptures(Parameter : Pointer) : Integer; stdcall;
var
  jpeg: TJpegImage;
  x: Integer;
  fileName: string;
begin
  Result := 0;

  x := 0;
  Form1.Label1.Caption := BoolToStr(keepProcessing,true);   
  Form1.Label1.Refresh;
  
  while (keepProcessing) do
  begin
    x := x + 1;
    fileName := 'screen_' + IntToStr(x) + '.jpg';
    jpeg := WindowsScreenCapture;
    jpeg.SaveToFile( fileName );
    jpeg.Free;
  end;
  EndThread(0);
end;

procedure TForm1.StopCaptures(Sender: TObject);
begin
  keepProcessing := False;
end;

I have a 'Stop Captures' button on my form, which I want to use to set a variable, 'keepProcessing', to false. Since this is a global var, I figured that my threads could read this var and act to close themselves, like what I am trying to do in 'ProcessScreenCaptures' above. I want the while loop to see the 'False' value and stop executing. But when I test it by printing out the value in a caption I have, it still says 'True', and the screen captures just keep going on. What am I missing here?

-Tony
 
For start, you are setting the label out of the thread loop.

The thread starts running (started in some procedure you are not showing here), it sets the label caption and enters the loop. When you (as the user) get a chance to click the "Stop" button the thread is looping, and the loop have not code to actualize the caption.

Why it is not stopping I can't say.

BTW, you are making a synch mistake with the label, see note below.

Try this instead:

Code:
interface
type
  TMyBee = class(TThread)
  protected 
    procedure Execute;  override;
  end;

type
  TForm1 = class(TForm)
    // Blah blah blah
  private
    { Private declarations }
    FBee : TMyBee;
  end;
 
implementation
procedure TMyBee.Execute;
var
  // Your vars here
begin
  while not Terminated do
    begin
      // Your working code here
    end;
end;

procedure TForm1.FormCreate(Sender : TObject);
begin
  // Any needed inizialization code
  FBee := TMyBee.Create(True);    // Create suspended
  FBee.Resume;                    // Start running it
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  FBee.Free;
  // Any needed destruction code
end;

procedure TForm1.StopCaptures(Sender: TObject);
begin
  FBee.Suspend;
end;

procedure TForm1.RestartCaptures(Sender: TObject);
begin
  FBee.Resume;
end;
[code]
     
Synch note: you are playing risky in your code. The TLabel is running in the main VCL thread context and you are accesing it from your worker thread context without proper synchronization. This type of thing can open a big can of bugs.

HTH.
buho (A).
 
The Delphi help file has a pretty good primer on using threads via it's TThread object.

For very detailed help using threads to avoid problems such sync, try here
 
Hey Guys,

I sure have appreciated your help thusfar. I appreciate being able to pick your brains, and once again, I thank you.

Would you know if this would be a correct approach to multithreading:

(1) Establish a thread containing a loop (exitable upon setting a flag or some such idea)

(2) Within this thread, have an array of thread handles and ids, such that, in the loop, you can dynamically spawn threads in this approximate manner:

thread[x] = BeginThread( ...params..., threadID[x] );

Is there anything inherent in this approach which makes it flawed? Is there a better way to go about multithreadnig? I am trying to spawn a thread for every file I have to FTP to a server, in order to implement parallel uploading!

Thanks,

-Tony
 
I've used TObjectList to hold multiple thread objects in the past. Unless you have a specific reason, there's no need to establish a separate thread just to spawn threads.

This way the TObjectList can tell you how many threads you've got running, and can handle stepping through them if you need to kill them all off.
 
Hey Guys,

After much research, I finally found the apparent cause of my choppy-mouse-while-my-app-is-running woes. Apparently, hardware acceleration set to full on windows causes problems for screen capture software! Shutting it off completely eliminated my mouse problems!!! Now all I need is a way for me to programatically turn it off and then on again at the end of my operations. Wouldn't happen to know how to do that, would ya? :)

I implemented my threads using TThreads, and things are cool, though I still haven't implemented multithreading for the file uploads yet. Griffyn - how exactly were you using TObjects to handle the threads?

If you guys are curious about the whole hardware acceleration deal, you can visit this link:


I'm glad it's not me!! I thought there was something I was missing regarding threads.

-Tony
 
Can you elaborate a little on:

a) what thing are you capturing and

b) how are you capturing it?

buho (A).

 
Buho,

I am capturing the entire screen, and doing so with the following code:

Code:
procedure ScreenShot( x : integer; y : integer; Width : integer; Height : integer; bm : TBitMap );
var
  dc: HDC; lpPal : PLOGPALETTE;
begin
{test width and height}
  if ((Width = 0) OR (Height = 0)) then exit;
  bm.Width := Width;
  bm.Height := Height;
{get the screen dc}
  dc := GetDc(0);
  if (dc = 0) then exit;
{do we have a palette device?}
  if (GetDeviceCaps(dc, RASTERCAPS) AND RC_PALETTE = RC_PALETTE) then
  begin
    {allocate memory for a logical palette}
    GetMem(lpPal, sizeof(TLOGPALETTE) + (255 * sizeof(TPALETTEENTRY)));
    {zero it out to be neat}
    // FillChar(lpPal^, sizeof(TLOGPALETTE) + (255 * sizeof(TPALETTEENTRY)), #0);
    {fill in the palette version}
    lpPal^.palVersion := $300;
    {grab the system palette entries}
    lpPal^.palNumEntries :=GetSystemPaletteEntries(dc,0,256,lpPal^.palPalEntry);
    if (lpPal^.PalNumEntries <> 0) then
    begin
      {create the palette}
      bm.Palette := CreatePalette(lpPal^);
    end;
    FreeMem(lpPal, sizeof(TLOGPALETTE) + (255 * sizeof(TPALETTEENTRY)));
  end;
  {copy from the screen to the bitmap}
  BitBlt(bm.Canvas.Handle,0,0,Width,Height,Dc,x,y,SRCCOPY);
  {release the screen dc}
  ReleaseDc(0, dc);
end;

-Tony
 
You really like to do things the hard way, man! :)

Code:
procedure TSnapper.Snap(User : AnsiString);
  var
    HDSK    : HDC;
    BMP     : TBitmap;
  begin
    snLock.Enter;
    HDSK := GetDC(GetDesktopWindow);
    {The BMP is not destroyed here but in TStore.}
    BMP := TBitmap.Create;      
    try
      {Get the image.}
      BMP.Width := Screen.Width;
      BMP.Height := Screen.Height;
      BitBlt(BMP.Canvas.Handle, 
             0, 0, BMP.Width, BMP.Height, 
             HDSK, 0, 0, 
             SrcCopy);
      {Send image to store and signal the store event.}
      snStore.Store(BMP, User, Now);
      snStore.Signal;
    finally
      {Clear the house.}
      ReleaseDC(GetDesktopWindow, HDSK);
      snLock.Leave;
    end;
  end;

The code above is for a "cop" service I've wrote some time ago (no legal issues here). It takes a desktop snap.

The code is multithread, you can se the locks at work (actually TSnapper is a thread and TStore is another thread).

No problems with accelerators or whatever. It is basically the code you posted here in "Delphi way".

buho (A).
 
I've used TObjectList to manage many threads. Essentially, create your thread (assign a temporary thread var to it if you need to work on it before you Execute it), and call the TObjectList.Add method to add the thread to your list. Then throw your temporary thread var away. The TObjectList now contains a pointer to the thread object - and you can search it if you need to access it again from your main VCL thread.

Make the TObjectList global, and when your application is closing, it's a simple matter of stepping through all (if any) of the TObjectList's thread pointers to tell your threads to Terminate. Your OnClose method can sit in an infinite loop waiting for this to happen, and continually looping through all the TObjectList's pointers until all the threads have terminated.
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top