proper UCF writer

This commit is contained in:
minjaesong
2025-10-07 20:53:13 +09:00
parent abce002cdd
commit 7474a9d472
2 changed files with 274 additions and 27 deletions

View File

@@ -937,6 +937,11 @@ transmission capability, and region-of-interest coding.
When header-only file contain video packets, they should be presented as an Intro Movie
before the user-interactable selector (served by the UCF payoad)
## Packet Structure (all packets EXCEPT sync packets follow this structure; sync packets are one-byte packet)
uint8 Packet Type
uint32 Payload Size
* Payload
## Packet Types
0x10: I-frame (intra-coded frame)
0x11: P-frame (delta-coded frame)

View File

@@ -1,7 +1,8 @@
/**
* UCF Payload Writer for TAV Files
* Creates a 4KB UCF cue file for concatenated TAV files
* Usage: ./create_ucf_payload input.tav output.ucf
* TAV+UCF Payload Writer for TAV Files
* Creates a TAV header-only (32 bytes) + UCF cue file (4KB) for concatenated TAV files
* Total output size: 4096 bytes (32 + 4064)
* Usage: ./create_ucf_payload input.tav output.ucf [track_names.txt]
*/
#include <stdio.h>
@@ -9,8 +10,9 @@
#include <string.h>
#include <stdint.h>
#define UCF_SIZE 4096
#define TAV_OFFSET_BIAS UCF_SIZE
#define TAV_HEADER_SIZE 32
#define UCF_SIZE 4064
#define TAV_OFFSET_BIAS (TAV_HEADER_SIZE + UCF_SIZE)
#define TAV_MAGIC "\x1FTSVMTA" // Matches both TAV and TAP
typedef struct {
@@ -23,11 +25,76 @@ typedef struct {
// ... rest of header fields
} __attribute__((packed)) TAVHeader;
// Write TAV header-only payload (File Role = 1)
static void write_tav_header_only(FILE *out) {
uint8_t header[TAV_HEADER_SIZE] = {0};
// Magic: "\x1FTSVMTAV"
header[0] = 0x1F;
header[1] = 'T';
header[2] = 'S';
header[3] = 'V';
header[4] = 'M';
header[5] = 'T';
header[6] = 'A';
header[7] = 'V';
// Version: 5 (YCoCg-R perceptual)
header[8] = 5;
// Width: 560 (little-endian)
header[9] = 0x30;
header[10] = 0x02;
// Height: 448 (little-endian)
header[11] = 0xC0;
header[12] = 0x01;
// FPS: 30
header[13] = 30;
// Total Frames: 0xFFFFFFFF (still image marker / not applicable)
header[14] = 0xFF;
header[15] = 0xFF;
header[16] = 0xFF;
header[17] = 0xFF;
// Wavelet Filter Type: 1 (9/7 irreversible, default)
header[18] = 1;
// Decomposition Levels: 6
header[19] = 6;
// Quantiser Indices (Y, Co, Cg): 255 (not applicable for header-only)
header[20] = 0xFF;
header[21] = 0xFF;
header[22] = 0xFF;
// Extra Feature Flags: 0x80 (bit 7 = has no actual packets)
header[23] = 0x80;
// Video Flags: 0
header[24] = 0;
// Encoder quality level: 0
header[25] = 0;
// Channel layout: 0 (Y-Co-Cg)
header[26] = 0;
// Reserved[4]: zeros (27-30 already initialized to 0)
// File Role: 1 (header-only, UCF payload follows)
header[31] = 1;
fwrite(header, 1, TAV_HEADER_SIZE, out);
}
// Write UCF header
static void write_ucf_header(FILE *out, uint16_t num_cues) {
uint8_t magic[8] = {0x1F, 'T', 'S', 'V', 'M', 'U', 'C', 'F'};
uint8_t version = 1;
uint32_t cue_file_size = UCF_SIZE;
uint32_t cue_file_size = TAV_OFFSET_BIAS;
uint8_t reserved = 0;
fwrite(magic, 1, 8, out);
@@ -38,10 +105,8 @@ static void write_ucf_header(FILE *out, uint16_t num_cues) {
}
// Write UCF cue element (internal addressing, human+machine interactable)
static void write_cue_element(FILE *out, uint64_t offset, uint16_t track_num) {
uint8_t addressing_mode = 0x21; // 0x20 (human) | 0x01 (machine) | 0x02 (internal)
char name[16];
snprintf(name, sizeof(name), "Track %d", track_num);
static void write_cue_element(FILE *out, uint64_t offset, const char *name) {
uint8_t addressing_mode = 0x22; // 0x20 (human) | 0x01 (machine) | 0x02 (internal)
uint16_t name_len = strlen(name);
// Offset with 4KB bias
@@ -55,7 +120,77 @@ static void write_cue_element(FILE *out, uint64_t offset, uint16_t track_num) {
fwrite(&biased_offset, 6, 1, out);
}
// Find all TAV headers in the file
// Read track names from file (newline-delimited)
static char **read_track_names(const char *filename, int *count_out) {
FILE *f = fopen(filename, "r");
if (!f) {
return NULL;
}
char **names = NULL;
int count = 0;
int capacity = 16;
char line[256];
names = malloc(capacity * sizeof(char *));
if (!names) {
fclose(f);
return NULL;
}
while (fgets(line, sizeof(line), f)) {
// Remove trailing newline
size_t len = strlen(line);
if (len > 0 && line[len - 1] == '\n') {
line[len - 1] = '\0';
len--;
}
if (len > 0 && line[len - 1] == '\r') {
line[len - 1] = '\0';
len--;
}
// Skip empty lines
if (len == 0) {
continue;
}
// Expand capacity if needed
if (count >= capacity) {
capacity *= 2;
char **new_names = realloc(names, capacity * sizeof(char *));
if (!new_names) {
// Cleanup on failure
for (int i = 0; i < count; i++) {
free(names[i]);
}
free(names);
fclose(f);
return NULL;
}
names = new_names;
}
// Allocate and copy name
names[count] = strdup(line);
if (!names[count]) {
// Cleanup on failure
for (int i = 0; i < count; i++) {
free(names[i]);
}
free(names);
fclose(f);
return NULL;
}
count++;
}
fclose(f);
*count_out = count;
return names;
}
// Find all TAV headers in the file (with smart packet-wise skipping)
static int find_tav_headers(FILE *in, uint64_t **offsets_out) {
uint64_t *offsets = NULL;
int count = 0;
@@ -70,10 +205,18 @@ static int find_tav_headers(FILE *in, uint64_t **offsets_out) {
// Seek to beginning
fseek(in, 0, SEEK_SET);
uint64_t pos = 0;
uint8_t magic[8];
while (fread(magic, 1, 8, in) == 8) {
while (1) {
// Remember current position before reading
uint64_t pos = ftell(in);
// Try to read magic
if (fread(magic, 1, 8, in) != 8) {
// End of file
break;
}
// Check for TAV magic signature
if (memcmp(magic, TAV_MAGIC, 7) == 0 && (magic[7] == 'V' || magic[7] == 'P')) {
// Found TAV header
@@ -92,12 +235,47 @@ static int find_tav_headers(FILE *in, uint64_t **offsets_out) {
printf("Found TAV header at offset: 0x%lX (%lu)\n", pos, pos);
// Skip past this header (32 bytes total)
fseek(in, pos + 32, SEEK_SET);
pos += 32;
uint64_t packet_pos = pos + 32;
fseek(in, packet_pos, SEEK_SET);
// Smart packet-wise skipping
while (1) {
uint8_t packet_type;
if (fread(&packet_type, 1, 1, in) != 1) {
// End of file
break;
}
// Check if this is the start of next TAV file (0x1F is prohibited as packet type)
if (packet_type == 0x1F) {
// Rewind 1 byte to re-read as magic at the top of outer loop
fseek(in, packet_pos, SEEK_SET);
break;
}
// printf("TAV Packet 0x%02X at 0x%lX\n", packet_type, packet_pos);
// Sync packets (0xFE, 0xFF) have no payload size - they're single-byte packets
if (packet_type == 0xFE || packet_type == 0xFF) {
packet_pos += 1;
fseek(in, packet_pos, SEEK_SET);
continue;
}
// Read payload size (uint32, little-endian)
uint32_t payload_size = 0;
if (fread(&payload_size, 4, 1, in) != 1) {
// End of file
break;
}
// Skip packet: 1 byte (type) + 4 bytes (size) + payload_size
packet_pos += 1 + 4 + payload_size;
fseek(in, packet_pos, SEEK_SET);
}
} else {
// Move forward by 1 byte for next search
fseek(in, pos + 1, SEEK_SET);
pos++;
}
}
@@ -106,19 +284,39 @@ static int find_tav_headers(FILE *in, uint64_t **offsets_out) {
}
int main(int argc, char *argv[]) {
if (argc != 3) {
fprintf(stderr, "Usage: %s <input.tav> <output.ucf>\n", argv[0]);
if (argc < 3 || argc > 4) {
fprintf(stderr, "Usage: %s <input.tav> <output.ucf> [track_names.txt]\n", argv[0]);
fprintf(stderr, "Creates a 4KB UCF payload for concatenated TAV file\n");
fprintf(stderr, " track_names.txt: Optional file with track names (one per line)\n");
return 1;
}
const char *input_path = argv[1];
const char *output_path = argv[2];
const char *names_path = (argc == 4) ? argv[3] : NULL;
// Read track names if provided
char **track_names = NULL;
int num_names = 0;
if (names_path) {
track_names = read_track_names(names_path, &num_names);
if (track_names) {
printf("Loaded %d track name(s) from '%s'\n", num_names, names_path);
} else {
fprintf(stderr, "Warning: Could not read track names from '%s', using defaults\n", names_path);
}
}
// Open input file
FILE *in = fopen(input_path, "rb");
if (!in) {
fprintf(stderr, "Error: Cannot open input file '%s'\n", input_path);
if (track_names) {
for (int i = 0; i < num_names; i++) {
free(track_names[i]);
}
free(track_names);
}
return 1;
}
@@ -129,11 +327,24 @@ int main(int argc, char *argv[]) {
if (num_tracks < 0) {
fprintf(stderr, "Error: Failed to scan input file\n");
if (track_names) {
for (int i = 0; i < num_names; i++) {
free(track_names[i]);
}
free(track_names);
}
return 1;
}
if (num_tracks == 0) {
fprintf(stderr, "Error: No TAV headers found in input file\n");
free(offsets);
if (track_names) {
for (int i = 0; i < num_names; i++) {
free(track_names[i]);
}
free(track_names);
}
return 1;
}
@@ -144,25 +355,47 @@ int main(int argc, char *argv[]) {
if (!out) {
fprintf(stderr, "Error: Cannot create output file '%s'\n", output_path);
free(offsets);
if (track_names) {
for (int i = 0; i < num_names; i++) {
free(track_names[i]);
}
free(track_names);
}
return 1;
}
// Write TAV header-only payload (File Role = 1)
write_tav_header_only(out);
printf("Written TAV header-only payload (%d bytes)\n", TAV_HEADER_SIZE);
// Write UCF header
write_ucf_header(out, num_tracks);
// Write cue elements
for (int i = 0; i < num_tracks; i++) {
write_cue_element(out, offsets[i], i + 1);
printf("Written cue element: Track %d at offset 0x%lX (biased: 0x%lX)\n",
i + 1, offsets[i], offsets[i] + TAV_OFFSET_BIAS);
char default_name[32];
const char *name;
// Use custom name if available, otherwise generate default
if (track_names && i < num_names) {
name = track_names[i];
} else {
snprintf(default_name, sizeof(default_name), "Track %d", i + 1);
name = default_name;
}
write_cue_element(out, offsets[i], name);
printf("Written cue element: '%s' at offset 0x%lX (biased: 0x%lX)\n",
name, offsets[i], offsets[i] + TAV_OFFSET_BIAS);
}
// Get current file position
long current_pos = ftell(out);
// Fill remaining space with zeros to reach 4KB
if (current_pos < UCF_SIZE) {
size_t remaining = UCF_SIZE - current_pos;
// Fill remaining space with zeros to reach TAV header + 4KB UCF
size_t target_size = TAV_HEADER_SIZE + UCF_SIZE;
if (current_pos < target_size) {
size_t remaining = target_size - current_pos;
uint8_t *zeros = calloc(remaining, 1);
if (zeros) {
fwrite(zeros, 1, remaining, out);
@@ -173,9 +406,18 @@ int main(int argc, char *argv[]) {
fclose(out);
free(offsets);
printf("\nUCF payload created successfully: %s\n", output_path);
printf("File size: %d bytes (4KB)\n", UCF_SIZE);
printf("\nTo create seekable TAV file, prepend this UCF to your TAV file:\n");
// Clean up track names
if (track_names) {
for (int i = 0; i < num_names; i++) {
free(track_names[i]);
}
free(track_names);
}
printf("\nTAV+UCF payload created successfully: %s\n", output_path);
printf("File size: %zu bytes (TAV header: %d + UCF: %d)\n",
(size_t)(TAV_HEADER_SIZE + UCF_SIZE), TAV_HEADER_SIZE, UCF_SIZE);
printf("\nTo create seekable TAV file, prepend this payload to your concatenated TAV file:\n");
printf(" cat %s input.tav > output_seekable.tav\n", output_path);
return 0;