Skip to content

Deinterlacing support #58

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Feb 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions avpipe.go
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,7 @@ type XcParams struct {
Rotate int `json:"rotate"`
Profile string `json:"profile"`
Level int `json:"level"`
Deinterlace int `json:"deinterlace"`
}

// NewXcParams initializes a XcParams struct with unset/default values
Expand Down Expand Up @@ -1402,6 +1403,7 @@ func getCParams(params *XcParams) (*C.xcparams_t, error) {
rotate: C.int(params.Rotate),
profile: C.CString(params.Profile),
level: C.int(params.Level),
deinterlace: C.dif_type(params.Deinterlace),

// All boolean params are handled below
}
Expand Down
38 changes: 38 additions & 0 deletions doc/dev.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# DEVELOPER NOTES



## HOW TO

### Transcode a "content part" into ABR segments

```
./bin/exc -f hqp_3VQ52.mp4 -format dash -xc-type video -video-seg-duration-ts 120120
```

- output files: `./O/O1/`


### Transcode a "content part" into encrypted ABR segments


```
./bin/exc -f hqp_3VQ52.mp4 -format dash -xc-type video -video-seg-duration-ts 120120 -crypt-scheme cenc -crypt-key 13c396f04c947a0b0c9794f7f114a614 -crypt-kid 351a81dbaf704ab43ce7f3b4fbc603b3 -crypt-iv 9c33afa0bff3c27f671fa8e91f31f2c9
```

- output files: `./O/O1/`


## Special Use Cases

### Deinterlacing

Deinterlacing using the bwdiff "field" filter creates two frames for each input frame and requires adjusting the framerate.
When doubling the framerate the timebase (timescale) may not accommodate the new frame duration and it needs to be adjusted as well.

Deinterlace a source file (25fps) using CRF 16 and max_rate 20 Mbps. Note video timebase changes to 50 to accommodate new frame duration - this will be adjusted by ffmpeg to 12,800. The resulting frame duration is 265 (12800/50).

```
./bin/exc -f TestFile.mxf -format fmp4-segment -xc-type video -crf 16 -seg-duration 30.000 -force-keyint 100 -enc-height 1080 -enc-width 1920 -deinterlace 1 -video-time-base 50 -video-frame-duration-ts 256 -rc-max-rate 200000000 -rc-buffer-size 40000000
```

56 changes: 56 additions & 0 deletions doc/transcoding.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# AVPIPE TRANSCODING


## Job Types

The avpipe library is designed to accommodate specific transcoding operations.

### Mez creation - VOD

The input file can be any format and any size - this is the source media for the mezzanine.

The operation creates 30 second (approx) content parts, fragmented MP4, encoded at the desired resolution and bitrate, and conditioned with GOP and keyframe interval of approx 2 sec.


### Mez creation - Live

The input is a live stream RTMP, MPEGTS, SRT or HLS.
The operation creates 30 second (approx) content parts, fragmented MP4, encoded at the desired resolution and bitrate, and conditioned with GOP and keyframe interval of approx 2 sec.

### ABR segments

The input is a conditioned mez content part, as created by the mez creation operation

The output is a set of "CMAF" segments (2 sec approx) and the corresponding 'init' segment

### Mux-only File Maker

> TODO

### File Maker

> TODO

### Thumbnail Extraction

The input is a conditioned mez content part, as created by the mez creation operation.

The output is a set of individual frames.

## Common Use Cases

> TODO

## Special Use Cases

### Deinterlacing

Deinterlacing using the bwdif "field" filter creates two frames for each input frame and requires adjusting the framerate.

When doubling the framerate the timebase (timescale) may not accommodate the new frame duration and it needs to be adjusted as well.

For example if the input frame rate is 25 and timebase is 1/25, the input frame duration is 1. When deinterlacing using the bwdif "field" filter
we need to change the output timebase to 1/50 and the frame rate becomes 50 fps.

Note that ffmpeg will force the timebase denominator to be greater than 10,000 and will double our "1/50" timebase until it becomes 1/12800. The new frame duration,
for 50 fps, will be 256.
7 changes: 7 additions & 0 deletions elvxc/cmd/transcode.go
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,7 @@ func InitTranscode(cmdRoot *cobra.Command) error {
cmdTranscode.PersistentFlags().Int32("rotate", 0, "Rotate the output video frame (valid values 0, 90, 180, 270).")
cmdTranscode.PersistentFlags().StringP("profile", "", "", "Encoding profile for video. If it is not determined, it will be set automatically.")
cmdTranscode.PersistentFlags().Int32("level", 0, "Encoding level for video. If it is not determined, it will be set automatically.")
cmdTranscode.PersistentFlags().Int32("deinterlace", 0, "Deinterlace filter (values 0 - none, 1 - bwdif_field, 2 - bwdif_frame send_frame).")

return nil
}
Expand Down Expand Up @@ -621,6 +622,11 @@ func doTranscode(cmd *cobra.Command, args []string) error {

profile := cmd.Flag("profile").Value.String()

deinterlace, err := cmd.Flags().GetInt32("deinterlace")
if err != nil {
return fmt.Errorf("Invalid deinterlace value")
}

cryptScheme := avpipe.CryptNone
val := cmd.Flag("crypt-scheme").Value.String()
if len(val) > 0 {
Expand Down Expand Up @@ -719,6 +725,7 @@ func doTranscode(cmd *cobra.Command, args []string) error {
Rotate: int(rotate),
Profile: profile,
Level: int(level),
Deinterlace: int(deinterlace),
}

err = getAudioIndexes(params, audioIndex)
Expand Down
13 changes: 12 additions & 1 deletion exc/elv_xc.c
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,10 @@ in_stat(
if (debug_frame_level)
elv_dbg("IN STAT stream_index=%d, fd=%d, video frame read=%"PRId64, stream_index, fd, c->video_frames_read);
break;
case in_stat_first_keyframe_pts:
if (debug_frame_level)
elv_dbg("IN STAT fd=%d, first keyframe PTS=%"PRId64", url=%s", fd, c->first_key_frame_pts, c->url);
break;
case in_stat_data_scte35:
if (debug_frame_level)
elv_dbg("IN STAT stream_index=%d, fd=%d, data=%s", stream_index, fd, c->data);
Expand Down Expand Up @@ -1028,6 +1032,7 @@ usage(
"\t-d : (optional) Decoder name. For video default is \"h264\", can be: \"h264\", \"h264_cuvid\", \"jpeg2000\", \"hevc\"\n"
"\t For audio default is \"aac\", but for ts files should be set to \"ac3\"\n"
"\t-debug-frame-level : (optional) Enable/disable debug frame level. Default is 0, must be 0 or 1.\n"
"\t-deinterlace : (optional) Deinterlace filter. Default is 0 (none), can be: 1 (bwdif send_field), 2 (bwdif send_frame)\n"
"\t-duration-ts : (optional) Default: -1 (entire stream)\n"
"\t-e : (optional) Video encoder name. Default is \"libx264\", can be: \"libx264\", \"libx265\", \"h264_nvenc\", \"hevc_nvenc\", \"h264_videotoolbox\", or \"mjpeg\"\n"
"\t-enc-height : (optional) Default: -1 (use source height)\n"
Expand Down Expand Up @@ -1167,6 +1172,7 @@ main(
.start_fragment_index = 0, /* Default is zero */
.sync_audio_to_stream_id = -1, /* Default -1 (no sync to a video stream) */
.rotate = 0, /* Default 0 (means no transpose/rotation) */
.deinterlace = 0, /* Default 0 (no deinterlacing) */
.xc_type = xc_none,
.video_bitrate = -1, /* not used if using CRF */
.watermark_text = NULL,
Expand Down Expand Up @@ -1284,7 +1290,12 @@ main(
if (p.debug_frame_level != 0 && p.debug_frame_level != 1) {
usage(argv[0], argv[i], EXIT_FAILURE);
}
} else if (strlen(argv[i]) > 2) {
} else if (!strcmp(argv[i], "-deinterlace")) {
if (sscanf(argv[i+1], "%d", &p.deinterlace) != 1) {
usage(argv[0], argv[i], EXIT_FAILURE);
}
}
else if (strlen(argv[i]) > 2) {
usage(argv[0], argv[i], EXIT_FAILURE);
} else {
p.dcodec = strdup(argv[i+1]);
Expand Down
8 changes: 8 additions & 0 deletions libavpipe/include/avpipe_xc.h
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,13 @@ typedef enum image_type {
gif_image
} image_type;

// deinterlacing filter types
typedef enum dif_type {
dif_none = 0, // No deinterlacing
dif_bwdif = 1, // Use filter bwdif mode 'send_field' (two frames per input frame)
dif_bwdif_frame = 2 // Use filter bwdif mode 'send_frame' (one frame per input frame)
} dif_type;

#define DRAW_TEXT_SHADOW_OFFSET 0.075
#define MAX_EXTRACT_IMAGES_SZ 100

Expand Down Expand Up @@ -454,6 +461,7 @@ typedef struct xcparams_t {
int rotate; // For video transpose or rotation
char *profile;
int level;
dif_type deinterlace; // Deinterlacing filter
} xcparams_t;

#define MAX_CODEC_NAME 256
Expand Down
44 changes: 42 additions & 2 deletions libavpipe/src/avpipe_xc.c
Original file line number Diff line number Diff line change
Expand Up @@ -1182,6 +1182,7 @@ prepare_video_encoder(
out_stream->time_base = (AVRational) {1, params->video_time_base};
else
out_stream->time_base = in_stream->time_base;

out_stream->avg_frame_rate = decoder_context->format_context->streams[decoder_context->video_stream_index]->avg_frame_rate;
out_stream->codecpar->codec_tag = 0;

Expand Down Expand Up @@ -1237,13 +1238,15 @@ prepare_video_encoder(
encoder_codec_context->time_base = (AVRational) {1, params->video_time_base};
else
encoder_codec_context->time_base = decoder_context->codec_context[index]->time_base;

encoder_codec_context->sample_aspect_ratio = decoder_context->codec_context[index]->sample_aspect_ratio;
if (params->video_bitrate > 0)
encoder_codec_context->bit_rate = params->video_bitrate;
if (params->rc_buffer_size > 0)
encoder_codec_context->rc_buffer_size = params->rc_buffer_size;
if (params->rc_max_rate > 0)
encoder_codec_context->rc_max_rate = params->rc_max_rate;

encoder_codec_context->framerate = decoder_context->codec_context[index]->framerate;

// This needs to be set before open (ffmpeg samples have it wrong)
Expand Down Expand Up @@ -3416,6 +3419,42 @@ get_filter_str(
{
*filter_str = NULL;

// Validate filter compatibility
// Note these filters can theoretically be made to work together but not a real use case
if (params->rotate > 0 || params->deinterlace != dif_none) {
if ((params->watermark_text && *params->watermark_text != '\0') ||
(params->watermark_overlay && params->watermark_overlay[0] != '\0')) {
elv_err("Incompatible filter parameters - watermark not supported with rotate and deinterlacing");
return eav_param;
}
if (params->rotate > 0 && params->deinterlace != dif_none) {
elv_err("Incompatible filter parameters - both rotate and deinterlacing");
return eav_param;
}
if (params->deinterlace == dif_bwdif) {
// This filter needs to create two output frames for each input frame and
// requires the caller to specify the new frame duration (1/2 of input frame duration)
if (params->video_frame_duration_ts == 0) {
elv_err("Incorrect filter spec - deinterlacing requires frame duration");
return eav_param;
}
}
}

// General syntax for the bwdif filter:
// "bwdif=mode=send_frame:parity=auto:deint=all"
switch (params->deinterlace) {
case dif_bwdif:
*filter_str = strdup("bwdif=mode=send_field");
return eav_success;
case dif_bwdif_frame:
*filter_str = strdup("bwdif=mode=send_frame");
return eav_success;
default:
// Nothing to do
break;
}

if (params->rotate > 0) {
switch (params->rotate) {
case 90:
Expand Down Expand Up @@ -4633,7 +4672,8 @@ log_params(
"video_frame_duration_ts=%d "
"rotate=%d "
"profile=%s "
"level=%d",
"level=%d "
"deinterlace=%d",
params->stream_id, params->url,
avpipe_version(),
params->bypass_transcoding, params->skip_decoding,
Expand All @@ -4657,7 +4697,7 @@ log_params(
params->filter_descriptor,
params->extract_image_interval_ts, params->extract_images_sz,
1, params->video_time_base, params->video_frame_duration_ts, params->rotate,
params->profile ? params->profile : "", params->level);
params->profile ? params->profile : "", params->level, params->deinterlace);
elv_log("AVPIPE XCPARAMS %s", buf);
}

Expand Down