How to determine if BitmapFactory.decodeStream has finished decoding the image from a socket InputStream?

663 views Asked by At

I'm developing an Android app that receives an image via BluetoothSocket and I'm trying to retrieve its Bitmap by using the BitmapFactory.decodeStream() method, so something like:

// Create BluetoothSocket socket and connect
// ...

// Get input stream
InputStream inputStream = socket.getInputStream();

// Get image bitmap
Bitmap bmp = BitmapFactory.decodeStream(inputStream);

What happens is that the application gets stuck on BitmapFactory.decodeStream(). To stop the application from hanging forever I have to manually close the socket from the remote device, and only after that the image gets decoded and returned successfully, but this is not a feasible solution for my application since I want the image to be transmitted programmatically. Another thing I tried was to create a separate Thread and call decodeStream() inside of it, then call Thread.start() and then Thread.join(milliseconds) with a hard-coded amount of time to wait before killing the thread. This works but I'd like to avoid it for 2 reasons:

  • I want the transfer to be as fast as possible, but in this case if the image is smaller than expected the thread takes longer than needed
  • If the image is bigger than expected, the thread could finish while the data is still being processed, causing some data loss

I looked at the documentation hoping to find a way to preliminarily set the expected image size before decoding the image, so that the app knows when the data has been received correctly, something like:

// Estimate length of expected data (size of image in bytes)
byte[] numOfBytes = new byte[inputStream.available()];
inputStream.read(numOfBytes)

// Set preliminary options
BitmapFactory.Options options = new BitmapFactory.Options();
options.setExpectedImageSize(numOfBytes)          // what I would ideally want: set fixed number of bytes 
                                                  // to let the BitmapFactory.decodeStream() method know when
                                                  // the decoding is over

// Get image bitmap
Bitmap bmp = BitmapFactory.decodeStream(inputStream, null, options);

but such procedure doesn't seem to exist.

Is there any way I can achieve this?

CODE

Server side (C#)

// Create bluetooth socket 
// ...

DataWriter writer = new DataWriter(socket.OutputStream);

// Send image byte array
writer.WriteBytes(imageByteArray);
await writer.StoreAsync();
await writer.FlushAsync();

writer.Dispose();
socket.Dispose();

Client side (Java)

// Create bluetooth socket and connect
// ...

InputStream inputStream = socket.getInputStream();

Bitmap bmp = BitmapFactory.decodeStream(inputStream);

return bmp;

Sent: Sent image

Received: Recevied image

2

There are 2 answers

12
blackapps On

When BitmapFactory.decodeStream() is ready it will close the stream. And hence the socket will be closed too. You say it hangs? Wel... hmmmm. hmmm.

That is why i said you earlier that the server should close the socket after having send the file. What is causing my image to corrupt during transfer via Bluetooth RFCOMM using FTP?

If you want to keep the connection open after a picture sent/received then you cannot use BitmapFactory.decodeStream(). –

hoping to find a way to preliminarily set the expected image size before decoding the image,

Well that is pretty standard. You let the sever first send an integer (four bytes) indicating the lenght/amount of following bytes.

Client first reads that integer and could make a byte array with the right size where it puts the received bytes of the file. You can then use BitmapFactory.decodeByteArray(). (or something like that).

0
Stephen C On

If the decodeStream doesn't complete, that most likely means that the InputStream it is trying to read from has blocked. And that means that it hasn't received the data it expects.

There is another possible explanation: the server could be sending a malformed image that is causing the decoder to get stuck on the client side. One example in Java 5's image decoder ... long since fixed ... was CVE-2006-6745. There may be more recent ones.

Apparently, unlike the Java SE image decoder classes, Android's decodeStream does not call close(). That means that there isn't any way to tell if the decodeStream call has completed apart from checking the value that it returns. The javadoc states:

"If the input stream is null, or cannot be used to decode a bitmap, the function returns null. The stream's position will be where ever it was after the encoded data was read."

But I think you are really missing the point. Correct me if I am wrong, but what you are actually trying to do is to stop the client size from blocking the server and vice versa. The solution to that is to use timers or timeouts.

  • On the server side, use a timer to detect that it has taken too long to send the (complete) image file.

  • On the client side, use a timer to detect that the decoderStream has taken too long.

  • In either case, the your code should then be able to call close the stream. On the client side, the decoderStream call should then return null. (At least, that is what the documentation seems to imply.)


Your idea for fixing this was something like the following:

byte[] numOfBytes = new byte[inputStream.available()];
inputStream.read(numOfBytes);
BitmapFactory.Options options = new BitmapFactory.Options();
options.setExpectedImageSize(numOfBytes);
Bitmap bmp = BitmapFactory.decodeStream(inputStream, null, options);

This approach is fatally flawed.

  1. Calling InputStream.available on a socket input stream ONLY tells you how much data can be read without blocking. Basically, that is the data that has already been received into the kernel TCP/IP buffers on the client side. It probably won't be the entire image.

    Basically, calling InputStream.available is rarely the right thing to do. (IMO, it would be a good idea if they deprecated it ... though I doubt that they will.)

  2. Therefore inputStream.read(numOfBytes) won't read the entire image.

  3. But then you don't do anything useful with numOfBytes.

  4. There isn't a way to set an "expected image size" in the Options.