Skip to content
Open
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
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Note: Once installed in a [python environment](https://docs.python.org/3/library
7. Upload batch content
8. Download database content

*_Important_*: the following workflow has been implemented while generating HEVC test content. Although it hasn't been tested with AVC content, it is expected to work exactly the same. For audio content, a [separate set of instructions](https://github.com/cta-wave/Test-Content-Generation/blob/master/Instructions/audio.md) is available. It is suggested to use this worflow for all content future generation.
*_Important_*: the following workflow has been implemented while generating HEVC test content. Although it hasn't been tested thoroughly with other codecs, it is expected to work exactly the same. For audio content, a [separate set of instructions](https://github.com/cta-wave/Test-Content-Generation/blob/master/Instructions/audio.md) is available. It is suggested to use this worflow for all content future generation.


### 1. Download mezzanine content
Expand Down Expand Up @@ -67,7 +67,6 @@ Batch files used to produce reference content is stored in the [./profiles](prof

### 3. Batch encode/package content


#### 3.1 Video content

typical usage of `tcgen encode`:
Expand All @@ -81,7 +80,7 @@ For detail on each available options use : `tcgen encode --help`



The encoding and packaging is performed using [GPAC](http://gpac.io), leveraging [libavcodec](https://ffmpeg.org/libavcodec.html) with [x264](http://www.videolan.org/developers/x264.html) and [x265](https://www.x265.org/) to generate the CMAF content along with a DASH manifest. The intent is to keep the size of the post-processing (e.g. manifest manipulation) as small as possible.
The encoding and packaging is performed using [GPAC](http://gpac.io), leveraging [libavcodec](https://ffmpeg.org/libavcodec.html) with [x264](http://www.videolan.org/developers/x264.html), [x265](https://www.x265.org/) and [VVenC](https://github.com/fraunhoferhhi/vvenc) to generate the CMAF content along with a DASH manifest. The intent is to keep the size of the post-processing (e.g. manifest manipulation) as small as possible.



Expand Down
12 changes: 12 additions & 0 deletions profiles/vvc.mezzanine_v4.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Stream ID,mezzanine radius,pic timing,VUI timing,sample entry,CMAF frag dur,init constraints,frag_type,resolution,framerate,bitrate,duration,cmaf_profile,wave_profile,cenc,sar,mezzanine_prefix_25HZ,mezzanine_prefix_30HZ
1,L1_1920x1080,False,True,hvc1,2.0,multiple,duration,1920x1080,1,8000,30.0,cmf2,vvc1,False,1/1,croatia_,tos_
#1_enc,L1_1920x1080,False,True,hvc1,2.0,multiple,duration,1920x1080,1,8000,30.0,cmf2,vvc1,True,1/1,croatia_,tos_
#2,L1_1920x1080,True,False,hev1,2.0,single,pframes,1920x1080,1,8000,30.0,cmfc,vvc1,False,1/1,croatia_,tos_
#3,L1_1920x1080,True,True,hvc1,2.0,multiple,duration,1920x1080,1,8000,30.0,cmf2,vvc1,False,1/1,croatia_,tos_
#4,L1_1920x1080,False,False,hvc1,2.0,multiple,duration,1920x1080,1,8000,30.0,cmf2,vvc1,False,1/1,croatia_,tos_
#5,L1_1920x1080,False,True,hev1,2.0,multiple,duration,1920x1080,1,8000,30.0,cmf2,vvc1,False,1/1,croatia_,tos_
#6,L1_1920x1080,False,True,hvc1,2.0,multiple,pframes,1920x1080,1,8000,30.0,cmf2,vvc1,False,1/1,croatia_,tos_
#7,L1_1920x1080,False,True,hvc1,2.0,multiple,duration,1920x1080,1,8000,30.0,cmfc,vvc1,False,1/1,croatia_,tos_
#10,L1_1920x1080,False,True,hvc1,2.0,multiple,duration,1920x1080,1,8000,60.0,cmf2,vvc1,False,1/1,croatia_,tos_
#11,L1_1920x1080,False,True,hvc1,2.0,multiple,duration,1920x1080,0.5,5000,60.0,cmf2,vvc1,False,1/1,croatia_,tos_
#12,J1_1280x720,False,True,hvc1,2.0,multiple,duration,1280x720,0.5,3100,60.0,cmf2,vvc1,False,1/1,croatia_,tos_
8 changes: 7 additions & 1 deletion src/tcgen/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"cud1": ("video", "h265", None),
"clg1": ("video", "h265", None),
"chd1": ("video", "h265", None),
"vvc1": ("video", "h266", None),
"caac": ("audio", "aac", "caac"),
"dts1": ("audio", "copy", None),
"dts2": ("audio", "copy", None)
Expand Down Expand Up @@ -239,6 +240,7 @@ class CmafBrand(str, Enum):
CUD1: str = "cud1"
CLG1: str = "clg1"
CHD1: str = "chd1"
VVCCHD: str = "vvc1"

def __str__(self):
return self.value
Expand All @@ -253,6 +255,10 @@ def from_string(cls, s):
return cls.CLG1
elif s == "chd1" :
return cls.CHD1
elif s == "vvc1" :
return cls.VVCCHD
else:
raise Exception(f'unknown CMAF media profile: {s}')

def locate_source_content(tc:'TestContent', fps_family:FPS_FAMILY):
m = tc.get_mezzanine(fps_family)
Expand Down Expand Up @@ -556,7 +562,7 @@ def from_matrix_column(col) -> 'TestContent':
# 13 - Duration of stream
duration = float(col[12].rstrip('s')) if bool(col[12]) else -1

# 14 - AVC/HEVC profile and level
# 14 - codec profile and level
codec = col[13]

# 15 - CMAF media profile
Expand Down
66 changes: 52 additions & 14 deletions src/tcgen/run_encode.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
class VideoCodecOptions(Enum):
AVC = "h264"
HEVC = "h265"
VVC = "h266"

class AudioCodecOptions(Enum):
AAC = "aac"
Expand Down Expand Up @@ -105,6 +106,16 @@ class HEVCCHD1:
m_resolution_h = "2160"
m_frame_rate = 60

# VVC: ISO/IEC 23000-19
class VVCCHD:
m_profile = "main10"
m_level = "41"
m_color_primary = "9" # GF_COLOR_PRIM_BT2020
m_color_trc = "16" # GF_COLOR_TRC_SMPTE2084
m_colorspace = "9" # GF_COLOR_MX_BT2020_NCL
m_resolution_w = "1920"
m_resolution_h = "1080"
m_frame_rate = 60

# DASHing
class DASH:
Expand Down Expand Up @@ -245,17 +256,18 @@ def __init__(self, representation_config):
self.m_media_type = value
elif name == "codec":
if value != VideoCodecOptions.AVC.value and value != VideoCodecOptions.HEVC.value \
and value != VideoCodecOptions.VVC.value \
and value != AudioCodecOptions.AAC.value and value != AudioCodecOptions.COPY.value:
print("Supported codecs are AVC denoted by \"h264\" and HEVC denoted by \"h265\" for video, and "
"AAC denoted by \"aac\" or the special value \"copy\" disables the audio transcoding.")
print("Supported codecs are AVC denoted by \"h264\", HEVC (\"h265\") and VVC (\"h266\") for video, and "
"AAC denoted by \"aac\" or the special value \"copy\" disables the audio transcoding. Found \"" + value + "\".")
sys.exit(1)
self.m_codec = value
elif name == "vse":
if value != VisualSampleEntry.AVC1.value and value != VisualSampleEntry.AVC3.value and \
value != VisualSampleEntry.AVC1p3.value and \
value != VisualSampleEntry.HEV1.value and value != VisualSampleEntry.HVC1.value:
print("Supported video sample entries for AVC are \"avc1\", \"avc3\", \"avc1+3\" and"
" for HEVC \"hev1\" and \"hvc1\".")
" for HEVC \"hev1\" and \"hvc1\".") # TODO: add 'vvc1' and 'vvi1'
sys.exit(1)
else:
self.m_video_sample_entry = value
Expand Down Expand Up @@ -358,6 +370,22 @@ def __init__(self, representation_config):
if self.m_resolution_w is None and self.m_resolution_h is None:
self.m_resolution_w = HEVCCLG1.m_resolution_w
self.m_resolution_h = HEVCCLG1.m_resolution_h
elif value == "vvc1":
if self.m_profile is None:
self.m_profile = VVCCHD.m_profile
if self.m_level is None:
self.m_level = VVCCHD.m_level
if self.m_frame_rate is None:
self.m_frame_rate = VVCCHD.m_frame_rate
if self.m_color_primary is None:
self.m_color_primary = VVCCHD.m_color_primary
if self.m_color_trc is None:
self.m_color_trc = VVCCHD.m_color_trc
if self.m_colorspace is None:
self.m_colorspace = VVCCHD.m_colorspace
if self.m_resolution_w is None and self.m_resolution_h is None:
self.m_resolution_w = VVCCHD.m_resolution_w
self.m_resolution_h = VVCCHD.m_resolution_h
else:
print("Unknown CMAF profile: " + name)

Expand Down Expand Up @@ -431,6 +459,7 @@ def format_command(self, i):
if self.m_media_type in ("v", "video"):
is_avc = self.m_codec == VideoCodecOptions.AVC.value
is_hevc = self.m_codec == VideoCodecOptions.HEVC.value
is_vvc = self.m_codec == VideoCodecOptions.VVC.value

# Resize
command += "ffsws:osize=" + self.m_resolution_w + "x" + self.m_resolution_h
Expand All @@ -447,6 +476,8 @@ def format_command(self, i):
command += ":c=libx264"
elif is_hevc:
command += ":c=libx265"
elif is_vvc:
command += ":c=vvc"
command += ":b=" + self.m_bitrate + "k"
command += ":bf=" + str(self.m_num_b_frames)
command += ":fintra=" + self.m_segment_duration
Expand Down Expand Up @@ -489,19 +520,26 @@ def format_command(self, i):
if bool(self.m_max_cll_fall):
command += f":max-cll={self.m_max_cll_fall}"

if self.m_pic_timing == "True":
if is_avc:
command += ":nal-hrd=vbr"
elif is_hevc:
command += ":hrd=1"
elif is_vvc:
command += "::vvenc-params=\""
command += "refreshsec=" + self.m_segment_duration
command += ":refreshtype=idr"

# common x264 / x265 options
command += ":vbv-bufsize=" + str(int(self.m_bitrate) * 3) + \
":vbv-maxrate=" + str(int(int(self.m_bitrate) * 3 / 2))
if is_avc or is_hevc:
if self.m_pic_timing == "True":
if is_avc:
command += ":nal-hrd=vbr"
elif is_hevc:
command += ":hrd=1"

# common x264 / x265 options
command += ":vbv-bufsize=" + str(int(self.m_bitrate) * 3) + \
":vbv-maxrate=" + str(int(int(self.m_bitrate) * 3 / 2))

# FIXME: VVenC parsing is broken
if self.m_aspect_ratio_x and self.m_aspect_ratio_y:
command += ":sar=" + self.m_aspect_ratio_x + "\\:" + self.m_aspect_ratio_y

if self.m_aspect_ratio_x and self.m_aspect_ratio_y:
command += ":sar=" + self.m_aspect_ratio_x + "\\:" + self.m_aspect_ratio_y

command += "\":" # closing encoder specific parameters

bsrw = None
Expand Down
9 changes: 7 additions & 2 deletions src/tcgen/tcgen.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
import asyncio
import xml.etree.ElementTree as ET
import shutil
import logging
import sys
import traceback

from tcgen.models import TestContent, FPS_FAMILY, locate_source_content, Mezzanine
from tcgen.database import Database, most_recent_batch
Expand All @@ -36,10 +39,10 @@ def cli(ctx):
@click.option('-b', '--batch-dir', default=datetime.today().strftime('%Y-%m-%d'), help='batch directory name. default value uses the current date, eg. 2024-12-31')
@click.option('--encode/--no-encode', default=True, help="encode content")
@click.option('--format-mpd/--no-format-mpd', default=True, help="patch mpd content to match CTA WAVE requirements")
@click.option('-t', '--test-id', help='process only vector with id "-', default=None)
@click.option('-t', '--test-id', help='process only vector with id', default=None)
@click.option('-f', '--fps-family', default='ALL', help='process only one of 14.985_29.97_59.94 - 12.5_25_50 - 15_30_60')
@click.option('--drm-config', default=(Path(__file__) / '../../../DRM.xml').resolve(), help='path to DRM.xml config file')
@click.option('--dry_run/--no-dry-run', default=False, help="dry run, usefull for debugging")
@click.option('--dry_run/--no-dry-run', default=False, help="dry run, useful for debugging")
def encode(ctx, mezzanine, config, vectors_dir, batch_dir, encode, format_mpd, test_id, fps_family, drm_config, dry_run):
"""
Encode content from MEZZANINE directory into test vectors using content options specified in CONFIG.
Expand Down Expand Up @@ -73,6 +76,7 @@ def encode(ctx, mezzanine, config, vectors_dir, batch_dir, encode, format_mpd, t
patch_mpd(output_mpd, m, tc)

except BaseException as e:
traceback.print_exc()
print(e)


Expand Down Expand Up @@ -119,6 +123,7 @@ def export(ctx, mezzanine, config, vectors_dir, database, zip):
m = locate_source_content(tv, fps)
db.add_entry(tv, m, batch_dir.name)
except BaseException as e:
traceback.print_exc()
logging.warning(f'{test_entry_key} : {e}')

if database is not None:
Expand Down