From 7474a9d472e214f315e1b0873672b2bedd1aa505 Mon Sep 17 00:00:00 2001 From: minjaesong Date: Tue, 7 Oct 2025 20:53:13 +0900 Subject: [PATCH] proper UCF writer --- terranmon.txt | 5 + video_encoder/create_ucf_payload.c | 296 ++++++++++++++++++++++++++--- 2 files changed, 274 insertions(+), 27 deletions(-) diff --git a/terranmon.txt b/terranmon.txt index 2ab4875..9317fc2 100644 --- a/terranmon.txt +++ b/terranmon.txt @@ -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) diff --git a/video_encoder/create_ucf_payload.c b/video_encoder/create_ucf_payload.c index 0cb6b3e..3c337df 100644 --- a/video_encoder/create_ucf_payload.c +++ b/video_encoder/create_ucf_payload.c @@ -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 @@ -9,8 +10,9 @@ #include #include -#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 \n", argv[0]); + if (argc < 3 || argc > 4) { + fprintf(stderr, "Usage: %s [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;