mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-03-07 11:51:49 +09:00
tavenc/dec: interlaced mode
This commit is contained in:
@@ -145,9 +145,9 @@ test_mpeg_motion: test_mpeg_motion.cpp
|
|||||||
tests: $(TEST_TARGETS)
|
tests: $(TEST_TARGETS)
|
||||||
|
|
||||||
# Build with debug symbols
|
# Build with debug symbols
|
||||||
debug: CFLAGS += -g -DDEBUG -fsanitize=address
|
debug: CFLAGS += -g -DDEBUG -fsanitize=address -fno-omit-frame-pointer
|
||||||
debug: DBGFLAGS += -fsanitize=address
|
debug: DBGFLAGS += -fsanitize=address -fno-omit-frame-pointer
|
||||||
debug: tav_new #$(TARGETS)
|
debug: $(TARGETS)
|
||||||
|
|
||||||
# Clean build artifacts
|
# Clean build artifacts
|
||||||
clean:
|
clean:
|
||||||
|
|||||||
@@ -1575,9 +1575,9 @@ int tav_video_decode_gop(tav_video_context_t *ctx,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply grain synthesis to Y channel ONLY (using ORIGINAL dimensions - grain must match encoder's frame size)
|
// Apply grain synthesis to Y channel ONLY (use final dimensions to match allocated buffer)
|
||||||
// Note: Grain synthesis is NOT applied to chroma channels
|
// Note: Grain synthesis is NOT applied to chroma channels
|
||||||
apply_grain_synthesis(gop_y[t], width, height, ctx->params.decomp_levels, t,
|
apply_grain_synthesis(gop_y[t], final_width, final_height, ctx->params.decomp_levels, t,
|
||||||
QLUT[ctx->params.quantiser_y], ctx->params.encoder_preset);
|
QLUT[ctx->params.quantiser_y], ctx->params.encoder_preset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -121,6 +121,8 @@ typedef struct {
|
|||||||
// TAV header info
|
// TAV header info
|
||||||
tav_header_t header;
|
tav_header_t header;
|
||||||
int perceptual_mode;
|
int perceptual_mode;
|
||||||
|
int interlaced; // 1 if video is interlaced (from video_flags bit 0)
|
||||||
|
int decode_height; // Actual decode height (half of header.height when interlaced)
|
||||||
|
|
||||||
// Video decoder context
|
// Video decoder context
|
||||||
tav_video_context_t *video_ctx;
|
tav_video_context_t *video_ctx;
|
||||||
@@ -214,10 +216,24 @@ static int read_tav_header(decoder_context_t *ctx) {
|
|||||||
int base_version = ctx->header.version & 0x07; // Remove temporal wavelet flag
|
int base_version = ctx->header.version & 0x07; // Remove temporal wavelet flag
|
||||||
ctx->perceptual_mode = (base_version == 5 || base_version == 6);
|
ctx->perceptual_mode = (base_version == 5 || base_version == 6);
|
||||||
|
|
||||||
|
// Detect interlaced mode from video_flags bit 0
|
||||||
|
ctx->interlaced = (ctx->header.video_flags & 0x01) ? 1 : 0;
|
||||||
|
|
||||||
|
// Calculate decode height: half of header height for interlaced video
|
||||||
|
// The header stores the full display height, but encoded frames are half-height
|
||||||
|
if (ctx->interlaced) {
|
||||||
|
ctx->decode_height = ctx->header.height / 2;
|
||||||
|
} else {
|
||||||
|
ctx->decode_height = ctx->header.height;
|
||||||
|
}
|
||||||
|
|
||||||
if (ctx->verbose) {
|
if (ctx->verbose) {
|
||||||
printf("=== TAV Header ===\n");
|
printf("=== TAV Header ===\n");
|
||||||
printf(" Version: %d\n", ctx->header.version);
|
printf(" Version: %d\n", ctx->header.version);
|
||||||
printf(" Resolution: %dx%d\n", ctx->header.width, ctx->header.height);
|
printf(" Resolution: %dx%d\n", ctx->header.width, ctx->header.height);
|
||||||
|
if (ctx->interlaced) {
|
||||||
|
printf(" Interlaced: yes (decode height: %d)\n", ctx->decode_height);
|
||||||
|
}
|
||||||
printf(" FPS: %d\n", ctx->header.fps);
|
printf(" FPS: %d\n", ctx->header.fps);
|
||||||
printf(" Total frames: %u\n", ctx->header.total_frames);
|
printf(" Total frames: %u\n", ctx->header.total_frames);
|
||||||
printf(" Wavelet filter: %d\n", ctx->header.wavelet_filter);
|
printf(" Wavelet filter: %d\n", ctx->header.wavelet_filter);
|
||||||
@@ -260,63 +276,126 @@ static int spawn_ffmpeg(decoder_context_t *ctx) {
|
|||||||
// Child process - execute FFmpeg
|
// Child process - execute FFmpeg
|
||||||
close(video_pipe_fd[1]); // Close write end
|
close(video_pipe_fd[1]); // Close write end
|
||||||
|
|
||||||
|
// For interlaced video: input is half-height fields, output is full-height interlaced
|
||||||
|
// For progressive video: input and output are both full-height
|
||||||
char video_size[32];
|
char video_size[32];
|
||||||
char framerate[16];
|
char framerate[16];
|
||||||
snprintf(video_size, sizeof(video_size), "%dx%d", ctx->header.width, ctx->header.height);
|
snprintf(video_size, sizeof(video_size), "%dx%d", ctx->header.width, ctx->decode_height);
|
||||||
snprintf(framerate, sizeof(framerate), "%d", ctx->header.fps);
|
snprintf(framerate, sizeof(framerate), "%d", ctx->header.fps);
|
||||||
|
|
||||||
// Redirect video pipe to fd 3
|
// Redirect video pipe to fd 3
|
||||||
dup2(video_pipe_fd[0], 3);
|
dup2(video_pipe_fd[0], 3);
|
||||||
close(video_pipe_fd[0]);
|
close(video_pipe_fd[0]);
|
||||||
|
|
||||||
if (ctx->output_raw) {
|
if (ctx->interlaced) {
|
||||||
// Raw video output (no compression)
|
// Interlaced mode: merge separate fields into interlaced frames
|
||||||
execl("/usr/bin/ffmpeg", "ffmpeg",
|
// tinterlace=interleave_top combines consecutive fields into interlaced frames
|
||||||
"-f", "rawvideo",
|
// Output will be full height (header.height) at half framerate
|
||||||
"-pixel_format", "rgb24",
|
// Field order is set to top-field-first to match encoder
|
||||||
"-video_size", video_size,
|
if (ctx->output_raw) {
|
||||||
"-framerate", framerate,
|
// Raw video output (no compression)
|
||||||
"-i", "pipe:3",
|
execl("/usr/bin/ffmpeg", "ffmpeg",
|
||||||
"-f", "u8",
|
"-f", "rawvideo",
|
||||||
"-ar", "32000",
|
"-pixel_format", "rgb24",
|
||||||
"-ac", "2",
|
"-video_size", video_size,
|
||||||
"-i", ctx->audio_temp_file,
|
"-framerate", framerate,
|
||||||
"-c:v", "rawvideo",
|
"-i", "pipe:3",
|
||||||
"-pixel_format", "rgb24",
|
"-f", "u8",
|
||||||
"-c:a", "pcm_u8",
|
"-ar", "32000",
|
||||||
"-f", "matroska",
|
"-ac", "2",
|
||||||
ctx->output_file,
|
"-i", ctx->audio_temp_file,
|
||||||
"-y",
|
"-vf", "tinterlace=interleave_top",
|
||||||
"-v", "warning",
|
"-field_order", "tt",
|
||||||
(char*)NULL);
|
"-c:v", "rawvideo",
|
||||||
|
"-pixel_format", "rgb24",
|
||||||
|
"-c:a", "pcm_u8",
|
||||||
|
"-f", "matroska",
|
||||||
|
ctx->output_file,
|
||||||
|
"-y",
|
||||||
|
"-v", "warning",
|
||||||
|
(char*)NULL);
|
||||||
|
} else {
|
||||||
|
// FFV1 output (lossless compression) with interlaced flag
|
||||||
|
execl("/usr/bin/ffmpeg", "ffmpeg",
|
||||||
|
"-f", "rawvideo",
|
||||||
|
"-pixel_format", "rgb24",
|
||||||
|
"-video_size", video_size,
|
||||||
|
"-framerate", framerate,
|
||||||
|
"-i", "pipe:3",
|
||||||
|
"-f", "u8",
|
||||||
|
"-ar", "32000",
|
||||||
|
"-ac", "2",
|
||||||
|
"-i", ctx->audio_temp_file,
|
||||||
|
"-vf", "tinterlace=interleave_top",
|
||||||
|
"-field_order", "tt",
|
||||||
|
"-color_range", "2",
|
||||||
|
"-c:v", "ffv1",
|
||||||
|
"-level", "3",
|
||||||
|
"-coder", "1",
|
||||||
|
"-context", "1",
|
||||||
|
"-g", "1",
|
||||||
|
"-slices", "24",
|
||||||
|
"-slicecrc", "1",
|
||||||
|
"-pixel_format", "rgb24",
|
||||||
|
"-color_range", "2",
|
||||||
|
"-c:a", "pcm_u8",
|
||||||
|
"-f", "matroska",
|
||||||
|
ctx->output_file,
|
||||||
|
"-y",
|
||||||
|
"-v", "warning",
|
||||||
|
(char*)NULL);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// FFV1 output (lossless compression)
|
// Progressive mode - simple passthrough
|
||||||
execl("/usr/bin/ffmpeg", "ffmpeg",
|
if (ctx->output_raw) {
|
||||||
"-f", "rawvideo",
|
// Raw video output (no compression)
|
||||||
"-pixel_format", "rgb24",
|
execl("/usr/bin/ffmpeg", "ffmpeg",
|
||||||
"-video_size", video_size,
|
"-f", "rawvideo",
|
||||||
"-framerate", framerate,
|
"-pixel_format", "rgb24",
|
||||||
"-i", "pipe:3",
|
"-video_size", video_size,
|
||||||
"-f", "u8",
|
"-framerate", framerate,
|
||||||
"-ar", "32000",
|
"-i", "pipe:3",
|
||||||
"-ac", "2",
|
"-f", "u8",
|
||||||
"-i", ctx->audio_temp_file,
|
"-ar", "32000",
|
||||||
"-color_range", "2",
|
"-ac", "2",
|
||||||
"-c:v", "ffv1",
|
"-i", ctx->audio_temp_file,
|
||||||
"-level", "3",
|
"-c:v", "rawvideo",
|
||||||
"-coder", "1",
|
"-pixel_format", "rgb24",
|
||||||
"-context", "1",
|
"-c:a", "pcm_u8",
|
||||||
"-g", "1",
|
"-f", "matroska",
|
||||||
"-slices", "24",
|
ctx->output_file,
|
||||||
"-slicecrc", "1",
|
"-y",
|
||||||
"-pixel_format", "rgb24",
|
"-v", "warning",
|
||||||
"-color_range", "2",
|
(char*)NULL);
|
||||||
"-c:a", "pcm_u8",
|
} else {
|
||||||
"-f", "matroska",
|
// FFV1 output (lossless compression)
|
||||||
ctx->output_file,
|
execl("/usr/bin/ffmpeg", "ffmpeg",
|
||||||
"-y",
|
"-f", "rawvideo",
|
||||||
"-v", "warning",
|
"-pixel_format", "rgb24",
|
||||||
(char*)NULL);
|
"-video_size", video_size,
|
||||||
|
"-framerate", framerate,
|
||||||
|
"-i", "pipe:3",
|
||||||
|
"-f", "u8",
|
||||||
|
"-ar", "32000",
|
||||||
|
"-ac", "2",
|
||||||
|
"-i", ctx->audio_temp_file,
|
||||||
|
"-color_range", "2",
|
||||||
|
"-c:v", "ffv1",
|
||||||
|
"-level", "3",
|
||||||
|
"-coder", "1",
|
||||||
|
"-context", "1",
|
||||||
|
"-g", "1",
|
||||||
|
"-slices", "24",
|
||||||
|
"-slicecrc", "1",
|
||||||
|
"-pixel_format", "rgb24",
|
||||||
|
"-color_range", "2",
|
||||||
|
"-c:a", "pcm_u8",
|
||||||
|
"-f", "matroska",
|
||||||
|
ctx->output_file,
|
||||||
|
"-y",
|
||||||
|
"-v", "warning",
|
||||||
|
(char*)NULL);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fprintf(stderr, "Error: Failed to execute FFmpeg\n");
|
fprintf(stderr, "Error: Failed to execute FFmpeg\n");
|
||||||
@@ -432,7 +511,8 @@ static int init_decoder_threads(decoder_context_t *ctx) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Pre-allocate frame buffers for each slot (assuming max GOP size of 32)
|
// Pre-allocate frame buffers for each slot (assuming max GOP size of 32)
|
||||||
size_t frame_size = ctx->header.width * ctx->header.height * 3;
|
// Use decode_height for interlaced video (half of header height)
|
||||||
|
size_t frame_size = ctx->header.width * ctx->decode_height * 3;
|
||||||
int max_gop_size = 32;
|
int max_gop_size = 32;
|
||||||
|
|
||||||
for (int i = 0; i < ctx->num_slots; i++) {
|
for (int i = 0; i < ctx->num_slots; i++) {
|
||||||
@@ -462,7 +542,7 @@ static int init_decoder_threads(decoder_context_t *ctx) {
|
|||||||
|
|
||||||
tav_video_params_t video_params = {
|
tav_video_params_t video_params = {
|
||||||
.width = ctx->header.width,
|
.width = ctx->header.width,
|
||||||
.height = ctx->header.height,
|
.height = ctx->decode_height, // Use decode_height for interlaced video
|
||||||
.decomp_levels = ctx->header.decomp_levels,
|
.decomp_levels = ctx->header.decomp_levels,
|
||||||
.temporal_levels = 2,
|
.temporal_levels = 2,
|
||||||
.wavelet_filter = ctx->header.wavelet_filter,
|
.wavelet_filter = ctx->header.wavelet_filter,
|
||||||
|
|||||||
@@ -138,6 +138,8 @@ typedef struct {
|
|||||||
char *fontrom_high;
|
char *fontrom_high;
|
||||||
int separate_audio_track;
|
int separate_audio_track;
|
||||||
int use_native_audio; // PCM8 instead of TAD
|
int use_native_audio; // PCM8 instead of TAD
|
||||||
|
int interlaced; // Interlaced mode (half-height internally, full height in header)
|
||||||
|
int header_height; // Height to write to header (may differ from enc_params.height when interlaced)
|
||||||
|
|
||||||
// Audio encoding
|
// Audio encoding
|
||||||
int has_audio;
|
int has_audio;
|
||||||
@@ -318,6 +320,7 @@ static void print_usage(const char *program) {
|
|||||||
printf(" --fontrom-low FILE Font ROM for low ASCII (.chr)\n");
|
printf(" --fontrom-low FILE Font ROM for low ASCII (.chr)\n");
|
||||||
printf(" --fontrom-high FILE Font ROM for high ASCII (.chr)\n");
|
printf(" --fontrom-high FILE Font ROM for high ASCII (.chr)\n");
|
||||||
printf(" --suppress-xhdr Suppress Extended Header packet (enabled by default)\n");
|
printf(" --suppress-xhdr Suppress Extended Header packet (enabled by default)\n");
|
||||||
|
printf(" --interlaced Enable interlaced video mode (half-height encoding)\n");
|
||||||
printf(" -v, --verbose Verbose output\n");
|
printf(" -v, --verbose Verbose output\n");
|
||||||
printf(" --help Show this help\n");
|
printf(" --help Show this help\n");
|
||||||
printf("\nExamples:\n");
|
printf("\nExamples:\n");
|
||||||
@@ -385,12 +388,35 @@ static int get_video_info(const char *input_file, int *width, int *height,
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Open FFmpeg pipe for reading RGB24 frames.
|
* Open FFmpeg pipe for reading RGB24 frames.
|
||||||
|
*
|
||||||
|
* When interlaced=1:
|
||||||
|
* - full_height is the full display height (written to header)
|
||||||
|
* - FFmpeg outputs half-height frames via tinterlace+separatefields
|
||||||
|
* - Filtergraph: scale/crop to full size, then tinterlace weave halves
|
||||||
|
* framerate, then separatefields restores framerate at half height
|
||||||
*/
|
*/
|
||||||
static FILE* open_ffmpeg_pipe(const char *input_file, int width, int height) {
|
static FILE* open_ffmpeg_pipe(const char *input_file, int width, int height,
|
||||||
|
int interlaced, int full_height) {
|
||||||
char cmd[MAX_PATH * 2];
|
char cmd[MAX_PATH * 2];
|
||||||
snprintf(cmd, sizeof(cmd),
|
|
||||||
"ffmpeg -hide_banner -v quiet -i \"%s\" -f rawvideo -pix_fmt rgb24 -vf \"scale=%d:%d:force_original_aspect_ratio=increase,crop=%d:%d\" -",
|
if (interlaced) {
|
||||||
input_file, width, height, width, height);
|
// Interlaced mode filtergraph:
|
||||||
|
// 1. scale and crop to full size (width x full_height)
|
||||||
|
// 2. tinterlace interleave_top:cvlpf - weave fields, halves framerate
|
||||||
|
// 3. separatefields - separate into half-height frames, doubles framerate back
|
||||||
|
// Final output: width x (full_height/2) at original framerate
|
||||||
|
snprintf(cmd, sizeof(cmd),
|
||||||
|
"ffmpeg -hide_banner -v quiet -i \"%s\" -f rawvideo -pix_fmt rgb24 -vf "
|
||||||
|
"\"scale=%d:%d:force_original_aspect_ratio=increase,crop=%d:%d,"
|
||||||
|
"tinterlace=interleave_top:cvlpf,separatefields\" -",
|
||||||
|
input_file, width, full_height, width, full_height);
|
||||||
|
} else {
|
||||||
|
// Progressive mode - simple scale and crop
|
||||||
|
snprintf(cmd, sizeof(cmd),
|
||||||
|
"ffmpeg -hide_banner -v quiet -i \"%s\" -f rawvideo -pix_fmt rgb24 -vf "
|
||||||
|
"\"scale=%d:%d:force_original_aspect_ratio=increase,crop=%d:%d\" -",
|
||||||
|
input_file, width, height, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
FILE *fp = popen(cmd, "r");
|
FILE *fp = popen(cmd, "r");
|
||||||
if (!fp) {
|
if (!fp) {
|
||||||
@@ -427,8 +453,15 @@ static int read_rgb_frame(FILE *fp, uint8_t *rgb_frame, size_t frame_size) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Write TAV file header.
|
* Write TAV file header.
|
||||||
|
*
|
||||||
|
* When interlaced mode is enabled:
|
||||||
|
* - header_height should be the full display height (e.g., 448)
|
||||||
|
* - params->height is the internal encoding height (e.g., 224)
|
||||||
|
* - video_flags bit 0 is set to indicate interlaced
|
||||||
*/
|
*/
|
||||||
static int write_tav_header(FILE *fp, const tav_encoder_params_t *params, int has_audio, int has_subtitles) {
|
static int write_tav_header(FILE *fp, const tav_encoder_params_t *params,
|
||||||
|
int has_audio, int has_subtitles,
|
||||||
|
int interlaced, int header_height) {
|
||||||
// Magic (8 bytes: \x1FTSVMTAV)
|
// Magic (8 bytes: \x1FTSVMTAV)
|
||||||
fwrite(TAV_MAGIC, 1, 8, fp);
|
fwrite(TAV_MAGIC, 1, 8, fp);
|
||||||
|
|
||||||
@@ -469,8 +502,11 @@ static int write_tav_header(FILE *fp, const tav_encoder_params_t *params, int ha
|
|||||||
fwrite(&width, sizeof(uint16_t), 1, fp);
|
fwrite(&width, sizeof(uint16_t), 1, fp);
|
||||||
|
|
||||||
// Height (uint16_t, 2 bytes)
|
// Height (uint16_t, 2 bytes)
|
||||||
|
// For interlaced mode, write the full display height (header_height)
|
||||||
|
// For progressive mode, write params->height
|
||||||
// Write 0 if height exceeds 65535 (extended dimensions will be in XDIM)
|
// Write 0 if height exceeds 65535 (extended dimensions will be in XDIM)
|
||||||
uint16_t height = (params->height > 65535) ? 0 : (uint16_t)params->height;
|
int actual_height = interlaced ? header_height : params->height;
|
||||||
|
uint16_t height = (actual_height > 65535) ? 0 : (uint16_t)actual_height;
|
||||||
fwrite(&height, sizeof(uint16_t), 1, fp);
|
fwrite(&height, sizeof(uint16_t), 1, fp);
|
||||||
|
|
||||||
// FPS (uint8_t, 1 byte) - simplified to just fps_num
|
// FPS (uint8_t, 1 byte) - simplified to just fps_num
|
||||||
@@ -499,7 +535,9 @@ static int write_tav_header(FILE *fp, const tav_encoder_params_t *params, int ha
|
|||||||
fputc(extra_flags, fp);
|
fputc(extra_flags, fp);
|
||||||
|
|
||||||
// Video flags (uint8_t, 1 byte)
|
// Video flags (uint8_t, 1 byte)
|
||||||
uint8_t video_flags = 0; // Progressive, non-NTSC, lossy
|
// Bit 0 = interlaced, Bit 1 = NTSC framerate, Bit 2 = lossless, etc.
|
||||||
|
uint8_t video_flags = 0;
|
||||||
|
if (interlaced) video_flags |= 0x01; // Bit 0: interlaced
|
||||||
fputc(video_flags, fp);
|
fputc(video_flags, fp);
|
||||||
|
|
||||||
// Quality level (uint8_t, 1 byte)
|
// Quality level (uint8_t, 1 byte)
|
||||||
@@ -1379,7 +1417,9 @@ static int encode_video_mt(cli_context_t *cli) {
|
|||||||
printf("Opening FFmpeg pipe...\n");
|
printf("Opening FFmpeg pipe...\n");
|
||||||
cli->ffmpeg_pipe = open_ffmpeg_pipe(cli->input_file,
|
cli->ffmpeg_pipe = open_ffmpeg_pipe(cli->input_file,
|
||||||
cli->enc_params.width,
|
cli->enc_params.width,
|
||||||
cli->enc_params.height);
|
cli->enc_params.height,
|
||||||
|
cli->interlaced,
|
||||||
|
cli->header_height);
|
||||||
if (!cli->ffmpeg_pipe) {
|
if (!cli->ffmpeg_pipe) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -1468,11 +1508,14 @@ static int encode_video_mt(cli_context_t *cli) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Write TAV header
|
// Write TAV header
|
||||||
write_tav_header(cli->output_fp, &cli->enc_params, cli->has_audio, cli->subtitles != NULL);
|
write_tav_header(cli->output_fp, &cli->enc_params, cli->has_audio, cli->subtitles != NULL,
|
||||||
|
cli->interlaced, cli->header_height);
|
||||||
|
|
||||||
// Write Extended Header (unless suppressed)
|
// Write Extended Header (unless suppressed)
|
||||||
|
// For interlaced mode, use header_height for XDIM if needed
|
||||||
|
int xhdr_height = cli->interlaced ? cli->header_height : cli->enc_params.height;
|
||||||
if (!cli->suppress_xhdr) {
|
if (!cli->suppress_xhdr) {
|
||||||
cli->extended_header_offset = write_extended_header(cli, cli->enc_params.width, cli->enc_params.height);
|
cli->extended_header_offset = write_extended_header(cli, cli->enc_params.width, xhdr_height);
|
||||||
if (cli->extended_header_offset < 0) {
|
if (cli->extended_header_offset < 0) {
|
||||||
fprintf(stderr, "Warning: Failed to write Extended Header\n");
|
fprintf(stderr, "Warning: Failed to write Extended Header\n");
|
||||||
}
|
}
|
||||||
@@ -1838,7 +1881,9 @@ static int encode_video(cli_context_t *cli) {
|
|||||||
printf("Opening FFmpeg pipe...\n");
|
printf("Opening FFmpeg pipe...\n");
|
||||||
cli->ffmpeg_pipe = open_ffmpeg_pipe(cli->input_file,
|
cli->ffmpeg_pipe = open_ffmpeg_pipe(cli->input_file,
|
||||||
cli->enc_params.width,
|
cli->enc_params.width,
|
||||||
cli->enc_params.height);
|
cli->enc_params.height,
|
||||||
|
cli->interlaced,
|
||||||
|
cli->header_height);
|
||||||
if (!cli->ffmpeg_pipe) {
|
if (!cli->ffmpeg_pipe) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -1925,11 +1970,14 @@ static int encode_video(cli_context_t *cli) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Write TAV header (with actual encoder params)
|
// Write TAV header (with actual encoder params)
|
||||||
write_tav_header(cli->output_fp, &cli->enc_params, cli->has_audio, cli->subtitles != NULL);
|
write_tav_header(cli->output_fp, &cli->enc_params, cli->has_audio, cli->subtitles != NULL,
|
||||||
|
cli->interlaced, cli->header_height);
|
||||||
|
|
||||||
// Write Extended Header (unless suppressed)
|
// Write Extended Header (unless suppressed)
|
||||||
|
// For interlaced mode, use header_height for XDIM if needed
|
||||||
|
int xhdr_height_st = cli->interlaced ? cli->header_height : cli->enc_params.height;
|
||||||
if (!cli->suppress_xhdr) {
|
if (!cli->suppress_xhdr) {
|
||||||
cli->extended_header_offset = write_extended_header(cli, cli->enc_params.width, cli->enc_params.height);
|
cli->extended_header_offset = write_extended_header(cli, cli->enc_params.width, xhdr_height_st);
|
||||||
if (cli->extended_header_offset < 0) {
|
if (cli->extended_header_offset < 0) {
|
||||||
fprintf(stderr, "Warning: Failed to write Extended Header\n");
|
fprintf(stderr, "Warning: Failed to write Extended Header\n");
|
||||||
}
|
}
|
||||||
@@ -2237,6 +2285,7 @@ int main(int argc, char *argv[]) {
|
|||||||
{"tiled", no_argument, 0, 1029},
|
{"tiled", no_argument, 0, 1029},
|
||||||
{"suppress-xhdr", no_argument, 0, 1030},
|
{"suppress-xhdr", no_argument, 0, 1030},
|
||||||
{"threads", required_argument, 0, 't'},
|
{"threads", required_argument, 0, 't'},
|
||||||
|
{"interlaced", no_argument, 0, 1031},
|
||||||
{"help", no_argument, 0, '?'},
|
{"help", no_argument, 0, '?'},
|
||||||
{0, 0, 0, 0}
|
{0, 0, 0, 0}
|
||||||
};
|
};
|
||||||
@@ -2384,6 +2433,9 @@ int main(int argc, char *argv[]) {
|
|||||||
case 1030: // --suppress-xhdr
|
case 1030: // --suppress-xhdr
|
||||||
cli.suppress_xhdr = 1;
|
cli.suppress_xhdr = 1;
|
||||||
break;
|
break;
|
||||||
|
case 1031: // --interlaced
|
||||||
|
cli.interlaced = 1;
|
||||||
|
break;
|
||||||
case 't': { // --threads
|
case 't': { // --threads
|
||||||
int threads = atoi(optarg);
|
int threads = atoi(optarg);
|
||||||
if (threads < 0 || threads > MAX_THREADS) {
|
if (threads < 0 || threads > MAX_THREADS) {
|
||||||
@@ -2435,6 +2487,20 @@ int main(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle interlaced mode: store full height for header, use half-height internally
|
||||||
|
if (cli.interlaced) {
|
||||||
|
// Store full height for the header
|
||||||
|
cli.header_height = cli.enc_params.height;
|
||||||
|
// Use half-height internally (FFmpeg will output half-height frames)
|
||||||
|
cli.enc_params.height = cli.enc_params.height / 2;
|
||||||
|
printf("Interlaced mode: header=%dx%d, internal=%dx%d\n",
|
||||||
|
cli.enc_params.width, cli.header_height,
|
||||||
|
cli.enc_params.width, cli.enc_params.height);
|
||||||
|
} else {
|
||||||
|
// Progressive mode: header_height equals internal height
|
||||||
|
cli.header_height = cli.enc_params.height;
|
||||||
|
}
|
||||||
|
|
||||||
// Set audio quality to match video quality if not specified
|
// Set audio quality to match video quality if not specified
|
||||||
if (cli.audio_quality < 0) {
|
if (cli.audio_quality < 0) {
|
||||||
cli.audio_quality = cli.enc_params.quality_level; // Match luma quality
|
cli.audio_quality = cli.enc_params.quality_level; // Match luma quality
|
||||||
|
|||||||
Reference in New Issue
Block a user