Created
January 14, 2023 19:23
-
-
Save kuzux/d6d86f405195973419b9ee80a623b2b2 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stddef.h> | |
#include <stdint.h> | |
#include <stdbool.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <assert.h> | |
typedef struct { | |
uint8_t* buf; | |
int curr; | |
int len; | |
} stream_t; | |
typedef struct { | |
// all unchanged from the header, ids of these things | |
uint8_t id; | |
uint8_t level; | |
uint8_t bitrate; | |
uint8_t samplerate; | |
} frame_header_t; | |
// returns read byte | |
// or negative on error (eof) | |
int read_next_byte(stream_t* stream, uint8_t* byte) { | |
if(stream->curr == stream->len) return -1; | |
*byte = stream->buf[stream->curr++]; | |
return (int)*byte; | |
} | |
// returns header position | |
// or negative on error (eof) | |
int read_next_frame(stream_t* stream, frame_header_t* new_frame) { | |
uint8_t byte = 0x00; | |
while(true) { | |
uint8_t byte1; | |
int pos = stream->curr; | |
if(read_next_byte(stream, &byte1) < 0) break; | |
if(byte1 != 0xFF) continue; | |
uint8_t byte2; | |
if(read_next_byte(stream, &byte2) < 0) break; | |
if(byte2 & 0XF0 != 0xF0) continue; | |
uint8_t byte3, byte4; | |
if(read_next_byte(stream, &byte3) < 0) break; | |
if(read_next_byte(stream, &byte4) < 0) break; | |
new_frame->id = (byte2 >> 3) & 0x01; | |
new_frame->level = (byte2 >> 1) & 0x03; | |
new_frame->bitrate = (byte3 >> 4) & 0x0F; | |
new_frame->samplerate = (byte3 >> 2) & 0x03; | |
return pos; | |
} | |
return -1; | |
} | |
// returns 0 on success | |
// negative on unsupported frame | |
int is_frame_supported(const frame_header_t* frame) { | |
// only supporting MPEG I Level 3 | |
if(frame->id != 0x1) return -1; | |
if(frame->level != 0x1) return -2; | |
// invalid bitrate & samplerates | |
if(frame->bitrate == 0xF) return -3; | |
if(frame->samplerate == 0x3) return -4; | |
return 0; | |
} | |
// returns location of the TAG header | |
// or negative on error (not found) | |
int id3v1_header_location(stream_t* stream) { | |
while(true) { | |
int pos = stream->curr; | |
uint8_t byte1, byte2, byte3; | |
if(read_next_byte(stream, &byte1) < 0) break; | |
if(byte1 != 'T') { | |
continue; | |
} | |
if(read_next_byte(stream, &byte2) < 0) break; | |
if(byte2 != 'A') { | |
stream->curr--; | |
continue; | |
} | |
if(read_next_byte(stream, &byte3) < 0) break; | |
if(byte3 != 'G') { | |
stream->curr--; | |
continue; | |
} | |
return pos; | |
} | |
return -1; | |
} | |
int main(int argc, char** argv) { | |
assert(argc == 2); | |
char* filename = argv[1]; | |
FILE* fp = fopen(filename, "rb"); | |
assert(fp); | |
fseek(fp, 0, SEEK_END); | |
size_t filelen = ftell(fp); | |
fseek(fp, 0, SEEK_SET); | |
printf("%zu bytes in file\n", filelen); | |
uint8_t* buf = malloc(filelen); | |
size_t bytes_read = fread(buf, 1, filelen, fp); | |
assert(bytes_read == filelen); | |
stream_t stream = { buf, 0, filelen }; | |
int tag_pos = id3v1_header_location(&stream); | |
stream.curr = 0; | |
if(tag_pos > 0) { | |
printf("id3 header found at %d\n", tag_pos); | |
stream.len = tag_pos; | |
} else { | |
printf("id3 header not found\n"); | |
} | |
size_t num_frames = 0; | |
size_t valid_frames = 0; | |
while(true) { | |
frame_header_t header; | |
int pos = read_next_frame(&stream, &header); | |
if(pos < 0) break; | |
num_frames++; | |
if(is_frame_supported(&header) < 0) continue; | |
// printf("frame %zu at pos %d\n", num_frames, pos); | |
valid_frames++; | |
} | |
printf("%zu frames in total, %zu valid\n", num_frames, valid_frames); | |
free(buf); | |
fclose(fp); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment