Created
January 24, 2025 21:52
-
-
Save RobSpectre/654ab6177c4a8248bc6910f27e3d1c1a to your computer and use it in GitHub Desktop.
A C program that converts extracted Zello voice messages from their custom file format to Ogg Opus.
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 <stdio.h> | |
#include <stdlib.h> | |
#include <stdint.h> | |
#include <string.h> | |
#include <ogg/ogg.h> | |
#include <opus/opus.h> | |
#define SAMPLE_RATE_HZ 48000 | |
#define CHANNELS 1 | |
#define MAX_PACKET_SIZE 2048 | |
#define OPUS_HEAD_SIZE 19 | |
#define OPUS_TAGS_SIZE 84 | |
#define FRAME_SIZE_MS 20 | |
#define FRAME_PER_PACKET 1 | |
typedef struct { | |
uint16_t length; | |
uint16_t duration; | |
unsigned char data[MAX_PACKET_SIZE]; | |
} CustomPacket; | |
int read_custom_packet(FILE *input, CustomPacket *packet) { | |
if (fread(&packet->length, 2, 1, input) != 1) { | |
return -1; | |
} | |
if (fread(&packet->duration, 2, 1, input) != 1) { | |
return -1; | |
} | |
if(packet->length > MAX_PACKET_SIZE) { | |
printf("Packet Length Exceeds MAX_PACKET_SIZE: %d.\n", packet->length); | |
return -1; | |
} | |
if (fread(&packet->data, packet->length, 1, input) != 1) { | |
return -1; | |
} | |
return 0; | |
} | |
int main(int argc, char *argv[]) { | |
if(argc != 3) { | |
printf("Usage: %s <input_file> <output_file>\n", argv[0]); | |
return 1; | |
} | |
FILE *input_file = fopen(argv[1], "rb"); | |
if (!input_file) { | |
perror("Error opening input file"); | |
return 1; | |
} | |
FILE *output_file = fopen(argv[2], "wb"); | |
if (!output_file) { | |
perror("Error opening output file"); | |
fclose(input_file); | |
return 1; | |
} | |
ogg_stream_state os; | |
ogg_page og; | |
ogg_packet op; | |
srand(time(NULL)); | |
int serialno = rand(); | |
ogg_stream_init(&os, serialno); | |
// --- Opus Header --- | |
unsigned char opus_head[OPUS_HEAD_SIZE] = { | |
'O', 'p', 'u', 's', 'H', 'e', 'a', 'd', | |
1, // Version | |
CHANNELS, // Channels | |
(unsigned char)(SAMPLE_RATE_HZ & 0xFF), // Sample rate (LSB) | |
(unsigned char)((SAMPLE_RATE_HZ >> 8) & 0xFF), | |
(unsigned char)((SAMPLE_RATE_HZ >> 16) & 0xFF), | |
(unsigned char)((SAMPLE_RATE_HZ >> 24) & 0xFF), // Sample rate (MSB) | |
0, // Pre-skip | |
(unsigned char)(FRAME_PER_PACKET), // Frame Per Packet | |
(unsigned char)(FRAME_SIZE_MS), // Frame duration in milliseconds | |
}; | |
op.packet = opus_head; | |
op.bytes = OPUS_HEAD_SIZE; | |
op.b_o_s = 1; // Mark as beginning of stream | |
op.e_o_s = 0; | |
op.granulepos = 0; | |
op.packetno = 0; | |
ogg_stream_packetin(&os, &op); | |
// Generate and Write the first page | |
if (ogg_stream_pageout(&os, &og) > 0) { | |
fwrite(og.header, 1, og.header_len, output_file); | |
fwrite(og.body, 1, og.body_len, output_file); | |
} | |
// --- Opus Tags --- | |
unsigned char opus_tags[OPUS_TAGS_SIZE] = { | |
'O', 'p', 'u', 's', 'T', 'a', 'g', 's', | |
0x00, 0x00, 0x00, 0x04, // Vendor string length | |
'l','i','b','o', // Vendor string | |
0x00,0x00,0x00,0x00, // Number of tags | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00 | |
}; | |
op.packet = opus_tags; | |
op.bytes = OPUS_TAGS_SIZE; | |
op.b_o_s = 0; | |
op.e_o_s = 0; | |
op.granulepos = 0; | |
op.packetno = 1; | |
ogg_stream_packetin(&os, &op); | |
// Generate and Write the second page | |
if (ogg_stream_pageout(&os, &og) > 0) { | |
fwrite(og.header, 1, og.header_len, output_file); | |
fwrite(og.body, 1, og.body_len, output_file); | |
} | |
CustomPacket packet; | |
long packet_counter = 2; | |
long granule_pos = 0; | |
while(read_custom_packet(input_file, &packet) == 0) { | |
//printf("Read Packet Length %d, Duration %d\n", packet.length, packet.duration); | |
if(packet.duration == 0) { | |
// this is a header | |
uint16_t sample_rate = (packet.data[0] << 8) | packet.data[1]; | |
uint8_t frames_per_packet = packet.data[2]; | |
uint8_t frame_size = packet.data[3]; | |
printf("Config: sample_rate:%d frames/packet:%d frame_size:%d\n", | |
sample_rate, frames_per_packet, frame_size); | |
} else { | |
// this is an opus frame | |
op.packet = packet.data; | |
op.bytes = packet.length; | |
op.b_o_s = 0; | |
op.e_o_s = 0; | |
granule_pos += packet.duration / (FRAME_SIZE_MS / 1000.0); | |
op.granulepos = granule_pos; | |
op.packetno = packet_counter++; | |
ogg_stream_packetin(&os, &op); | |
while (ogg_stream_pageout(&os, &og) > 0) { | |
fwrite(og.header, 1, og.header_len, output_file); | |
fwrite(og.body, 1, og.body_len, output_file); | |
} | |
} | |
} | |
// Mark end of stream, and write the final page. | |
op.packet = NULL; | |
op.bytes = 0; | |
op.e_o_s = 1; | |
ogg_stream_packetin(&os, &op); | |
while (ogg_stream_pageout(&os, &og) > 0) { | |
fwrite(og.header, 1, og.header_len, output_file); | |
fwrite(og.body, 1, og.body_len, output_file); | |
} | |
ogg_stream_clear(&os); | |
fclose(input_file); | |
fclose(output_file); | |
printf("Conversion complete.\n"); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment