I am trying to copy a WAV sound in C. the original file is a 2 seconds file, but I want to replicate the data in the destination file several times, so that it plays longer. For example, if I copy it 3 times, it should play for 6 seconds... right?
But for some reason, even though the destination file is bigger than the original file, it still plays for 2 seconds...
Can anyone help please?
Here is my code:
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
using namespace std;
typedef struct header_file
{
char chunk_id[4];
int chunk_size;
char format[4];
char subchunk1_id[4];
int subchunk1_size;
short int audio_format;
short int num_channels;
int sample_rate;
int byte_rate;
short int block_align;
short int bits_per_sample;
char subchunk2_id[4];
int subchunk2_size;
} header;
typedef struct header_file* header_p;
int main()
{
FILE * infile = fopen("../files/man1_nb.wav","rb"); // Open wave file in read mode
FILE * outfile = fopen("../files/Output.wav","wb"); // Create output ( wave format) file in write mode
int BUFSIZE = 2; // BUFSIZE can be changed according to the frame size required (eg: 512)
int count = 0; // For counting number of frames in wave file.
short int buff16[BUFSIZE]; // short int used for 16 bit as input data format is 16 bit PCM audio
header_p meta = (header_p)malloc(sizeof(header)); // header_p points to a header struct that contains the wave file metadata fields
int nb; // variable storing number of byes returned
if (infile)
{
fread(meta, 1, sizeof(header), infile); // Read only the header
fwrite(meta,1, sizeof(*meta), outfile); // copy header to destination file
int looper = 0; // number of times sound data is copied
for(looper=0; looper <2; looper++){
while (!feof(infile))
{
nb = fread(buff16,1,BUFSIZE,infile); // Reading data in chunks of BUFSIZE
count++; // Incrementing Number of frames
fwrite(buff16,1,nb,outfile); // Writing read data into output file
}
fseek(infile, 44, SEEK_SET); // Go back to end of header
}
}
fclose(infile); fclose(outfile);
return 0;
}
Both of your read and write code parts are wrong.
wav files have RIFF format and consists of tlv chunks. Each chunk consists of header and data. Typically wav file consists of 3 chunks: format chunk with FOURCC code, format chunk with PCMWAVEFORMAT struct and data chunk with sound data. Also since size of each chunk is limited by 32 bit of length holding field, large files are constructed by concatenating wav files together.
You need to parse file chunk-by-chunk, and write into destination chunk-by-chunk, updating headers accordingly.
When you change size of your data you'll need to update output header as well.
long total_bytes_written_to_outfile = ftell(outfile);
// correct chunk_size and subchunk2_size just before closing outfile:
fseek(outfile, 0, SEEK_SET);
int size = total_bytes_written_to_outfile - sizeof(*meta);
meta->chunk_size = sizeof(header) - sizeof(meta->chunk_id) - sizeof(meta->chunk_size) + size;
meta->subchunk2_size = size;
fwrite(meta, 1, sizeof(*meta), outfile);
fclose(outfile);
Also, to make sure you are reading correct file check that meta->chunk_size == file size of man1_nb.wav - 8
Related
I've got a simple wav header reader i found online a long time ago, i've gotten back round to using it but it seems to replace around 1200 samples towards the end of the data chunk with a single random repeated number, eg -126800. At the end of the sample is expected silence so the number should be zero.
Here is the simple program:
void main() {
WAV_HEADER* wav = loadWav(".\\audio\\test.wav");
double sample_count = wav->SubChunk2Size * 8 / wav->BitsPerSample;
printf("Sample count: %i\n", (int)sample_count);
vector<int16_t> samples = vector<int16_t>();
for (int i = 0; i < wav->SubChunk2Size; i++)
{
int val = ((wav->data[i] & 0xff) << 8) | (wav->data[i + 1] & 0xff);
samples.push_back(val);
}
printf("done\n");
}
And here is the Wav reader:
typedef struct
{
//riff
uint32_t Chunk_ID;
uint32_t ChunkSize;
uint32_t Format;
//fmt
uint32_t SubChunk1ID;
uint32_t SubChunk1Size;
uint16_t AudioFormat;
uint16_t NumberOfChanels;
uint32_t SampleRate;
uint32_t ByteRate;
uint16_t BlockAlignment;
uint16_t BitsPerSample;
//data
uint32_t SubChunk2ID;
uint32_t SubChunk2Size;
//Everything else is data. We note it's offset
char data[];
} WAV_HEADER;
#pragma pack()
inline WAV_HEADER* loadWav(const char* filePath)
{
long size;
WAV_HEADER* header;
void* buffer;
FILE* file;
fopen_s(&file,filePath, "r");
assert(file);
fseek(file, 0, SEEK_END);
size = ftell(file);
rewind(file);
std::cout << "Size of file: " << size << std::endl;
buffer = malloc(sizeof(char) * size);
fread(buffer, 1, size, file);
header = (WAV_HEADER*)buffer;
//Assert that data is in correct memory location
assert((header->data - (char*)header) == sizeof(WAV_HEADER));
//Extra assert to make sure that the size of our header is actually 44 bytes
assert((header->data - (char*)header) == 44);
fclose(file);
return header;
}
Im not sure what the problem is, i've confirmed that there is no meta data, nor is there a mis match between the numbers read from the header of the file and the actual file. Im assuming its a size/offset misallignment on my side, but i cannot see it.
Any help welcomed.
Sulkyoptimism
WAV is just a container for different audio sample formats.
You're making assumptions on a wav file that would have been OK on Windows 3.11 :) These don't hold in 2021.
Instead of rolling your own Wav file reader, simply use one of the available libraries. I personally have good experiences using libsndfile, which has been around roughly forever, is very slim, can deal with all prevalent WAV file formats, and with a lot of other file formats as well, unless you disable that.
This looks like a windows program (one notices by the fact you're using very WIN32API style capital struct names – that's a bit oldschool); so, you can download libsndfile's installer from the github releases and directly use it in your visual studio (another blind guess).
Apple (macOS and iOS) software often does not create WAVE/RIFF files with just a canonical Microsoft 44-byte header at the beginning. Those Wave files can instead can use a longer header followed by a padding block.
So you need to use the full WAVE RIFF format parsing specification instead of just reading from a fixed size 44 byte struct.
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:
open the file in binary mode.
evaluate the size of the file
decide on a max upload size and allocate a buffer
Read the file and store to the buffer.
use http post to send data to the server.
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.
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);
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!
I need to erase or overwrite(erase would be better) a number of bytes from the start of a file. The content are first read into a data structure the after deleting new content are written.
Currently I have the reading and writing part. How can I do the data clearing part? Ask if you want to know anything else. Thanks, sorry for bad English :)
typedef struct header {
char version[5]; //Offset 0, length 5, Archiver version
int files_no; //Offset 5, length 8, Number of files in the archive
char desc[256]; //Offset 13, length 256, Archive description, Header size 296??
header() {
strcpy(version, "0.20");
strcpy(desc, "THIS IS A DESCRIPTION FIELD WITH 256BYTE!");
}
}archiveHeader;
int archive(char *argv[], int argc) {
archiveHeader archive_struct_write, archive_struct_read;
string output = argv[argc-1]; output += ".n0b"; //Name of file to modify
int write_counter = 1;
ofstream file_write(output.c_str(), ios::binary | ios::app);
file_write.seekp(0, file_write.beg);
ifstream file_read(output.c_str(), ios::binary);
file_read.read((char*)&archive_struct_read, sizeof(archive_struct_read)); //Read existing beginning data
//Steps for erasing the read stuff goes here.
write_counter = archive_struct_read.files_no;
archive_struct_write.files_no = write_counter + 1;
file_write.write((char*)&archive_struct_write, sizeof(archive_struct_write)); //New beginning data written
file_read.close();
file_write.close();
write_counter++;
return 0;
}
Edit : Buffering everything after the beginning bytes and writing to a new file is not an option, the file can vary in size upto GBs. Thanks!
I have never worked with binary files before. I opened an .mp3 file using the mode ios::binary, read data from it, assigned 0 to each byte read and then rewrote them to another file opened in ios::binary mode. I opened the output file on a media player, it sounds corrupted but I can still hear the song. I want to know what happened physically.
How can I access/modify the raw data ( bytes ) of an audio ( video, images, ... ) using C++ ( to practice file encryption/decryption later )?
Here is my code:
#include <iostream>
#include <fstream>
#include <cstring>
using namespace std;
int main(){
char buffer[256];
ifstream inFile;
inFile.open("Backstreet Boys - Incomplete.mp3",ios::binary);
ofstream outFile;
outFile.open("Output.mp3",ios::binary);
while(!inFile.eof()){
inFile.read(buffer,256);
for(int i = 0; i<strlen(buffer); i++){
buffer[i] = 0;
}
outFile.write(buffer,256);
}
inFile.close();
outFile.close();
}
What you did has nothing to do with binary files or audio. You simply copied the file while zeroing some of the bytes. (The reason you didn't zero all of the bytes is because you use i<strlen(buffer), which simply counts up to the first zero byte rather than reporting the size of the buffer. Also you modify the buffer which means strlen(buffer) will report the length as zero after you zero the first byte.)
So the exact change in audio you get is entirely dependent on the mp3 file format and the audio compression it uses. MP3 is not an audio format that can be directly manipulated in useful ways.
If you want to manipulate digital audio, you need to learn about how raw audio is represented by computers.
It's actually not too difficult. For example, here's a program that writes out a raw audio file containing just a 400Hz tone.
#include <fstream>
#include <limits>
int main() {
const double pi = 3.1415926535;
double tone_frequency = 400.0;
int samples_per_second = 44100;
double output_duration_seconds = 5.0;
int output_sample_count =
static_cast<int>(output_duration_seconds * samples_per_second);
std::ofstream out("signed-16-bit_mono-channel_44.1kHz-sample-rate.raw",
std::ios::binary);
for (int sample_i = 0; sample_i < output_sample_count; ++sample_i) {
double t = sample_i / static_cast<double>(samples_per_second);
double sound_amplitude = std::sin(t * 2 * pi * tone_frequency);
// encode amplitude as a 16-bit, signed integral value
short sample_value =
static_cast<short>(sound_amplitude * std::numeric_limits<short>::max());
out.write(reinterpret_cast<char const *>(&sample_value),
sizeof sample_value);
}
}
To play the sound you need a program that can handle raw audio, such as Audacity. After running the program to generate the audio file, you can File > Import > Raw data..., to import the data for playing.
How can I access/modify the raw data ( bytes ) of an audio ( video, images, ... ) using C++ ( to practice file encryption/decryption later )?
As pointed out earlier, the reason your existing code is not completely zeroing out the data is because you are using an incorrect buffer size: strlen(buffer). The correct size is the number of bytes read() put into the buffer, which you can get with the function gcount():
inFile.read(buffer,256);
int buffer_size = inFile.gcount();
for(int i = 0; i < buffer_size; i++){
buffer[i] = 0;
}
outFile.write(buffer, buffer_size);
Note: if you were to step through your program using a debugger you probably would have pretty quickly seen the problem yourself when you noticed the inner loop executing less than you expected. Debuggers are a really handy tool to learn how to use.
I notice you're using open() and close() methods here. This is sort of pointless in this program. Just open the file in the constructor, and allow the file to be automatically closed when inFile and outFile go out of scope:
{
ifstream inFile("Backstreet Boys - Incomplete.mp3",ios::binary);
ofstream outFile("Output.mp3",ios::binary);
// don't bother calling .close(), it happens automatically.
}
I'm trying to read a standard 24-bit BMP file into a byte array so that I can send that byte array to libpng to be saved as a png. My code, which compiles:
#include <string>
#include <stdio.h>
#include <iostream>
#include <fstream>
#include <Windows.h>
#include "png.h"
using namespace std;
namespace BMP2PNG {
long getFileSize(FILE *file)
{
long lCurPos, lEndPos;
lCurPos = ftell(file);
fseek(file, 0, 2);
lEndPos = ftell(file);
fseek(file, lCurPos, 0);
return lEndPos;
}
private: System::Void button1_Click(System::Object^ sender, System::EventArgs^ e)
{
std::string filenamePNG = "D:\\TEST.png";
FILE *fp = fopen(filenamePNG.c_str(), "wb");
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,NULL,NULL,NULL);
png_info *info_ptr = png_create_info_struct(png_ptr);
png_init_io(png_ptr, fp);
png_set_IHDR(png_ptr,info_ptr,1920,1080,16,PNG_COLOR_TYPE_RGB,PNG_INTERLACE_NONE,PNG_COMPRESSION_TYPE_BASE,PNG_FILTER_TYPE_BASE);
png_write_info(png_ptr,info_ptr);
png_set_swap(png_ptr);
const char *inputImage = "G:\\R-000.bmp";
BYTE *fileBuf;
BYTE *noHeaderBuf;
FILE *inFile = NULL;
inFile = fopen(inputImage, "rb");
long fileSize = getFileSize(inFile);
fileBuf = new BYTE[fileSize];
noHeaderBuf = new BYTE[fileSize - 54];
fread(fileBuf,fileSize,1,inFile);
for(int i = 54; i < fileSize; i++) //gets rid of 54-byte bmp header
{
noHeaderBuf[i-54] = fileBuf[i];
}
fclose(inFile);
png_write_rows(png_ptr, (png_bytep*)&noHeaderBuf, 1);
png_write_end(png_ptr, NULL);
fclose(fp);
}
};
Unfortunately, when I click the button that runs the code, I get an error "Attempted to read or write protected memory...". I'm very new to C++, but I thought I was reading in the file correctly. Why does this happen and how do I fix it?
Also, my end goal is to read a BMP one pixel row at a time so I don't use much memory. If the BMP is 1920x1080, I just need to read 1920 x 3 bytes for each row. How would I go about reading a file into a byte array n bytes at a time?
Your getFileSize() method is not actually returning the file size. You're basically moving to the correct position in the BMP header but instead of actually reading the next 4 bytes that represent the size, you're returning the position in the file (which will be always 2).
Then in the caller function you don't have any error checking and you have code that assumes the file size is always greater than 54 (the allocations for the read buffers for example).
Also keep in mind that the file size field in the BMP header might not always be correct, you should also take into account the actual file size.
You are reading filee size of your *.bmp file, but "real" data can be larger. BMP can have compression (RLE). After that when you write decompressed PNG to that array, you can have overflow size of image, because you previsouly obtained size of compressed BMP file.
In function
png_set_IHDR(png_ptr,info_ptr,1920,1080,16,PNG_COLOR_TYPE_RGB,PNG_INTERLACE_NONE,PNG_COMPRESSION_TYPE_BASE,PNG_FILTER_TYPE_BASE);
Why do you have bit depth set to 16 ? Shouldn´t it be 8, because each RGB channel from BMP is 8bit.
Also for PNG handling, I am using this library: http://lodev.org/lodepng/. It works fine.