ESP32 xQueue, how to copy struct (memory) efficiently ?

Ok so i finished a testing sketch which more or less implements how i thought it up initially.
Instead of blocking the main thread using the Filename Queue i decided it was easier to let the Error Queue respond. It isn't the prettiest yet, and some things still need to be modified.

#include "FS.h"
#include "SD.h"

#define HSPI_SCK 17
#define HSPI_MISO 16
#define HSPI_MOSI 4
#define SD_CS 15  // Added 10K pullup for v2 (strapping pin must be HIGH)

#define LED_WHITE 14

#define SPI_FRQ 32000000

#define SERIAL_OUTPUT_BAUD 500000
#define PIXEL_BYTES (680 * 3)
#define PORTS 4
#define FRAMES (40 * 60 * 2) // 40 Hz, 60 Seconds, 2 Minutes

#define FILENAME_LENGTH 30
#define BLOCK_SIZE 512
#define SD_QUEUE_SIZE 32 // maybe only 8
#define SD_ERR_QUEUE_SIZE 16

#define NO_ERROR 0
#define UNEXP_SDQ 1
#define UNEXP_SDQ_EMPTY 2
#define UNEXP_FILENAME_EMPTY 3
#define UNEXP_FILENAME_NULL 4
#define FILE_NOT_CREATED 5
#define NAME_RECEIVED 6
#define EMPTY_QUEUE 7
#define WRITE_ERROR 8
#define HEADER_WRITTEN 9
#define WRITE_STOP 10
#define NAME_NOT_EXP 11
#define Q_NOT_EMPTY 12
#define STACK_SIZE 13

#define WAIT_FOR_NAME 0
#define WAIT_FOR_DATA 1

#define STACK_CHECK //{uint32_t stack = uxTaskGetStackHighWaterMark(NULL); if (stack < maxStack) maxStack = stack;}


typedef struct {
  uint16_t len;
  uint8_t block[BLOCK_SIZE];
} SdBlock;

QueueHandle_t SdQ;
QueueHandle_t SdFileName;
QueueHandle_t SdError;

SPIClass * hspi = NULL;

uint8_t stripixels[PORTS][PIXEL_BYTES];
uint16_t lengths[PORTS] = {PIXEL_BYTES, PIXEL_BYTES, PIXEL_BYTES, PIXEL_BYTES};
uint16_t totalLengths = 0;

void setup() {
  Serial.begin(500000);
  delay(1000);
  Serial.println();
  Serial.println();
  Serial.println("SD Task on core 0 test");
  pinMode(LED_WHITE, OUTPUT);
  SdQ = xQueueCreate(SD_QUEUE_SIZE, sizeof(SdBlock));
  SdFileName = xQueueCreate(1, FILENAME_LENGTH + 1);  // filename Queue should be only 1 so the main thread can be blocked
  uint8_t err = 0;
  SdError = xQueueCreate(SD_ERR_QUEUE_SIZE, sizeof(err)); // error codes are only 1 byte

  if (SdQ == NULL) {
    Serial.println(F("Error creating the SD queue"));
  }
  if (SdFileName == NULL) {
    Serial.println(F("Error creating the finename queue"));
  }
  if (SdError == NULL) {
    Serial.println(F("Error creating the SD Error queue"));
  }

  if (InitSdCard()) {
    Serial.println(F("SD card initialized"));
  }

  xTaskCreatePinnedToCore(
    HandleQueueToSD,
    "Task Write To SD",  // A name just for humans
    3000,  // The stack size can be checked by calling `uxHighWaterMark = uxTaskGetStackHighWaterMark(NULL);`
    NULL,  // No parameter is used
    2,  // Priority, with 3 (configMAX_PRIORITIES - 1) being the highest, and 0 being the lowest.
    NULL,  // Task handle is not used here
    0  // pinned to core
  );

  uint8_t b = 0;
  for (uint8_t i = 0; i < PORTS; i++) {
    for (uint16_t j = 0; j < PIXEL_BYTES; j++) {
      stripixels[i][j] = b;
      b++;
    }
    totalLengths += lengths[i];
  }

  Serial.println(F("Arrays Filled."));
  Serial.print(F("Total Lengths : "));
  Serial.println(totalLengths, DEC);
  delay(1000);

  if (CreateSdFileToTask("TestxTaskSd_2")) {
    Serial.println(F("Created SD Ok."));
    uint32_t frames = FRAMES;
    uint32_t lastFrame = millis();
    int pinstate = HIGH;
    while (frames) {
      digitalWrite(LED_WHITE, pinstate);
      pinstate = HIGH - pinstate;
      frames--;
      while (millis() - lastFrame < 25) {  // blocking time limiter
        delayMicroseconds(10); // stability
      }
      lastFrame = millis();
      for (uint8_t i = 0; i < PORTS; i++) {
        if (!SendDataBlockToSdQueue(stripixels[i], lengths[i])) {
          frames = 0;
        }
      }
    } // frames
    if (StopSdFileToTask()) {
      Serial.println(F("Filename Cleared, Writing Stopped"));
      /*uint8_t err = 0;
      int ret = xQueueReceive(SdError, &err, portMAX_DELAY);
      if (err == STACK_SIZE) {
        uint32_t maxStack = 0;
        for (uint8_t i = 0; i < 4; i++) {
          ret = xQueueReceive(SdError, &err, portMAX_DELAY);
          maxStack |= ((uint32_t) err << (8 * i));
        }
        Serial.print(F("Minimum Free Stack Size : "));
        Serial.println(maxStack, DEC);
      }*/
    }
  }// file Created
}

void loop() {

}

void SendBlockToTask(uint8_t* buf, uint16_t len) {
  SdBlock qBlock;
  memcpy (qBlock.block, buf, len);
  qBlock.len = len;
  xQueueSend(SdQ, &qBlock, portMAX_DELAY);
}

void PrintAllErrorQueue() {
  int ret = pdPASS;
  uint8_t err = NO_ERROR;
  while (uxQueueMessagesWaiting(SdError)) {
    ret = xQueueReceive(SdError, &err, portMAX_DELAY);
    Serial.print(F("Error : "));
    Serial.println(err, DEC);
  }
}

bool StopSdFileToTask() {
  PrintAllErrorQueue(); // flush the error Queue
  char nameString[FILENAME_LENGTH + 1] = {'\0'};
  xQueueSend(SdFileName, &nameString, portMAX_DELAY);  // send the noname
  while (!uxQueueMessagesWaiting(SdError)) {  // wait for response
    delayMicroseconds(10); // stability
  }
  uint8_t err;
  int ret = xQueueReceive(SdError, &err, portMAX_DELAY);
  if (err != WRITE_STOP) {
    Serial.println(F("'Write Stop' not received"));
    Serial.print(F("Error : "));
    Serial.println(err, DEC);
    PrintAllErrorQueue();
    return false;
  }
  return true;
}

bool CreateSdFileToTask(String filename) {
  PrintAllErrorQueue(); // flush the error Queue
  char nameString[FILENAME_LENGTH + 1] = {'\0'};
  filename = "/" + filename;
  filename += ".pst";  // adding the extension here is better
  uint8_t len = filename.length();
  if (len > FILENAME_LENGTH) len = FILENAME_LENGTH;
  filename.toCharArray(nameString, len + 1);
  xQueueSend(SdFileName, &nameString, portMAX_DELAY);  // send the name

  while (!uxQueueMessagesWaiting(SdError)) {  // wait for response
    delay(10); // stability
  }
  uint8_t err;
  int ret = xQueueReceive(SdError, &err, portMAX_DELAY);
  if (err != NAME_RECEIVED) {
    Serial.println(F("Name not received"));
    Serial.print(F("Error : "));
    Serial.println(err, DEC);
    PrintAllErrorQueue();
    return false;
  }

  char header[] = "DmagiX PixelStream vx.xx\rColorSequence=X\rFrameSize=0000\rDmxSize=000\rID=000000#";
  SdBlock qBlock;
  memcpy (qBlock.block, header, sizeof(header) - 1);
  qBlock.len = sizeof(header) - 1;
  xQueueSend(SdQ, &qBlock, portMAX_DELAY); // send the header to the SD Queue

  ret = xQueueReceive(SdError, &err, portMAX_DELAY);
  if (err != HEADER_WRITTEN) {
    Serial.println(F("Header not written"));
    Serial.print(F("Error : "));
    Serial.println(err, DEC);
    PrintAllErrorQueue();
    return false;
  }

  ret = pdPASS;
  err = NO_ERROR;
  while (uxQueueMessagesWaiting(SdError) && (ret == pdPASS)) {
    ret = xQueueReceive(SdError, &err, portMAX_DELAY);
    Serial.print(F("Error : "));
    Serial.println(err, DEC);
  }
  if (err != NO_ERROR) return false;
  delay(10);
  return true; // success
}

bool SendDataBlockToSdQueue(uint8_t * block, uint16_t len) {
  uint16_t ndx = 0;
  while ((len) && (!uxQueueMessagesWaiting(SdError))) {  // break also on error message waiting
    uint16_t blockSize = 512;
    if (blockSize > len) blockSize = len;
    SdBlock qBlock;
    memcpy (qBlock.block, block + ndx, blockSize);
    qBlock.len = blockSize;
    xQueueSend(SdQ, &qBlock, portMAX_DELAY); // send the block to the SD Queue
    len -= blockSize;
    ndx += blockSize;
  }
  int ret = pdPASS;
  uint8_t err = NO_ERROR;
  while (uxQueueMessagesWaiting(SdError) && (ret == pdPASS)) {
    ret = xQueueReceive(SdError, &err, portMAX_DELAY);
    Serial.print(F("Error : "));
    Serial.println(err, DEC);
  }
  if (err != NO_ERROR) return false;
  return true; // success
}

bool InitSdCard() {
  if (hspi == NULL) {
    hspi = new SPIClass(HSPI);
  }
  hspi->begin(HSPI_SCK, HSPI_MISO, HSPI_MOSI, SD_CS);
  hspi->setFrequency(SPI_FRQ);

  if (!SD.begin(SD_CS, *hspi, SPI_FRQ)) {
    Serial.println(F("No SD card found"));
    return false;
  }
  return true;
}

//-----------------------------------------------------------------------------------

void HandleQueueToSD(void *pvParameters) {
  SdBlock taskBlock;
  char nameString[FILENAME_LENGTH + 1] = {'\0'};
  uint8_t taskState = WAIT_FOR_NAME;
  File sFile;
  //uint32_t maxStack = uxTaskGetStackHighWaterMark(NULL);

  for (;;) {
    
    vTaskDelay(1 / portTICK_PERIOD_MS);  // when running a higher priority
    
    if ((SdQ != NULL) && (SdFileName != NULL) && (SdError != NULL))  {  // Sanity check
      
      //STACK_CHECK
      
      switch (taskState) {
        //STACK_CHECK
        case WAIT_FOR_NAME: {
            if (nameString[0] == '\0') {  // only if we have no name

              if (uxQueueMessagesWaiting(SdQ)) {  // shouldn't happen, but we need to read them regardless
                int ret = xQueueReceive(SdQ, &taskBlock, (TickType_t) 0);  // it should be there, if it isn't we can continue
                if (ret == pdPASS) {  // and send error messages back
                  uint8_t err = UNEXP_SDQ;
                  xQueueSend(SdError, &err, (TickType_t) 0); // don't wait
                  //STACK_CHECK
                }
                else if (ret == pdFALSE) {
                  uint8_t err = UNEXP_SDQ_EMPTY;
                  xQueueSend(SdError, &err, (TickType_t) 0); // don't wait
                  //STACK_CHECK
                }
              }

              if (uxQueueMessagesWaiting(SdFileName)) {
                int ret = xQueueReceive(SdFileName, &nameString, portMAX_DELAY);  // read the filename
                if (ret == pdPASS) {
                  //STACK_CHECK
                  if (nameString[0] == '\0') {
                    uint8_t err = UNEXP_FILENAME_NULL;
                    xQueueSend(SdError, &err, (TickType_t) 0);  // should not be empty
                  }
                  else {
                    //STACK_CHECK
                    sFile = SD.open(nameString, FILE_WRITE);  // open the file
                    if (!sFile) {
                      uint8_t err = FILE_NOT_CREATED;
                      xQueueSend(SdError, &err, (TickType_t) 0);  // error
                    }
                    else {
                      //STACK_CHECK
                      uint8_t err = NAME_RECEIVED;
                      xQueueSend(SdError, &err, (TickType_t) 0);  // No error !!
                      while (!uxQueueMessagesWaiting(SdQ)) ;
                      //STACK_CHECK
                      int ret = xQueueReceive(SdQ, &taskBlock, portMAX_DELAY);
                      if (ret == pdFALSE) {
                        uint8_t err = EMPTY_QUEUE;
                        xQueueSend(SdError, &err, (TickType_t) 0);
                        //STACK_CHECK
                        sFile.close();
                        nameString[0] = '\0';
                      }
                      else {
                        //STACK_CHECK
                        uint16_t bytesWritten = sFile.write(taskBlock.block, taskBlock.len);
                        if (bytesWritten != taskBlock.len) { // error
                          uint8_t err = WRITE_ERROR;
                          //STACK_CHECK
                          xQueueSend(SdError, &err, (TickType_t) 0);
                          nameString[0] = '\0';
                        }
                        else {
                          uint8_t err = HEADER_WRITTEN;  // no error
                          //STACK_CHECK
                          xQueueSend(SdError, &err, (TickType_t) 0);
                          taskState = WAIT_FOR_DATA;
                        }
                        //STACK_CHECK
                        sFile.close();  // close file anyway
                      }
                    }
                  }
                }
                else if (ret == pdFALSE) {
                  //STACK_CHECK
                  uint8_t err = UNEXP_FILENAME_EMPTY;
                  xQueueSend(SdError, &err, (TickType_t) 0); // don't wait
                }
              }
            }
          }

        case WAIT_FOR_DATA: {
            //STACK_CHECK
            if (uxQueueMessagesWaiting(SdQ)) {
              sFile = SD.open(nameString, FILE_APPEND);  // open the file
              int ret = pdPASS;

              while (ret == pdPASS) {
                //STACK_CHECK
                ret = xQueueReceive(SdQ, &taskBlock, (TickType_t) 0); //portMAX_DELAY);
                if (ret == pdPASS) {
                  uint16_t bytesWritten = sFile.write(taskBlock.block, taskBlock.len);
                  if (bytesWritten != taskBlock.len) { // error
                    uint8_t err = WRITE_ERROR;
                    //STACK_CHECK
                    xQueueSend(SdError, &err, (TickType_t) 0); // send the error

                    // send specifics
                    err = bytesWritten >> 8;
                    xQueueSend(SdError, &err, (TickType_t) 0); 
                    err = bytesWritten & 0xFF;
                    xQueueSend(SdError, &err, (TickType_t) 0); 
                    err = taskBlock.len >> 8;
                    xQueueSend(SdError, &err, (TickType_t) 0); 
                    err = taskBlock.len & 0xFF;
                    xQueueSend(SdError, &err, (TickType_t) 0);

                    
                    nameString[0] = '\0';  // clear the name
                    taskState = WAIT_FOR_NAME;  // back to the name task
                    ret = pdFALSE; // Stop write loop
                    int fl = pdPASS;
                    while (fl == pdPASS) {  // flush the Queue
                      //STACK_CHECK
                      fl = xQueueReceive(SdQ, &taskBlock, (TickType_t) 0);
                    }
                  }
                  STACK_CHECK
                }
              } // read Queue loop
              //STACK_CHECK
              sFile.close();
              //STACK_CHECK
            }  // if SdQ available

            if (uxQueueMessagesWaiting(SdFileName)) {  // check if there is a name available
              int ret = xQueueReceive(SdFileName, &nameString, portMAX_DELAY);  // read the filename
              if (ret == pdPASS) {
                //STACK_CHECK
                if (nameString[0] == '\0') {
                  uint8_t err = WRITE_STOP;
                  xQueueSend(SdError, &err, (TickType_t) 0);  // should be empty
                  //STACK_CHECK
                }
                else {   // we were in a stream, so the name shoyuld have been empty
                  uint8_t err = NAME_NOT_EXP;
                  xQueueSend(SdError, &err, (TickType_t) 0);  // not empty
                  nameString[0] = '\0';
                  //STACK_CHECK
                }
                taskState = WAIT_FOR_NAME; // task ends either way
                int q = pdPASS;
                bool notEmpty = false;
                //STACK_CHECK
                while (q == pdPASS) {  // flush the Queue just in case (should be empty)
                  q = xQueueReceive(SdQ, &taskBlock, (TickType_t) 0);
                  if (q == pdPASS) {
                    notEmpty = true;
                    //STACK_CHECK
                  }
                }
                if (notEmpty) { // the queue really should be empty
                  uint8_t err = Q_NOT_EMPTY;
                  //STACK_CHECK
                  xQueueSend(SdError, &err, (TickType_t) 0);  // should be empty
                }
                
                // send the maxStack

                /*uint8_t err = STACK_SIZE;
                xQueueSend(SdError, &err, (TickType_t) 0);
                err = maxStack & 0xFF;
                maxStack = maxStack >> 8;
                xQueueSend(SdError, &err, (TickType_t) 0);
                err = maxStack & 0xFF;
                maxStack = maxStack >> 8;
                xQueueSend(SdError, &err, (TickType_t) 0);
                err = maxStack & 0xFF;
                maxStack = maxStack >> 8;
                xQueueSend(SdError, &err, (TickType_t) 0);
                err = maxStack & 0xFF;
                maxStack = maxStack >> 8;
                xQueueSend(SdError, &err, (TickType_t) 0);
                maxStack = uxTaskGetStackHighWaterMark(NULL);
                */
              }
            }
          }  // wait for data
      }
    }  // Sanity check
  }  // Infinite loop
}

Now when i was trying to work out how much Stack Size to declare, the indication was that about 2800 bytes were required, but when i set it to that, it started crashing until i commented out the measuring of the stack size !?
I don't see how that could require an extra 3000 bytes but that much more was what was required for a stable run.

The idea was anyway to create a buffer which could be flexible.

Other option could be to allocate a buffer for all the pixels on the heap. That way i could do all of the processing for what to store on the SD card on core 0, but that would mean that the buffer isn't flexible anymore. As it is, the buffer is partly available for writing when blocks have already been written to the SD card, which should make the less of an impact on the overall performance.

Anyway. This is the first testing of it, now i guess it's time to implement this is a bigger sketch and see how it works for me. The bigger the SD Queue the smoother i suspect.