I am working on the CS50 Intro to CS course--Lab 4: Volume. And I think I have some misunderstanding with the molloc function. At first I used the malloc function to allocate space for the header part as below:
// TODO: Copy header from input file to output file
uint8_t *header = malloc(HEADER_SIZE);
if (header == NULL)
{
return 2;
}
fread(header, sizeof(header), 1, input);
fwrite(header, sizeof(header), 1, output);
free(header);
The program managed to compile but I think it didn't copy the header properly. Then I tried this:
// TODO: Copy header from input file to output file
uint8_t header[HEADER_SIZE];
fread(header, sizeof(header), 1, input);
fwrite(header, sizeof(header), 1, output);
And that worked, but I don't know what changes I have made by doing so. I just see this two kind of codes as having the same effect. Can somebody tell me the difference and what mistakes I made? I mean if I really want to use malloc function, what the codes should be like? Below is my full successful code for volumn.c:
// Modifies the volume of an audio file
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
// Number of bytes in .wav header
const int HEADER_SIZE = 44;
int main(int argc, char *argv[])
{
// Check command-line arguments
if (argc != 4)
{
printf("Usage: ./volume input.wav output.wav factor\n");
return 1;
}
// Open files and determine scaling factor
FILE *input = fopen(argv[1], "r");
if (input == NULL)
{
printf("Could not open file.\n");
return 1;
}
FILE *output = fopen(argv[2], "w");
if (output == NULL)
{
printf("Could not open file.\n");
return 1;
}
float factor = atof(argv[3]);
// TODO: Copy header from input file to output file
uint8_t header[HEADER_SIZE];
fread(header, sizeof(header), 1, input);
fwrite(header, sizeof(header), 1, output);
// TODO: Read samples from input file and write updated data to output file
int16_t buffer;
while (fread(&buffer, sizeof(buffer), 1, input))
{
buffer *= factor;
fwrite(&buffer, sizeof(buffer), 1, output);
}
// Close files
fclose(input);
fclose(output);
}
While working with this lab I also encountered similar problem. I was able to solve it without using malloc but I wanted to write another solution incorporating it.
The problem with your malloc is that you are not providing the correct argument.
It is given that each byte of header is an uint8_t (8-bit unsigned int). So the size should be sizeof(uint8_t) * HEADER_SIZE. Check out an example here.
Therefore the correct way is:
uint8_t *header = malloc(sizeof(uint8_t) * HEADER_SIZE);
instead of uint8_t *header = malloc(HEADER_SIZE);
Also you should not check for header == NULL in the next line as we just initialized this pointer and it doesn't matter what's present there.
Thanks for the question and hope my answer helps!
Related
I'm trying to copy wav samples using two slightly different ways here. First way gives me correct output file sound and the second one - distorted sound:
fwrite case:
int16_t samp;
while (fread(&samp, sizeof(int16_t), 1, input))
{
samp *= factor;
fwrite(&samp, sizeof(int16_t), 1, output);
}
fputc case:
int16_t samp;
while ((samp = fgetc(input)) != EOF)
{
samp *= factor;
fputc(samp, output);
}
I already know that it happens exactly because of samp *= factor; in second case. Volume properly changes when I use numbers without a decimal point, and sound gets distorted when I use floating-point numbers.
So my question is: why it works in the fwrite case, and doesn't work in fputc case? Both of them are int being multiplied by a float. How does it even work in a first case?
Here below is a whole program for reference:
// Modifies the volume of an audio file
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
// Number of bytes in .wav header
const int HEADER_SIZE = 44;
int main(int argc, char *argv[])
{
// Check command-line arguments
if (argc != 4)
{
printf("Usage: ./volume input.wav output.wav factor\n");
return 1;
}
// Open files and determine scaling factor
FILE *input = fopen(argv[1], "r");
if (input == NULL)
{
printf("Could not open file.\n");
return 1;
}
FILE *output = fopen(argv[2], "w");
if (output == NULL)
{
printf("Could not open file.\n");
return 1;
}
float factor = atof(argv[3]);
// Copy header from input file to output file
uint8_t *hder = malloc(sizeof(uint8_t) * HEADER_SIZE);
fread(hder, sizeof(uint8_t), HEADER_SIZE, input);
fwrite(hder, sizeof(uint8_t), HEADER_SIZE, output);
free(hder);
// Read samples from input file and write updated data to output file
int16_t samp;
while (fread(&samp, sizeof(int16_t), 1, input))
{
samp *= factor;
fwrite(&samp, sizeof(int16_t), 1, output);
}
// while ((samp = fgetc(input)) != EOF)
// {
// samp *= factor;
// fputc(samp, output);
// }
// Close files
fclose(input);
fclose(output);
}
From the fputc docs on cppreference.com:
Internally, the character is converted to unsigned char just before being written.
When you call fputc your 16-bit samp is being converted to an 8-bit unsigned char. With fwrite, both bytes are being written to the output file.
Hello and thank you for taking a look.
I'm working through CS50x and am struggling with Recover. The aim is to open a .raw file, read its contents in 512-byte blocks, check the initial four bytes for .jpg headers, and then write each JPEG data to a new file.
I have a body of code written, and the file compiles. The debugger tells me that my buffer[512] variable remains empty/zeroed. This then means the program skips if/else conditions and the program exits.
While my logic within the While loop may be flawed, I haven't been able to step far enough into the program to consider this.
I looked up my issue before posting. Some sources like to use fread(buffer, 512, 1, input), but CS50 itself uses fread(buffer, 1, 512, input). Also, when initialising the filename, I have tried both char *filename = malloc(8 * sizeof(char)); and char filename[8];. For both lines I have tried each method and am still missing something.
My code is below. Thank you in advance for your time.
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
typedef uint8_t BYTE;
int main(int argc, char *argv[])
{
// First check the number of arguments is correct.
if (argc != 2)
{
printf("Correct Usage: ./recover.c [filename]\n");
return 1;
}
// Open the file.
FILE *inputFile = fopen(argv[1], "r");
if (inputFile == NULL)
{
printf("File not found.\n");
return 1;
}
// Create counter of number of files.
int counter = 0;
// Create filename variable
char *filename = malloc(8 * sizeof(char)); // 7 + 1 for \0
// Create a 512-size array buffer.
BYTE buffer[512];
// Initialise img file for scope access.
FILE *img = NULL;
while (fread(buffer, sizeof(BYTE), 512, inputFile))
{
// If start of new JPEG:
if (buffer[0] == 0xff && buffer[1] == 0xd8 && buffer[2] == 0xff && (buffer[3] & 0xf0) == 0xe0)
{
if (counter == 0) // If the FIRST JPEG
{
// Make new file:
sprintf(filename, "%03i.jpg", counter);
img = fopen(filename, "w");
fwrite(buffer, sizeof(BYTE), 512, img);
}
else // If not the first JPEG
{
fclose(img); // Close previous file.
counter++;
// Make new file:
sprintf(filename, "%03i.jpg", counter); // Update filename.
img = fopen(filename, "w");
fwrite(buffer, sizeof(BYTE), 512, img);
}
}
else if (counter > 0) // buffer is continuation of previous.
{
fwrite(buffer, sizeof(BYTE), 512, img);
}
else
{
printf("I exited with no images.\n");
return 2;
}
}
free(filename);
fclose(img);
fclose(inputFile);
return 0;
}
The program exits (returns) after the first line in the raw file is read (assuming it's not a jpeg header, which is the case with the distro raw file). else if (counter > 0) evaluates to false, so the else branch executes.
Thank you everyone for your response. The issue is now fixed!
#DinoCoderSaurus (sorry, can't upvote yet) prompted me to realise that I had assumed (wrongly) that the data in the raw file would immediately begin with a .jpeg header (in fact it looks like the data begins with a hidden message, "surprise").
The Else condition was initially put there to avoid errors but of course it was prematurely exiting the While loop. The buffer was populated correctly after a couple of loops.
I then encountered the second problem (pointed out by #Some_programmer_dude) that counter++; was in the wrong place, which meant after the first new JPEG, no others could be written.
I'll also take your comments about best practice into consideration.
This program is supposed to take the input file and copy it to the output file and then go by 2 bit samples and change the volume of the input file and save the updated version in the output file. The input file copies into output.wav but won't change the volume of it. I know I'm fairly on track but can't figure out why it won't work correctly. This also passes check50 somehow but when I compile and run it myself it doesn't do what it's supposed to do.
// Modifies the volume of an audio file
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
// Number of bytes in .wav header
const int HEADER_SIZE = 44;
int main(int argc, char *argv[])
{
// Check command-line arguments
if (argc != 4)
{
printf("Usage: ./volume input.wav output.wav factor\n");
return 1;
}
// Open files and determine scaling factor
FILE *input = fopen(argv[1], "r");
if (input == NULL)
{
printf("Could not open file.\n");
return 1;
}
FILE *output = fopen(argv[2], "w");
if (output == NULL)
{
printf("Could not open file.\n");
return 1;
}
float factor = atof(argv[3]);
// TODO: Copy header from input file to output file
uint8_t header[HEADER_SIZE];
fread(header, HEADER_SIZE, 1, input);
fwrite(header, HEADER_SIZE, 1, output);
// TODO: Read samples from input file and write updated data to output file
int16_t buffer;
while(fread(&buffer, sizeof(int16_t), 1, input))
{
buffer = buffer * factor;
fwrite(&buffer, sizeof(int16_t), 1, output);
}
// Close files
fclose(input);
fclose(output);
}
http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html tells you that a wave file is a sequence of chunks. Each chunk starts with a 4 byte chunk id (chkid) followed by a 4 byte (little-endian) chunk size (cksize). There are different types of chunks, so I suggest you read chkid and cksize, then figure out out if you want process that chunk. For scaling you only want process the samples of ckkid = "data" and it assumes your file is wFormatTag = WAVE_FORMAT_PCM, i.e. PCM encoded.
You didn't tell us much about your input file so I grabbed the first file from https://samplelib.com/sample-wav.html. It consist of 3 chunks "RIFF", "fmt " and "data". The data samples in this file start at byte 44. This file has wBitsPerSample = 16 or 2 bytes per sample. You said "2 bit samples" but I assume you meant byte. When I run your program over this file with factor = 2 the first sample is 0xf872 which is -1934, and the corresponding output file sample is 0xf0e4 = -3868. When I visually compare the input and output files in a wave editor (in my case mhwaveedit) it looks scaled.
As I mentioned, buffer * factor may under or overflow, and in audio you usually clip it rather than wrap. Here is way to accomplish this:
printf("buffer = %d", buffer);
if(factor * buffer > INT16_MAX)
buffer = INT16_MAX;
else if(factor * buffer < INT16_MIN)
buffer = INT16_MIN;
else
buffer *= factor;
printf(" => %d\n", buffer);
And when I now run your program it clips:
$ ./clipping input.wav output.wav 2|egrep -- '-32768|32767' | head -1
buffer = 16661 => 32767
where that sample previously would overflow:
$ ./scaling input.wav output.wav 2|grep 16661 | head -1
buffer = 16661 => -32214
I am solved the pset4 recover in the CS50. Although I solved the problem, I am confuse between "buffer" & "&buffer". Please pay attention to "LINE AAAAA" & "LINE BBBBB".
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
// New type to store a byte of data
typedef uint8_t BYTE;
// Number of "block size" 512
const int BLOCK_SIZE = 512;
int main(int argc, char *argv[])
{
// Check for 2 command-line arguments
if (argc != 2)
{
printf("Usage: ./recover IMAGE\n");
return 1;
}
// Open card.raw file
FILE *input = fopen(argv[1],"r");
// Check for fail to open
if (input == NULL)
{
printf("Couldn't open the file.\n");
return 1;
}
// Read the first 4 bytes
BYTE buffer[BLOCK_SIZE];
// Count image
int count_image = 0;
// Assign NULL to output_file
FILE *output = NULL;
// Declare filename
char filename[8];
// LINE AAAAA
while (fread(buffer, sizeof(BYTE), BLOCK_SIZE, input) == BLOCK_SIZE)
{
// Check first 4 bytes for JPEG file format
if (buffer[0] == 0xff && buffer[1] == 0xd8 && buffer[2] == 0xff && (buffer[3] & 0xf0) == 0xe0)
{
// Only for first image, generate new filename and write into it
if (count_image == 0)
{
// Generate a new file with sequence name
sprintf(filename, "%03i.jpg", count_image);
// Open new file
output = fopen(filename, "w");
// LINE BBBBB
fwrite (&buffer, sizeof(BYTE), BLOCK_SIZE, output);
// Add filename counter
count_image++;
}
// For subsequence new repeat JPG images
// Close output file, generate new file, and write into it
else if (count_image > 0)
{
// fclose current writing files
fclose (output);
// Generate a new file with sequence name
sprintf(filename, "%03i.jpg", count_image);
// Open new file
output = fopen(filename, "w");
// LINE BBBBB
fwrite (&buffer, sizeof(BYTE), BLOCK_SIZE, output);
// Add filename counter
count_image++;
}
}
// Not fulfill the 4 bytes JPG condition, keep writing to the same filename
else if (count_image > 0)
{
// LINE BBBBB
fwrite (&buffer, sizeof(BYTE), BLOCK_SIZE, output);
}
}
fclose(output);
fclose(input);
}
Question:
Why do we use "&buffer" in LINE BBBBB instead of "buffer"?
I know LINE AAAAA is using "buffer" as BYTE buffer[BLOCK_SIZE] is an pointer or array. So "buffer" mean the location of the pointer.
When buffer is used, it often converts to the address of the first element. &buffer is the address of the array.
Those 2 addresses will compare equal, yet have different types. When the type is important, use the matching one. Enable all warnings to help identify incorrect type usage.
Since fread() use void *, either will work.
Conceptually use buffer in this case to match the sizeof(BYTE) and BLOCK_SIZE.
or
fread(&buffer, sizeof buffer, 1, input) == 1)
fwrite(buffer, ...) will work fine.
I am learning how to code and I have no experience with that at all. I've successful got to PSET4 and stuck on recover. I've read everything online about this problem and i found out that many people have similar code as I do and it works. Does not work for me whatsoever. Please have a look and give me a hint what did I do wrong and how to correct it.
Here is everything about the pset4 recover i downloaded their card.raw from here card.raw
/** recovering JPEG files from a memory card
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
typedef uint8_t BYTE;
int main(int argc, char* argv[])
{
// ensure proper usage
if (argc != 2)
{
fprintf(stderr,
"Usage: ./recover infile (the name of a forensic image from which to recover JPEGs)\n");
return 1;
}
// open input file (forensic image)
FILE* inptr = fopen(argv[1], "r");
if (inptr == NULL)
{
fprintf(stderr, "Could not open %s.\n", argv[1]);
return 2;
}
FILE* outptr = NULL;
// create a pointer array of 512 elements to store 512 bytes from the memory card
BYTE* buffer = malloc(sizeof(BYTE) * 512);
if (buffer == NULL)
{
return 3;
}
// count amount of jpeg files found
int jpeg = 0;
// string for a file name using sprintf
char filename[8] = { 0 };
// read memory card untill the end of file
while (fread(buffer, sizeof(BYTE) * 512, 1, inptr) != 0)
{
// check if jpeg is found
if (buffer[0] == 0xff && buffer[1] == 0xd8 && buffer[2] == 0xff
&& (buffer[3] >= 0xe0 || buffer[3] <= 0xef))
{
if (jpeg > 0)
{
fclose(outptr);
}
sprintf(filename, "%03d.JPEG", jpeg);
outptr = fopen(filename, "w");
jpeg++;
}
if (jpeg > 0)
{
fwrite(buffer, sizeof(BYTE) * 512, 1, outptr);
}
}
// free memory
free(buffer);
// close filename
fclose(outptr);
// close input file (forensic image)
fclose(inptr);
return 0;
}
The main problem is that you invoke undefined behavior because filename is not enough big. sprintf() need be 9 and 17 bytes with your code but you only has 8. So you have a buffer overflow.
Just change:
char filename[8] = { 0 };
to
char filename[17] = { 0 };
Because, you use an int, this value is implemented defined but in many system has an int with 32 bits. So the value possible are between -2^31 and 2^31 - 1 that make a maximum of 11 chars (-2147483648). We add the number of chars in ".JPEG", 5. We have 16 but you forget the null terminate byte of a c-string. So we are 17 maximum.
Modern compiler warning you: gcc version 7.1.1 20170516 (GCC):
In function ‘main’:
warning: ‘sprintf’ writing a terminating nul past the end of the destination [-Wformat-overflow ]
sprintf(filename, "%03d.JPEG", jpeg++);
^
note: ‘sprintf’ output between 9 and 17 bytes into a destination of size 8
sprintf(filename, "%03d.JPEG", jpeg++);
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Plus, your typedef is useless because a char world be always a byte in C. More than that you don't need a byte but an octet so like char, uint8_t would be always an octet in C. So you don't need typedef.
Again one thing, you allocate your buffer but it's useless because your buffer has a constant size. So just create an array is more simple.
#include <stdint.h>
#include <stdio.h>
int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "Usage: ./recover infile (the name of a forensic image "
"from which to recover JPEGs)\n");
return 1;
}
FILE *inptr = fopen(argv[1], "r");
if (inptr == NULL) {
fprintf(stderr, "Could not open %s.\n", argv[1]);
return 2;
}
FILE *outptr = NULL;
uint8_t buffer[512];
size_t const buffer_size = sizeof buffer / sizeof *buffer;
size_t jpeg = 0;
while (fread(buffer, sizeof *buffer, buffer_size, inptr) == buffer_size) {
if (buffer[0] == 0xff && buffer[1] == 0xd8 && buffer[2] == 0xff &&
buffer[3] == 0xe0) {
if (outptr != NULL) {
fclose(outptr);
}
char filename[26];
sprintf(filename, "%03zu.JPEG", jpeg++);
outptr = fopen(filename, "w");
}
if (outptr != NULL) {
fwrite(buffer, sizeof *buffer, buffer_size, outptr);
}
}
if (outptr != NULL) {
fwrite(buffer, sizeof *buffer, buffer_size, outptr);
}
if (outptr != NULL) {
fclose(outptr);
}
fclose(inptr);
}
Note: This example is clearly not perfect, this will be better to make a true parser for jpeg file to have a better control flow. Here we suppose that all gonna be right.
how do you know that an instance of a JPEG image will always end with '\n'? Or better, how do you know that a JPEG image will be an exact multiple of 512?
You dont know.
So the posted code needs to calculate the actual value OR use some method to have the last call to fread() for any specific JPEG instance, to stop reading at the end of that image,
Then the check for the ID bytes of the next JPEG image will find the next image.
Otherwise, the start of the next image is already written to the prior output file and the check for a new image will fail.
In general this will result in the last created file containing more than one image.
This link: 'https://en.wikipedia.org/wiki/JPEG_File_Interchange_Format' is a web page that describes the format of a JPEG file.
On every digital camera that I have used, the SD card has a directory of all the files.
Suggest using that directory and the info in the linked web page to find each JPEG image and to determine when the end of that image has been encountered. (I.E. the 0xFF 0xD9)