0

Background: I have a .wav file saved on an SD card. I would like to transfer that file to a server using my esp32. I am using node red to handle the server side activities.

Method Employed:

  1. open the file in binary mode.
  2. evaluate the size of the file
  3. decide on a max upload size and allocate a buffer
  4. Read the file and store to the buffer.
  5. use http post to send data to the server.
  6. if file is too large to send in a single buffer then divide the file up and send multiple http posts.

Problem: I can successfully send text files. when I try to send .wav files the size of the sent wave file increases and the file is corrupted. Analyzing the file is difficult as its not all text, what I have done is open the file in notepad++ to see if I can spot anything. Everything should be the same in theory but several characters are coming up as blank squares in the transferred file and some are coming up as the exact same. example of original file and transferred file

Analysis/Theory:

I am quite lost as to what the issue is. My leading theory is that a wave file is written in int16_t but in order to post the data it needs to be * uint8_t, maybe when the casting of the int16 to a uint8 data is lost, I looked at trying to change a int16_t into two int8_t bytes as done here https://stackoverflow.com/a/53374797/14050333 but had no luck, maybe I'm jumping to conclusions. Any help would be hugely appreciated!

Code:

Full code used to sell text files.

void loop()
{
  WiFiClient client;
  Serial.println("starting file upload");
  IPAddress host(192, 168, 0, 37);
  int port = 1880;
  if (!client.connect(host, port))
  { // check connection to host if untrue internet connection could be down
    Serial.println("couldn't connect to host");
  }

  HTTPClient http;
  const char* serverName = "http://192.168.0.37:1880/sensor_file";
  http.begin(client, serverName);

  char *fname = "/sdcard/test_text.txt";
  FILE *fp = fopen(fname, "rb"); // read in bytes
    
  //get file size
  fseek(fp, 0, SEEK_END); //send file pointer to end of file 
  int file_size = ftell(fp); //get end position of file
  fseek(fp, 0, SEEK_SET); //send pointer back to start

  int max_upload_size = 10; // array size, larger = less uploads but too large can cause memory issues
  int num_of_uploads = file_size / max_upload_size; // figure out how many evenly sized upload chunks we need
  int num_of_uploads_mod = file_size % max_upload_size; //find out size of remaining upload chunk if needed
  int i;
  //upload file in even chunks    
  if (num_of_uploads > 0)
  {
    char buff1[max_upload_size+1] = {}; // array to save file too. add 1 for end of array symbol '\n'
    for (i = 0; i < num_of_uploads; i++)
    {
      fread(buff1, sizeof(buff1)-1, 1, fp); // -1 as don't want to count the '\n'
      http.addHeader("File_name", "test file"); //header to say what the file name is
      int httpResponseCode = http.POST((uint8_t *)buff1, sizeof(buff1)-1); //send data. Datatype is (uint8_t *)
    }
  }
  //upload any remaining data
  if (num_of_uploads_mod > 0)
  {
    int remainder = file_size - num_of_uploads * max_upload_size;
    char buff2[remainder+1] = {};
    fread(buff2, sizeof(buff2)-1, 1, fp); //read from file and store to buff2
    http.addHeader("File_name", "test file");
    int httpResponseCode = http.POST((uint8_t *)buff2, sizeof(buff2)-1); //send buff2 to server
  }
  http.end(); // Close connection
  delay(10 * 1000);
} 

Adjustments made for .wav files

int remainder = file_size - num_of_uploads * max_upload_size;
int16_t buff2[remainder+1] = {};
fread(buff2, sizeof(buff2)-1, 1, fp); //remainder
http.addHeader("File_name", "test file");
int httpResponseCode = http.POST((uint8_t *)buff2, sizeof(buff2)-1);
foch
  • 31
  • 1
  • 5
  • 3
    Why are you using an array of `int16_t`-type elements as a buffer? You are reading a file in binary mode, so be it `.wav`, `.jpg`, `.ttf`, or anything else, it's just a sequence of bytes (`uint8_t`, not `int16_t`) anyway. Another thing, `fread()` expects the size of each object to read as the second parameter and the number of objects to read as the third parameter, so, in case of objects being bytes, first define buffer as `uint8_t buff1[max_upload_size] = {};` (no need for +1/-1 games), and then `fread(buff1, sizeof *buff1, sizeof buff1 / sizeof *buff1, fp);`. The same for `buff2`. – heap underrun Nov 12 '21 at 02:07
  • 1
    Exactly what @heapunderrun said. It doesn't matter what the representation of the data in the file is - it's a set of bytes. You want an exact copy, you transmit exactly those bytes. It doesn't matter what they are. – romkey Nov 12 '21 at 02:13
  • 1
    Also, always check for errors and check the return value of `fread()`. By the way, variable-size arrays, although allowed in the C standard, are not a standard C++ feature. Your code seems mainly C (like `fread()` and other C stuff, instead of C++ approach like `std::fstream` and `std::vector`), but you also use classes (like `HTTPClient`), so it needs to be compiled as C++. Another thing: do you set the `Content-Type` header to some binary type or what? Content encoding may be the problem. – heap underrun Nov 12 '21 at 02:22
  • 1
    When you compared the sent and received .wav files (display HEX values by using HEX-editor plugin for Notepad++ ), what was the difference? You should try with a small .wav file (size < single buff size), and check the difference. There would be something interesting. – Peter Lee Nov 12 '21 at 02:43
  • 2
    why do you upload with many POST request? stream the file from SD to server with one POST request. esp32 HTTPClient has `int sendRequest(const char * type, Stream * stream, size_t size = 0);` – Juraj Nov 12 '21 at 05:40
  • @heapunderrun and romkey Thank you for the advice! So the reason i was using an array of int16_t was that initially read the wav file as a char array as I thought a char was just an 8 bit number the same as uint8_t, when things weren't being transmitted correctly I though int16_t seemed logical way to read the file as the wav file was being written as a int16_t. realizing now though that a char isn't unsigned. also thanks for the fread advice, looks like a much cleaner way than what I had after work I'll give it a go! – foch Nov 12 '21 at 09:29
  • I'm also coming at this not from a C or C++ background but rather an Arduino background so there is definitely a learning curve. I actually didn't realize std::fstream and fread were that different I thought they were just different approaches to the same thing. I don't set the Content-type as the file write was working fine with the text files so that is a good point. – foch Nov 12 '21 at 09:35
  • @Juraj streaming directly from the SD card was the initial goal but couldn't figure out how to post the data directly from the SD card rather than storing it temporarily in an array on the esp32. probably lack of knowledge on my part. when storing to an array if the size is too big the esp32 crashes. breaking it up was my way of getting around that, long term if I can get the current method to work I'll probably look at doing what you suggested! – foch Nov 12 '21 at 09:43

1 Answers1

0

Its working!

There were 2 main issues with the code as outlined by heap underrun. The first issue is that I was reading in the wav file as int16_t the correct datatype to use was uint8_t.

Why are you using an array of int16_t-type elements as a buffer? You are reading a file in binary mode, so be it .wav, .jpg, .ttf, or anything else, it's just a sequence of bytes (uint8_t, not int16_t) anyway. Another thing, fread() expects the size of each object to read as the second parameter and the number of objects to read as the third parameter, so, in case of objects being bytes, first define buffer as uint8_t buff1[max_upload_size] = {}; (no need for +1/-1 games), and then fread(buff1, sizeof *buff1, sizeof buff1 / sizeof *buff1, fp);. The same for buff2. – heap underrun

The second issue was that I did not include a header in the post stream specifying the content type. As it wasn't needed for the text file and when writing the file in node-red it lets you choose the encoding. I didn't think I would need it, however as it turns out I needed to add:

http.addHeader("Content-Type", "application/octet-stream");

Below is the working code for the file upload section:

  if (num_of_uploads > 0)
  {
    uint8_t buff1[max_upload_size] = {}; 
    for (i = 0; i < num_of_uploads; i++)
    {
      fread(buff1, sizeof *buff1, sizeof buff1 / sizeof *buff1, fp);
      http.addHeader("File_name", "test file"); //header to say what the file name is
      http.addHeader("Content-Type", "application/octet-stream");
      int httpResponseCode = http.POST(buff1, sizeof(buff1)); 
    }
  }

  if (num_of_uploads_mod > 0)
  {
    int remainder = file_size - num_of_uploads * max_upload_size;
    uint8_t buff2[remainder] = {};
    fread(buff2, sizeof *buff2, sizeof buff2 / sizeof *buff2, fp);
    http.addHeader("File_name", "test file");
    http.addHeader("Content-Type", "application/octet-stream");
    int httpResponseCode = http.POST(buff2, sizeof(buff2));
  }

On a slightly interesting side note out of curiosity I tried running the above code but with

uint16_t buff1[max_upload_size] = {};

and

http.POST((uint8_t) buff1, sizeof(buff2));

The file uploaded but the size was 2x what it should be, curiously however the file wasn't corrupted, and played the audio as it was recorded. Just thought that was interesting.

I'll close out this answer as the original question was successfully answered. Again thank you for the help, I've been at this literally weeks and you solved my problems in hours!

foch
  • 31
  • 1
  • 5