Skip to content

Instantly share code, notes, and snippets.

@RobSpectre
Created January 24, 2025 21:52
Show Gist options
  • Save RobSpectre/654ab6177c4a8248bc6910f27e3d1c1a to your computer and use it in GitHub Desktop.
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.
#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