1

I've written a Delphi program which creates MJPEG files, which can be several GB in length. The JPGs are grabbed from a DirectX camera using DSPack. That part works fine and creates a file of JPG images in the format:

FF D8 ....(image data)... FF D9 FF D8 .... (image data)... FF D9 FF D8 etc

FF D8 marks the start of a JPG and FF D9 marks the end. Each JPG is around 21KB in size.

Now, I'm trying to write a matching MJPEG player.

In the Form's FormCreate procedure, I create a FileStream and display the first JPG which works fine:

procedure TForm1.FormCreate(Sender: TObject);
var
  b: Array[0..1] of Byte;
  jpg: TJPEGImage;
begin
 :
 :
   MemoryStream:= TMemoryStream.Create;
   jpg:= TJPEGImage.Create;

   MJPEGStream:= TFileStream.Create(MJPEGFileName, fmOpenRead);

   MJPEGStream.Position:= 0;

   repeat                                   
      MJPEGStream.Read(b[0], 2);            // Find end of first jpg
      MemoryStream.Write(b[0], 2);          // and write to memory
   until (b[0] = $FF) and (b[1] = $D9);

   MemoryStream.Position:= 0;
   jpg.LoadFromStream(memoryStream);
   Image1.Picture.Assign(jpg);
   MemoryStream.Free;
   jpg.Free;
end;

I leave the FileStream open so, hopefully, its Position pointer is retained. I have a button on the form, the intention being to jog forwards one JPG at a time but, although the first 'jog' advances one JPG, subsequent jogs advance a random number of times. Here's the procedure:

procedure TForm1.btnJogForwardClick(Sender: TObject);
var
   b: Array[0..1] of Byte;
   jpg: TJPEGImage;

begin
    MemoryStream:= TMemoryStream.Create;
    try
       repeat
          MJPEGStream.Read(b[0], 2);
          MemoryStream.Write(b[0], 2);
       until ((b[0] = $FF) and (b[1] = $D9));

       MemoryStream.Position:= 0;
       jpg:= TJPEGImage.Create;
       try
         try
           jpg.LoadFromStream(MemoryStream);
           Image1.Picture.Assign(jpg);
         except
         end;
       finally
         jpg.Free;
       end;
    finally
       MemoryStream.Free;
  end;

I've checked with a 3rd Party MJPEG player and that is able to jog frame by frame so I know the MJPEG file is ok. Any suggestions as to why my procedure isn't stepping uniformly frame by frame would be appreciated.

Thanks, John.

vwlowen
  • 73
  • 2
  • 6
  • Reading and writing two bytes at a time is going to be very inefficient. You need some buffering. – David Heffernan Apr 29 '16 at 12:46
  • It is a bit sluggish but I'm not sure how to read/write a larger block without losing the position of the two End bytes (and leave the pointer ready at the next Start byte). Although it is slow, my thinking is, if I can't get this to work, I'd really be struggling with anything more complicated! – vwlowen Apr 29 '16 at 13:07
  • 3
    Is there by definition always an even number of image data bytes? If not you will miss the end marker. – Tom Brunberg Apr 29 '16 at 13:28
  • Hmm, yes, that's true! That explains the problem. I'll have to go away and have a re-think! – vwlowen Apr 29 '16 at 13:44
  • Looks like [JPEG File Interchange Format](https://en.wikipedia.org/wiki/JPEG_File_Interchange_Format)... – Ondrej Kelle Apr 29 '16 at 14:47
  • Exactly, aren't the `FFD9` bytes required at the start of the next frame? – Stijn Sanders Apr 29 '16 at 15:05
  • A "stand-alone" JPG starts with FF D8 and ends with FF D9 so I'm assuming the file pointer is in the correct position after it's rad the D9. – vwlowen Apr 29 '16 at 15:21
  • I'm currently trying loading a large chunk of the MJPEG into a 50000 byte buffer and scanning it one byte at a time to look for FF and D9. That's faster and takes care of the problem reading two bytes at a time. It seems to work for the first JPG but not for the second so it mustn't be setting the MJPEG file pointer to the right place. – vwlowen Apr 29 '16 at 15:32
  • Except the image data itself may contain the sequence `FF D9` so you'd better [parse it](http://stackoverflow.com/questions/4585527/detect-eof-for-jpg-images). – Ondrej Kelle Apr 29 '16 at 15:34
  • Apparently, FF D8 and FF D9 are reserved for the start and end flags. I've searched through by eye with a hex editor and FF D8 is always followed by FF D9. Unfortunately JPGs don't include their size like bitmaps otherwise it would be relatively easy to copy each one. – vwlowen Apr 29 '16 at 15:41
  • In your special case image, perhaps. Are you sure other images can't contain these bytes? – Ondrej Kelle Apr 29 '16 at 15:43
  • I've checked a few random ones taken with different cameras and they all follow the pattern. https://en.wikipedia.org/wiki/JPEG – vwlowen Apr 29 '16 at 15:44
  • And checking a few images makes you sure? :-) But it seems you're right after all: according to the [spec](https://www.w3.org/Graphics/JPEG/itu-t81.pdf) "B.1.1.5 Entropy-coded data segments" they stuff `00` after any `FF` byte, so the image scan data cannot contain markers. Also mentioned [here](http://stackoverflow.com/questions/1557071/the-size-of-a-jpegjfif-image). – Ondrej Kelle Apr 29 '16 at 15:51
  • Search for buffered file stream – David Heffernan Apr 29 '16 at 22:45

1 Answers1

1

Thanks for the comments and suggestions. I think I've managed to sort it.

const
  JPGSizeMax = 100000;

procedure TForm1.FormCreate(Sender: TObject);
var
   b: Array[0..JPGSizeMax] of Byte;
 :
 :
begin
 :
 :
MJPEGStream:= TFileStream.Create(MJPEGFileName, fmOpenRead);
MJPEGStream.Position:= 0;

MJPEGStream.Read(b[0], JPGSizeMax);
for i:= 0 to JPGSizeMax do
begin
   if (b[i] = $D9) and (b[i-1] = $FF) then
   begin
     Count:= i;
     break;
   end;
end;

MemoryStream.Write(b[0], Count);
FilePosition:= Count + 1;

MemoryStream.Position:= 0;
jpg.LoadFromStream(memoryStream);
Image1.Picture.Assign(jpg);

MemoryStream.Free;
jpg.Free;

end;

The procedure for the Jog button is much the same:

MJPEGStream.Position:= FilePosition;

MJPEGStream.Read(b[0], JPGSizeMax);
for i:= 0 to JPGSizeMax do
begin
   if (b[i] = $D9) and (b[i-1] = $FF) then
   begin
     Count:= i;
     break;
   end;
end;

  memoryStream.Write(b[0], Count);
  FilePosition:= FilePosition + count + 1;

// etc

Thanks again for pointing me in the right direction.

John.

vwlowen
  • 73
  • 2
  • 6