Reading raw binary IQ data in C/C++ with malloc() and pointer

Digital signal processing (DSP) of radio frequency (RF) data very often deals with read from and write to a binary file with IQ format.

“I” and “Q” stand for “in-phase” and “Quadrature phase”. It is related to how a signal is modulated into “in-phase” and “quadrature” channels or branched at baseband before the signal is modulated again for RF at high frequency for transmission.

IQ format is a format where the signal is recorded in an interchangeably IQ data, that is the data is arranged as IQIQIQIQ….IQ.

In this short post, we will present and discuss a way to read binary IQ file from a file and insert the read data into a variable to be processed.

the IQ binary data reading method utilises pointers in C/C++. This Pointers functionality is a powerful feature in C/C++. However, if the pointers are not well-managed, the program can cause an operating system crashing due to memory leak.


READ MORE: The problem of writing high-digit numbers into a file and its solution


Amazon #Ad - associate link

Advantages of using pointers in C/C++

In this section, several advantages of using pointers in C/C++ will be explained. Some of the pointers powerful features and advantages are:

  • Flexibility. By using pointers, we can adjust the size of the array. We do not need to fix the array size at compilation. That is, at runtime, we can create different size of array according to our need.
  • Efficiency. Memory allocation and deallocation at runtime are possible by using pointers. If the allocation and deallocation are correctly managed, the memory efficiency (RAM) will be high as the size of the array will fit exactly the data size at hand.
  • Less overhead. With pointers, we can just send the pointers of a variable, array or structure to another function for manipulation without sending all the dataset to the other function. By only sending the pointer, instead of the whole data, to the other function, the memory overhead will be significantly reduced.
  • Easy arrangement. When we want to rearrange an array or structures, with pointers, it is possible to not move the array or data structure themselves. We can in fact just rearrange or move the pointers to designated data. By doing this pointer rearrangement, the time complexity in manipulating large data can be improved (reduced), for example, in a data sorting process.

READ MORE: TUTORIAL: MATLAB software inter-connection and cooperation with PYTHON software using pyenv()


Using struct and pointers to read binary IQ data in C/C++

This section will explain the working principles of using structure of pointers to read binary IQ data. the full C implementation for example presented in the next section.

The implementation uses a single struct with two members, I and Q. These I and Q members are in the form of pointers data type.

Since the I and Q are as pointers, the memory required to store the data can be decided (allocated) at runtime depend on how many data we want to read.

That is, the I and Q is a dynamic multiple-array.

Figure 1 shows the illustration of the data structure to store the IQ data read from a raw IQ binary file. In this figure, the struct is called IQ_data. In this case, we useint16_t data type.

But basically, we can use any data type, such as float or int8_t, that we want depending on our specific case.

The IQ_data struct has two members I and Q to store the I and Q value, respectively. These members then allocate required memory at runtime based on how many IQ data to store.

The main idea of the process is that we read the IQ data from a binary file and store the read data into a buffer. This buffer has a size of the size of I and Q array in the IQ_data struct.

Then, we will copy the data, obtained from the buffer, into the I and Q array of the IQ_data struct.

Figure 1: Illustration of pointers to allocated memories.

READ MORE: Beware of integer data type in computing


The C implementation to read binary IQ data

The full C implementation of using pointers to read binary IQ data is as follows:

#include <iostream>
#include <string.h>

typedef struct{
	int16_t *I;
	int16_t *Q;
} IQ_data;


int main(){

	//Set the sampling frequency
	int fs=25e6; 
	
	//Define the struct for the IQ data as the head pointer
	IQ_data* rawData = (IQ_data*) malloc(sizeof(IQ_data));

	//Define the buffer, the pointer to read the data from a file into the memory RAM
	int16_t *buff; //contain IQIQIQIQ...IQ data

	//Allocate the memory for the I and Q data member
	if ( (rawData->I = (int16_t *) malloc( fs * sizeof(int16_t)) ) == NULL)
      return -1;
    if ( (rawData->Q = (int16_t *) malloc( fs * sizeof(int16_t)) ) == NULL)
      return -1;
    if ( (buff = (int16_t *) malloc( fs*2 * sizeof(int16_t)) ) == NULL)
      return -1;

	//Read the data
	FILE *fp;

	//char *fileName;
	char filename[1000];

	strcpy(filename,"IQ_data.bin");

	if( (fp = fopen(filename, "r")) == NULL ){
  		std::cout << "\nCannot open the file!\n" << std::endl;
		// Free memory
		free (rawData->I);
		free (rawData->Q);
		free (rawData);
		free (buff);
		return -1;
	}

	size_t readCount;

	if ( (readCount = fread(buff, sizeof(int16_t), fs*2, fp)) < fs){
	  std::cout << "\nIt is not possible to read enough data from file to carry out requested operation\n" << std::endl;
    }

	else{//insert the data into form the buffer to the the rawData struct to hold the IQ values
		for (int i=0; i<readCount; i+=2){
			rawData->I[i/2] = buff[i];		//I data
			rawData->Q[i/2] = buff[i+1];	//Q data
		}
	}

	//Show the data
	int I=rawData->I[500];
	int Q=rawData->Q[500];
	std::cout<<"\n";
	std::cout << "I="<<I<<"\n";
	std::cout << "Q="<<Q<<"\n";	

	// Free memory
    free (rawData->I);
    free (rawData->Q);
    free (rawData);
	free (buff);

	if (fp){
    	fclose(fp);
	}

}

Some explanations regarding the C implementation code above are as follows:

IQ_data* rawData = (IQ_data*) malloc(sizeof(IQ_data));

The line of code above is to create the instance of the IQ_data struct, that is rawData, as a pointer. The reason is that, since the member will be a pointer with some memory allocations, the rawData struct, as a pointer, can point to its I and Q member with the allocated memory.

if ( (readCount = fread(buff, sizeof(int16_t), fs*2, fp)) < fs){...

The line of code above is to read the binary data from the file and store it in a prepared buffer. This size of the buffer is the summation of the size of I and Q element of the rawData struct.

The data in this buffer is still the IQIQIQ..IQ interchange data.

for (int i=0; i<readCount; i+=2){
			rawData->I[i/2] = buff[i];		//I data
			rawData->Q[i/2] = buff[i+1];	//Q data
		}

The section of code above is to insert the data into the separated I and Q array of the rawData struct from the buffer.

    free (rawData->I);
    free (rawData->Q);
    free (rawData);
	free (buff);

Finally, the above section of code is to free the allocated memory. If we do not perform this operation, the allocated memory will be locked and cannot be used by other programs. This will cause memory leaks and we need to restart the system to free the memory.

 Conclusion

In this post, a way to read a binary IQ file and store the data into a struct variable in C programming has been presented.

This procedure of reading a binary IQ file is a bread-and-butter step in all DSP for RF signals. The method of the binary IQ file using pointer and malloc() is very efficient to read a large size of binary IQ files.

However, one must remember to always do not forget to free the allocated memory to avoid “memory leak” in the system.


You may find some interesting items by shopping here.

Amazon #Ad - associate link