What is causing my image to corrupt during transfer via Bluetooth RFCOMM using FTP?

344 Views Asked by At

I'm developing two separate applications for data transfer over Bluetooth RFCOMM using the Obex File Transfer protocol. On one side, a Windows C# Console Application running on a PC listens for incoming bluetooth connections and sends an image whenever a client makes a request. On the other side, an Android application running on a mobile device scans the nearby bluetooth devices, looks for the server and receives the image.

In most cases, everything works fine and the image is transmitted without problems. Sometimes - not very often, I still can't figure out how to reproduce the error - the image is corrupted during the transmission, as some of the received bytes from the Android app do not match the original buffer (I compute the CRC of the received buffer and compare it with the original one to check if the image has been sent successfully).

Here's an example:

Original

Original,

Received

Received

This kind of "glitchy" image is just an example, every time something goes wrong the received image has a different 'glitch effect'.

Few things I tried to solve the problem:

  • Changing UUID, but neither the OOP UUID nor a custom UUID seems to work, as the exact same problem arises.
  • My smartphone (Xiaomi Redmi Note 8T) from which I am running the client app had almost zero free space of internal storage, so I got desperate and tried to free some memory to see if that was causing the error for some reason (yeah it doesn't make much sense but it's worth mentioning). At first it worked and I thought that solved the problem somehow, but then the error re-appeared just like before.
  • Using an ACK system to control each sub array of data sent from the server to the client, something like: the PC sends the first sub array of data, then it waits until the smartphone sends an ACK to acknowledge the reception of the sub array, and ONLY after that it proceeds to send the next sub array of data, and so on until the end of the buffer. Needless to say that neither this option worked (again, same error and corrupted data).
  • I also tried to see if other devices trying to connect to my smartphone could cause the problem, but it wasn't the case.

CODE

Server side

Here's my implementation of the listener in the C# Console app running on Windows 10. I took this Server sample as a reference.

// Initialize the provider for the hosted RFCOMM service
_provider = await RfcommServiceProvider.CreateAsync(
        RfcommServiceId.ObexFileTransfer);       // Use Obex FTP protocol
        // UUID is 00001106-0000-1000-8000-00805F9B34FB


// Create a listener for this service and start listening
StreamSocketListener listener = new StreamSocketListener();
listener.ConnectionReceived += OnConnectionReceivedAsync;
await listener.BindServiceNameAsync(
    _provider.ServiceId.AsString(),
    SocketProtectionLevel
        .BluetoothEncryptionAllowNullAuthentication);

// Set the SDP attributes and start advertising
InitializeServiceSdpAttributes(_provider);
_provider.StartAdvertising(listener);

InitializeServiceSdpAttributes function:

const uint SERVICE_VERSION_ATTRIBUTE_ID = 0x0300;
const byte SERVICE_VERSION_ATTRIBUTE_TYPE = 0x0A;   // UINT32
const uint SERVICE_VERSION = 200;

void InitializeServiceSdpAttributes(RfcommServiceProvider provider)
{
    Windows.Storage.Streams.DataWriter writer = new Windows.Storage.Streams.DataWriter();

    // First write the attribute type
    writer.WriteByte(SERVICE_VERSION_ATTRIBUTE_TYPE);
    // Then write the data
    writer.WriteUInt32(MINIMUM_SERVICE_VERSION);

    IBuffer data = writer.DetachBuffer();
    provider.SdpRawAttributes.Add(SERVICE_VERSION_ATTRIBUTE_ID, data);
}

Whenever a new connection attempt is detected, the OnConnectionReceivedAsync function stops the advertisement, disposes the listener and creates a new StreamSocket object. At this point I set the input and output streams, convert the image to an array of bytes and send the buffer length to the remote device through the socket. Once the Android app has received the length of the buffer, it sends an ACK which means that it is ready to recevie the actual data.

// Create input and output stream
DataWriter writer = new DataWriter(_socket.OutputStream);

// Convert image to array of bytes
byte[] imageByteArray;
using (var inputStream = await file.OpenSequentialReadAsync())
{
    var readStream = inputStream.AsStreamForRead();

    imageByteArray = new byte[readStream.Length];
    await readStream.ReadAsync(imageByteArray, 0, imageByteArray.Length);
}

// Write length of data
writer.WriteBytes(intToByteArray(imageByteArray.Length));
await writer.StoreAsync();

// Wait for ACK ...

Finally, I send the image:

// Write bytes and send
writer.WriteBytes(imageByteArray);
await writer.StoreAsync();

// Wait for ACK ...

As soon as the image is sent, the app receives an ACK from the remote device when all the data has been received and then closes the connection.

Client Side

First of all, the Android app creates a BluetoothSocket object using the same UUID specified from the Server app:

// Scan devices and find the remote Server by specifying the target MAC address
// ...

// targetDevice is the Server device 
BluetoothSocket socket = targetDevice.createInsecureRfcommSocketToServiceRecord(
       UUID.fromString("00001106-0000-1000-8000-00805F9B34FB")         // FTP
);

// Connect the server
socket.connect();

Finally, it reads the incoming data from the socket InputStream. First it reads the length of the incoming buffer and sends an ACK to confirm that it's ready to receive the image. Then waits for each sub array until all the buffer is complete. At this point, it sends a final ACK and closes the connection.

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

// Buffer that contains the incoming data
byte[] buffer = null;
// The numOfBytes is the expected length of the buffer
int numOfBytes = 0;
// Index of the sub array within the complete buffer
int index = 0;
// flag is true if the receiver is computing the number of bytes that it has to receive
// flag is false if the receiver is actually reading the image sub arrays from the stream
int flag = true;

while(true){
    // Estimate number of incoming bytes
    if(flag){
        try{
            // inputStream.available() estimates the number of bytes that can be read
            byte[] temp = new byte[inputStream.available()];

            // Read the incoming data and store it in byte array temp (returns > 0 if successful)
            if(inputStream.read(temp) > 0){
                // Get length of expected data as array and parse it to an Integer
                String lengthString = new String(temp, StandardCharsets.UTF_8);
                numOfBytes = Integer.parseInt(lengthString);
                // Create buffer
                buffer = new byte[numOfBytes];
                // Set the flag to false (turn on read image mode)
                flag = false;

                // Send ACK
            }
        }
        catch (IOException e){
            // ...
        }
    }
    // Read image sub arrays
    else {
        try{
            byte[] data = new byte[inputStream.available()];
            // Read sub array and store it
            int numbers = inputStream.read(data);

            if(numbers <= 0 && index < numOfBytes)
                continue;

            // Copy sub array into the full image byte array
            System.arraycopy(data, 0, buffer, index, numbers);
            // Update index
            index = index + numbers;

            // Reached the end of the buffer (received all the data)
            if(index == numOfBytes){

                // Send ACK (Transfer success)
                // ...

                // Decode buffer and create image from byte array
                Bitmap bmp = BitmapFactory.decodeByteArray(buffer, 0, numOfBytes);
                // Store output image
                outputImage = bmp;

                // Dismiss the bluetooth manager (close socket, exit waiting loop...)
                dismiss();

                // Return the image
                return bmp;
            }    
        }
        catch (IOException e){
            // ...
        }
    }
}
0

There are 0 best solutions below