streaming and TStream problems

This is the forum for miscellaneous technical/programming questions.

Moderator: 2ffat

streaming and TStream problems

Postby mark_c » Tue Sep 04, 2018 3:23 am

I'm trying to study streaming video and I started building this simple example but it does not work.
My idea in the future is to load a certain number of jpeg images into memory and display them with a certain speed.

The code then first loads the image into memory and then displays it.

thank you

Code: Select all
void __fastcall TForm1::Button7Click(TObject *Sender)
{
        FILE *fp;
        int ch;

        AnsiString buf;

        fp=fopen("img\\0001.jpg","rb");

        do
        {
           ch = fgetc(fp);
                buf+=(char)ch;

        }
        while(ch != EOF);

        fclose(fp);

      TStream *picture = new TMemoryStream;
      TJPEGImage *myjpg = new TJPEGImage;

      const char *pBuffer = buf.c_str();

      picture=(TStream *)pBuffer;

      picture->Position=NULL;
      myjpg->LoadFromStream(picture);
      Image1->Canvas->Draw(0,0,myjpg);

}
mark_c
BCBJ Guru
BCBJ Guru
 
Posts: 129
Joined: Thu Jun 21, 2012 1:13 am

Re: streaming and TStream problems

Postby mark_c » Tue Sep 04, 2018 7:41 am

after many tests I wrote this code but I do not know if it is efficient

Code: Select all
void __fastcall TForm1::Button7Click(TObject *Sender)
{
        FILE *fp;
        int ch;

        //AnsiString buf;
        BYTE *buf = new BYTE[50000];
        int i=0;
        fp=fopen("img\\0001.jpg","rb");

        do
        {
           ch = fgetc(fp);
                buf[i++]=(char)ch;

        }
        while(ch != EOF);

        fclose(fp);

        Label5->Caption=strlen(buf);

   TStream *picture = new TMemoryStream;
   TJPEGImage *myjpg = new TJPEGImage;

        picture->Write(buf, 50000);


   picture->Position=NULL;
   myjpg->LoadFromStream(picture);
   Image1->Canvas->Draw(0,0,myjpg);
}
mark_c
BCBJ Guru
BCBJ Guru
 
Posts: 129
Joined: Thu Jun 21, 2012 1:13 am

Re: streaming and TStream problems

Postby rlebeau » Tue Sep 04, 2018 11:10 am

mark_c wrote:I'm trying to study streaming video and I started building this simple example but it does not work.


That is because the code you have written has mistakes in it.

mark_c wrote:The code then first loads the image into memory and then displays it.


The way you are loading the image file into an AnsiString is very inefficient, there are much better ways to handle that. For instance, querying the file size first, then pre-sizing the AnsiString's memory, then reading the file bytes directly into the AnsiString's memory in one go. Or using System::Ioutils::TFile::ReadAllBytes(). Or using std::vector with a std::ifstream and std::istream_iterator. Or a number of other possible ways.

But, more importantly, you are type-casting the raw image data to a TStream* pointer, which is just plain wrong, not to mention that assignment is also causing a memory leak.

I would suggest getting rid of the AnsiString altogether (since you are not using it correctly anyway) and just use TMemoryStream by itself. It has a LoadFromFile() method.

You are also leaking the TJPEGImage (which incidentally also has a LoadFromFile() method).

mark_c wrote:after many tests I wrote this code but I do not know if it is efficient


Not only is it inefficient, it has quite a few bugs in it, most notably memory leaks, but also a couple of buffer overflow as well.

Try this instead:

Code: Select all
void __fastcall TForm1::Button7Click(TObject *Sender)
{
    TMemoryStream *picture = new TMemoryStream;
    try
    {
        picture->LoadFromFile("img\\0001.jpg");

        Label5->Caption = picture->Size;

        TJPEGImage *myjpg = new TJPEGImage;
        try
        {
            picture->Position = 0;
            myjpg->LoadFromStream(picture);
            Image1->Picture->Assign(myjpg);
        }
        __finally {
            delete myjpg;
        }
    }
    __finally {
        delete picture;
    }
}


Alternatively:

Code: Select all
#include <memory>

void __fastcall TForm1::Button7Click(TObject *Sender)
{
    std::auto_ptr<TMemoryStream> picture(new TMemoryStream); // or std::unique_ptr if using C++11
    picture->LoadFromFile("img\\0001.jpg");

    Label5->Caption = picture->Size;

    std::auto_ptr<TJPEGImage> myjpg(new TJPEGImage); // or std::unique_ptr
    picture->Position = 0;
    myjpg->LoadFromStream(picture.get());
    Image1->Picture->Assign(myjpg.get());
}


Note, you don't really need the TMemoryStream as TJPEGImage has its own LoadFromFile() method:

Code: Select all
void __fastcall TForm1::Button7Click(TObject *Sender)
{
    TJPEGImage *myjpg = new TJPEGImage;
    try
    {
        myjpg->LoadFromFile("img\\0001.jpg");
        Image1->Picture->Assign(myjpg);
    }
    __finally {
        delete myjpg;
    }
}


Code: Select all
#include <memory>

void __fastcall TForm1::Button7Click(TObject *Sender)
{
    std::auto_ptr<TJPEGImage> myjpg(new TJPEGImage); // or std::unique_ptr
    myjpg->LoadFromFile("img\\0001.jpg");
    Image1->Picture->Assign(myjpg.get());
}


For that matter, so does TPicture:

Code: Select all
void __fastcall TForm1::Button7Click(TObject *Sender)
{
    Image1->Picture->LoadFromFile("img\\0001.jpg");
}
Remy Lebeau (TeamB)
Lebeau Software
User avatar
rlebeau
BCBJ Author
BCBJ Author
 
Posts: 1560
Joined: Wed Jun 01, 2005 3:21 am
Location: California, USA

Re: streaming and TStream problems

Postby mark_c » Tue Sep 04, 2018 11:35 am

thanks Remy,
I already knew the systems you proposed. My sample code is preparatory for straming images through sockets; that's why I'm filling an AnsiString with a jpeg file, it's just a simulation to understand which memory models should be used.

In reality, I have to fill a data buffer which: start = 0xFFD8 and end = 0xFFD9. Once the buffer has been filled:
<0xFFD8 ............... 0xFFD9>
After refill the buffer I have to pass it to a TImage object to display the image acquired via socket.
mark_c
BCBJ Guru
BCBJ Guru
 
Posts: 129
Joined: Thu Jun 21, 2012 1:13 am

Re: streaming and TStream problems

Postby mark_c » Tue Sep 04, 2018 12:36 pm

this version should be much more efficient, am I wrong?

Code: Select all
void __fastcall TForm1::Button8Click(TObject *Sender)
{

   FILE *fp;
   int ch;

   BYTE *buf;
   int i=0;
        long length;

   fp=fopen("img\\0001.jpg","rb");

        fseek(fp, 0L, SEEK_END);
        length = ftell(fp);
        fseek(fp, 0L, SEEK_SET);
        buf = new BYTE[length];

   do
   {
      ch = fgetc(fp);
      buf[i++]=(char)ch;

   }
   while(ch != EOF);

   fclose(fp);

   Label5->Caption=strlen(buf);

   TStream *picture = new TMemoryStream;
   TJPEGImage *myjpg = new TJPEGImage;

   picture->Write(buf, length);


   picture->Position=NULL;
   myjpg->LoadFromStream(picture);
   Image1->Canvas->Draw(0,0,myjpg);

        delete [] buf;

}
mark_c
BCBJ Guru
BCBJ Guru
 
Posts: 129
Joined: Thu Jun 21, 2012 1:13 am

Re: streaming and TStream problems

Postby rlebeau » Wed Sep 05, 2018 10:29 am

mark_c wrote:I already knew the systems you proposed. My sample code is preparatory for straming images through sockets; that's why I'm filling an AnsiString with a jpeg file, it's just a simulation to understand which memory models should be used.


Building off of our previous discussion, try something like this:

Code: Select all
class TReadOnlyMemoryStream : public TCustomMemoryStream
{
public:
   __fastcall TReadOnlyMemoryStream(void *APtr, NativeInt ASize)
      : TCustomMemoryStream()
   {
      SetPointer(APtr, ASize);
   }

   int __fastcall Write(const void *Buffer, int Count)
   {
      return 0;
   }
};

void __fastcall TForm1::ClientSocket1Connect(TObject *Sender, TCustomWinSocket *Socket)
{
   // buffer for incoming data...
   Socket->Data = new AnsiString;
}

void __fastcall TForm1::ClientSocket1Disconnect(TObject *Sender, TCustomWinSocket *Socket)
{
   // free the buffer...
   delete static_cast<AnsiString*>(Socket->Data);
   Socket->Data = NULL;
}

void __fastcall TForm1::ClientSocket1Read(TObject *Sender, TCustomWinSocket *Socket)
{
   char Buf[256];
   int ByteRecived = ClientSocket1->Socket->ReceiveBuf(Buf, sizeof(Buf));
   if (ByteRecived <= 0) return;

   // append received bytes to end of buffer...
   AnsiString *MyBuffer = static_cast<AnsiString*>(Socket->Data);
   //*MyBuffer += AnsiString(Buf, ByteRecived);
   int len = MyBuffer->Length();
   MyBuffer->SetLength(len + ByteRecived);
   memcpy(Buf, (*MyBuffer)[len+1], ByteRecived);

   // scan buffer for complete messages...

   // System::Pos(), PosEx(), AnsiString::Pos(), they are all inefficient for searching for single characters
   // in a loop, so using memchr() to scan the raw AnsiString memory directly...

   const char *pBuffer = MyBuffer->c_str();
   int BufferSize = MyBuffer->Length();
   int SizeProcessed = 0;

   do
   {
      // scan for start byte...
      char *pMsgStart = (char*) memchr(pBuffer, 0xD8, BufferSize);
      if (!pMsgStart)
      {
         // not found, wipe the buffer for next time...
         SizeProcessed += Size;
         break;
      }

      if (pMsgStart > pBuffer)
      {
         // not the first byte, ignore everything before it...
         int SizeToIgnore = (pMsgStart - pBuffer);
         pBuffer = pMsgStart;
         BufferSize -= SizeToIgnore;
         SizeProcessed += SizeToIgnore;
      }

      // scan for last byte...
      char *pMsgEnd = (char*) memchr(pMsgStart+1, 0xD9, BufferSize-1);
      if (!pMsgEnd)
      {
         // not found, keep the current message in the buffer for next time...
         break;
      }
      ++pMsgEnd;

      // total size between start and end bytes, inclusive
      int MsgSize = pMsgEnd - pMsgStart;
 
      TReadOnlyMemoryStream *stream = new TReadOnlyMemoryStream(pStart+1, MsgSize-2);
      try
      {
         TJPEGImage *jpg = new TJPEGImage;
         try
         {
            jpg->LoadFromStream(stream);
            Image1->Picture->Assign(jpg);
         }
         __finally {
            delete jpg;
         }
      }
      __finally {
        delete stream;
      }

      // done with current message...
      SizeProcessed += MsgSize;
      pBuffer = pMsgEnd;
      BufferSize -= MsgSize;
   }
   while (BufferSize > 0);

   if (SizeProcessed > 0)
      MyBuffer->Delete(1, SizeProcessed);
}


mark_c wrote:In reality, I have to fill a data buffer which: start = 0xFFD8 and end = 0xFFD9.


I guess you didn't pay attention to the last discussion we had about that. The correct values are 0xD8 and 0xD9, not 0xFFD8 and 0xFFD9.
Remy Lebeau (TeamB)
Lebeau Software
User avatar
rlebeau
BCBJ Author
BCBJ Author
 
Posts: 1560
Joined: Wed Jun 01, 2005 3:21 am
Location: California, USA

Re: streaming and TStream problems

Postby mark_c » Sat Sep 08, 2018 11:21 am

thanks Remy,
as always you are a very precious teacher
mark_c
BCBJ Guru
BCBJ Guru
 
Posts: 129
Joined: Thu Jun 21, 2012 1:13 am

Re: streaming and TStream problems

Postby mark_c » Mon Sep 10, 2018 6:32 am

this is another version for study purposes and I've noticed that sometimes it raises an exception about an index:

dstring.h
char & __fastcall operator [] (const int idx)
{
ThrowIfOutOfRange (idx); // Should Range-checking be optional to avoid overhead ??
Unique (); // Ensure we're not ref-counted
return Data [idx-1];
}


and secondly, the interface sometimes no longer responds: why?

The exception I think is due to AnsiString where the index [i] points to a non-existent position, but I can not explain why.

Code: Select all
void __fastcall TForm1::ClientSocket1Read(TObject *Sender,
TCustomWinSocket *Socket)
{
   if( !ClientSocket1->Active ) return;
   int ByteReceived;
   int Size = Socket->ReceiveLength();
   if (Size <= 0) return;
   char *Buf = new char[Size];

   do
   {
      ByteReceived = ClientSocket1->Socket->ReceiveBuf(Buf, Size);
      if (ByteReceived <= 0) break;
      MyBuffer+=AnsiString(Buf, ByteReceived);
      Size -= ByteReceived;
   }
   while( Size > 0 );

        Application->ProcessMessages();
       
        Label1->Caption=MyBuffer.Length();
   Label1->Update();

        for(int i=1;i<MyBuffer.Length();i++)
        {
                if((byte)MyBuffer[i] == 0xFF && (byte)MyBuffer[i+1] == 0xD8)
                {
                        MyJpegBuffer+=(char)0xFF;
                        MyJpegBuffer+=(char)0xD8;
                        head++;
                }
                else
                if((byte)MyBuffer[i] == 0xFF && (byte)MyBuffer[i+1] == 0xD9)
                {
                        MyJpegBuffer+=(char)0xFF;
                        MyJpegBuffer+=(char)0XD9;

                        char *asd=MyJpegBuffer.c_str();
                       
                        TStream *picture = new TMemoryStream;
                        TJPEGImage *myjpg = new TJPEGImage;
                        picture->Write(asd, MyJpegBuffer.Length());
                        picture->Position=NULL;
                        myjpg->LoadFromStream(picture);
                        Image1->Canvas->Draw(0,0,myjpg);
                        delete myjpg;
                        delete picture;

                        MyJpegBuffer="";
                        head=0;
                }
                else
                if(head == 1)
                        MyJpegBuffer+=MyBuffer[i];
        }


        if(head == 1)
                MyJpegBuffer+=MyBuffer[MyBuffer.Length()];


        MyBuffer="";
   delete[] Buf;
}
//---------------------------------------------------------------------------




in difinitive, I think it's the Application-> ProcessMessages (); to generate the problem but I do not explain why
mark_c
BCBJ Guru
BCBJ Guru
 
Posts: 129
Joined: Thu Jun 21, 2012 1:13 am

Re: streaming and TStream problems

Postby rlebeau » Mon Sep 10, 2018 10:53 am

mark_c wrote:and secondly, the interface sometimes no longer responds: why?


Most likely due to your use of ProcessMessages(). Get rid of it.

mark_c wrote:The exception I think is due to AnsiString where the index [i] points to a non-existent position, but I can not explain why.


That is what the debugger is meant for. Use it. You have several places in your code where you are using AnsiString::operator[], you need to figure out which one is throwing the exception, and why the index being passed to it is out of range.

Most likely, it is the use of operator[] after the loop exits, if MyBuffer is empty at that point.

For that matter, you really should not be clearing out the entire MyBuffer on every OnRead event anyway. Sometimes you need to carry data from one event to the next. That is the whole point of using a buffer in the first place. In fact, you are making things more complicated than they need to be by using multiple buffers and tracking bytes individually. Try something more like this instead:

Code: Select all
#include <System.AnsiStrings.hpp>

void __fastcall TForm1::ClientSocket1Connect(TObject *Sender, TCustomWinSocket *Socket)
{
   Socket->Data = new AnsiString;
}

void __fastcall TForm1::ClientSocket1Disconnect(TObject *Sender, TCustomWinSocket *Socket)
{
   delete static_cast<AnsiString*>(Socket->Data);
}

void __fastcall TForm1::ClientSocket1Read(TObject *Sender, TCustomWinSocket *Socket)
{
   int Size = Socket->ReceiveLength();
   if (Size <= 0) return;

   AnsiString &MyBuffer = *static_cast<AnsiString*>(Socket->Data);

   char Buf[1024];
   do
   {
      int ByteReceived = Socket->ReceiveBuf(Buf, min(Size, sizeof(Buf)));
      if (ByteReceived <= 0) break;
      MyBuffer += AnsiString(Buf, ByteReceived);
      Size -= ByteReceived;
   }
   while (Size > 0);

   Label1->Caption = MyBuffer.Length();
   Label1->Update();

   while (!MyBuffer.IsEmpty())
   {
      // look for the start of an image...
      int StartPos = MyBuffer.Pos("\xFF\xD8");
      if (StartPos == 0)
      {
         // not found, remove unused bytes from the buffer...
         if (MyBuffer[MyBuffer.Length()] == 0xFF)
            MyBuffer.Delete(1, MyBuffer.Length()-1);
         else
            MyBuffer = "";
         break;
      }

      if (StartPos > 1)
      {
         // remove unused bytes from the buffer...
         MyBuffer.Delete(1, StartPos-1);
         StartPos = 1;
      }

      // look for the end of the image...
      int EndPos = System::Ansistrings::PosEx("\xFF\xD9", MyBuffer, 3);
      if (EndPos == 0)
      {
         // not found, wait for more bytes to arrive...
         break;
      }

      // extract image from buffer...
      AnsiString MyJpegBuffer = MyBuffer.SubString(1, EndPos+1);
      MyBuffer.Delete(1, EndPos+1);

      // load and display image...
      TJPEGImage *myjpg = new TJPEGImage;
      try
      {
         TStream *picture = new TMemoryStream;
         try
         {
            picture->WriteBuffer(MyJpegBuffer.c_str(), MyJpegBuffer.Length());
            picture->Position = 0;
            myjpg->LoadFromStream(picture);
         }
         __finally
         {
            delete picture;
         }

         Image1->Picture->Assign(myjpg);
      }
      __finally
      {
         delete myjpg;
      }
   }
}


mark_c wrote:in difinitive, I think it's the Application-> ProcessMessages (); to generate the problem but I do not explain why


Because it causes reentrant issues. You should not be calling it at all. If OnRead is already running, and more data arrives, calling ProcessMessage() can cause OnRead to run again while the previous OnRead is still running. That can cause your buffer to be processed incorrectly, causing all kinds of logic errors and runtime errors.
Last edited by rlebeau on Sun Sep 23, 2018 1:50 pm, edited 1 time in total.
Remy Lebeau (TeamB)
Lebeau Software
User avatar
rlebeau
BCBJ Author
BCBJ Author
 
Posts: 1560
Joined: Wed Jun 01, 2005 3:21 am
Location: California, USA

Re: streaming and TStream problems

Postby mark_c » Fri Sep 21, 2018 11:33 am

if in the code below I add a Beep (500,100) in the position, see the code please, a delay is introduced in the acquisition of images during the streaming; is there a way to avoid this time delay due to synchronism?
Code: Select all
   void __fastcall TForm1::ClientSocket1Read(TObject *Sender,
   TCustomWinSocket *Socket)
   {
      if( !ClientSocket1->Active ) return;
      int ByteReceived;
      int Size = Socket->ReceiveLength();
      if (Size <= 0) return;
      char *Buf = new char[Size];

      do
      {
         ByteReceived = ClientSocket1->Socket->ReceiveBuf(Buf, Size);
         if (ByteReceived <= 0) break;
         MyBuffer+=AnsiString(Buf, ByteReceived);
         Size -= ByteReceived;
      }
      while( Size > 0 );
            

      for(int i=1;i<MyBuffer.Length();i++)
      {
         if((byte)MyBuffer[i] == 0xFF && (byte)MyBuffer[i+1] == 0xD8)
         {
            MyJpegBuffer+=(char)0xFF;
            MyJpegBuffer+=(char)0xD8;
            head++;
         }
         else
         if((byte)MyBuffer[i] == 0xFF && (byte)MyBuffer[i+1] == 0xD9)
         {
            MyJpegBuffer+=(char)0xFF;
            MyJpegBuffer+=(char)0XD9;

            char *asd=MyJpegBuffer.c_str();
            
            TStream *picture = new TMemoryStream;
            TJPEGImage *myjpg = new TJPEGImage;
            picture->Write(asd, MyJpegBuffer.Length());
            picture->Position=NULL;
            myjpg->LoadFromStream(picture);
            Image1->Canvas->Draw(0,0,myjpg);
            delete myjpg;
            delete picture;

// ********* here ****************
            ::Beep(500,100);
// *******************************            
            
            MyJpegBuffer="";
            head=0;
         }
         else
         if(head == 1)
            MyJpegBuffer+=MyBuffer[i];
      }


      if(head == 1)
         MyJpegBuffer+=MyBuffer[MyBuffer.Length()];


      MyBuffer="";
      delete[] Buf;
   }
   //---------------------------------------------------------------------------
mark_c
BCBJ Guru
BCBJ Guru
 
Posts: 129
Joined: Thu Jun 21, 2012 1:13 am

Re: streaming and TStream problems

Postby mark_c » Sat Sep 22, 2018 5:05 am

solved with a timer
mark_c
BCBJ Guru
BCBJ Guru
 
Posts: 129
Joined: Thu Jun 21, 2012 1:13 am

Re: streaming and TStream problems

Postby rlebeau » Sun Sep 23, 2018 1:54 pm

mark_c wrote:if in the code below I add a Beep (500,100) in the position, see the code please, a delay is introduced in the acquisition of images during the streaming;


Beep() is synchronous, it does not exit until the sound is finished playing.

mark_c wrote:is there a way to avoid this time delay due to synchronism?


Call Beep() in a separate thread. Or use MessageBeep(), which is asynchronous. Or use PlaySound() to play a WAV file, it has an option to play asynchronous.
Remy Lebeau (TeamB)
Lebeau Software
User avatar
rlebeau
BCBJ Author
BCBJ Author
 
Posts: 1560
Joined: Wed Jun 01, 2005 3:21 am
Location: California, USA

Re: streaming and TStream problems

Postby mark_c » Mon Sep 24, 2018 10:19 am

thanks Remi,
if I choose the thread I have to use the Synchronize () method, is right?
mark_c
BCBJ Guru
BCBJ Guru
 
Posts: 129
Joined: Thu Jun 21, 2012 1:13 am

Re: streaming and TStream problems

Postby rlebeau » Mon Sep 24, 2018 11:20 am

mark_c wrote:if I choose the thread I have to use the Synchronize () method, is right?


Not for a simple Beep(), no.
Remy Lebeau (TeamB)
Lebeau Software
User avatar
rlebeau
BCBJ Author
BCBJ Author
 
Posts: 1560
Joined: Wed Jun 01, 2005 3:21 am
Location: California, USA


Return to Technical

Who is online

Users browsing this forum: No registered users and 19 guests

cron