def get_hls_mux_args(enc_params, hls_ingest_url): output_config = enc_params['output'] segment_size = wc_utils.to_int(output_config['segment_size']) ffmpeg_out_url = hls_ingest_url now = datetime.datetime.now() ccgroup_name = 'cc' hls_args = '' hls_args += '%s=%s' %('f', 'hls') var_stream_map = '' if enc_params['output']['create_muxed_av'] == 'on' : for n in range(0, len(enc_params['video']['variants'])): var_stream_map += " a\:%d,v\:%d" %(n, n) if enc_params['video']['enable_cc'] == 'on': var_stream_map += ",ccgroup\:%s" %ccgroup_name else: for aud_tag in enc_params['audio']: aud_id = wc_utils.to_int(enc_params['audio'].keys().index(aud_tag)) var_stream_map += " a\:%d,agroup\:%s" %(aud_id, aud_tag) for n in range(0, len(enc_params['video']['variants'])): var_stream_map += " v\:%d,agroup\:%s" \ %(n, enc_params['video']['variants'][n]['audio_tag']) if enc_params['video']['enable_cc'] == 'on': var_stream_map += ",ccgroup\:%s" %ccgroup_name if enc_params['output']['seg_in_subfolder'] == 'on' : hls_segment_filename = '%s/variant_%%v/stream_%02d%02d%02d_%%d.ts' %\ (ffmpeg_out_url.replace(':', '\:'), now.hour, now.minute, now.second) else: hls_segment_filename = '%s/stream_%02d%02d%02d_%%v_%%d.ts' %\ (ffmpeg_out_url.replace(':', '\:'), now.hour, now.minute, now.second) hls_flags = 'program_date_time+round_durations' hls_ts_options = 'mpegts_pmt_start_pid=480:mpegts_start_pid=481' hls_args += ':%s=\'%s\'' %('var_stream_map', var_stream_map) hls_args += ':%s=\'%s\'' %('hls_segment_filename', hls_segment_filename) hls_args += ':%s=%s' %('hls_time', segment_size) hls_args += ':%s=%s' %('hls_flags', hls_flags) hls_args += ':%s=\'%s\'' %('hls_ts_options', str(hls_ts_options).replace(':', '\:')) hls_args += ':%s=%s' %('http_persistent', 1) hls_args += ':%s=%s' %('http_user_agent', enc_params['output']['user_agent']) hls_args += ':%s=%s' %('hls_list_size', 6) hls_args += ':%s=\'%s\'' %('cc_stream_map', "ccgroup\:%s,instreamid\:CC1" %ccgroup_name) hls_args += ':%s=%s' %('master_pl_name', enc_params['output']['hls_master_manifest']) hls_args += ':%s=%s' %('master_pl_publish_rate', 100) hls_args += ':%s=%s' %('timeout', 0.5) hls_args += ':%s=%s' %('method', 'PUT') hls_args += ':%s=%s' %('ignore_io_errors', '1') if output_config['enable_abs_seg_path'] == 'on' : hls_args += ':%s=\'%s\'' %('hls_base_url', str(output_config['abs_seg_path_base_url']).replace(':', '\:')) return hls_args
def get_vcodec_args(enc_params, osi): vid_config = enc_params['video']['variants'] video_codec = vid_config[osi]['codec'] video_bitrate = wc_utils.to_int(vid_config[osi]['bitrate']) video_width = str(vid_config[osi]['video_width']) video_height = str(vid_config[osi]['video_height']) args = '' ''' osi stands for output stream index. Each codec parameter is indexed with output stream index. This is needed for multi-bitrate usecase to map the encoded streamss to appropriate output streams. ''' postfix = ':' + str(osi) if enc_params['video']['rate_control'] == 'cbr': vbv_bufsize = wc_utils.to_int(video_bitrate * 0.1) else: vbv_bufsize = video_bitrate args += ' -c:v%s %s' % (postfix, video_codec) args += ' -pix_fmt%s yuv420p' % (postfix) cc_flag = 0 if enc_params['video']['enable_cc'] == 'on': cc_flag = 1 if (video_codec == 'libx264'): args += ' -preset%s %s' % (postfix, enc_params['video']['speed_preset']) args += ' -a53cc%s %d -nal-hrd%s %s -x264opts%s scenecut=-1:rc_lookahead=0' % \ (postfix, cc_flag, postfix, enc_params['video']['rate_control'], postfix) if psutil.cpu_count() > 8: args += ':threads=12' elif (video_codec == 'libvpx-vp9'): cpu_used = x264_preset_to_vp9_cpu_used( enc_params['video']['speed_preset']) args += ' -deadline%s realtime -cpu-used%s %d -tile-columns%s 4'\ ' -frame-parallel%s 1 -threads%s 8 -static-thresh%s 0 -lag-in-frames%s 0 ' % \ (postfix, postfix, cpu_used, postfix, postfix, postfix, postfix, postfix) args += ' -b:v%s %sk -bufsize%s %sk' % \ (postfix, video_bitrate, postfix, vbv_bufsize) if (video_width != '-1' and video_height != '-1'): args += ' -s%s ' % postfix + video_width + 'x' + video_height + ' ' args += ' -force_key_frames%s "expr:gte(t,n_forced*%s)" -bf%s %d' % \ (postfix, enc_params['output']['segment_size'],\ postfix, int(enc_params['video']['num_b_frame'])) args += ' ' return args
def get_dash_mux_args(enc_params): segment_size = wc_utils.to_int(enc_params['output']['segment_size']) now = datetime.datetime.now() curr_time = str(now.hour) + str(now.minute) + str(now.second) if enc_params['output']['seg_in_subfolder'] == 'on': chunk_name = '%s/chunk-stream_\$RepresentationID\$-\$Number%%05d\$.\$ext\$' % ( curr_time) init_seg_name = '%s/init-stream\$RepresentationID\$.\$ext\$' % ( curr_time) else: chunk_name = 'chunk-stream_%s_\$RepresentationID\$-\$Number%%05d\$.\$ext\$' % ( curr_time) init_seg_name = 'init-stream_%s_\$RepresentationID\$.\$ext\$' % ( curr_time) chunk_name = chunk_name.replace(':', '\:') streaming = 0 if enc_params['output']['dash_chunked'] == 'on': streaming = 1 dash_cmd = '' dash_cmd += '%s=%s' % ('f', 'dash') dash_cmd += ':%s=\'%s\'' % ('media_seg_name', chunk_name) dash_cmd += ':%s=\'%s\'' % ('init_seg_name', init_seg_name) dash_cmd += ':%s=%s' % ('min_seg_duration', int(segment_size) * 1000000) dash_cmd += ':%s=%s' % ('window_size', 3) if enc_params['output']['dash_segtimeline'] == 'on': dash_cmd += ':%s=%s' % ('use_timeline', 1) else: dash_cmd += ':%s=%s' % ('use_timeline', 0) dash_cmd += ':%s=%s' % ('http_user_agent', enc_params['output']['user_agent']) dash_cmd += ':%s=%s' % ('streaming', streaming) dash_cmd += ':%s=%s' % ('index_correction', 1) dash_cmd += ':%s=%s' % ('timeout', 0.5) dash_cmd += ':%s=%s' % ('dash_segment_type', 'mp4') dash_cmd += ':%s=%s' % ('method', 'PUT') dash_cmd += ':%s=%s' % ('ignore_io_errors', '1') if enc_params['output']['lhls'] == 'on': dash_cmd += ':%s=%s' % ('lhls', '1') if (segment_size < 8): dash_cmd += ':%s=%s' % ('http_persistent', 1) if (enc_params['output']['out_type'] == 'CMAF'): dash_cmd += ":%s=%s" % ('hls_playlist', 1) dash_cmd += ' ' return dash_cmd
def get_args(enc_params): out_type = enc_params['output']['out_type'] if not 'tee_port' in enc_params['output']: enc_params['output']['tee_port'] = 0 ffmpeg_output_args = ' ' ffmpeg_input_args = ' ' if enc_params['input']['input_interface'] != wc_capture.INPUT_INTERFACE_URL: ffmpeg_input_args += ' -copyts ' ffmpeg_input_args += ' -probesize 10M -f %s ' % ( enc_params['input']['input_interface']) else: if (urlparse.urlparse( enc_params['input']['inputurl']).scheme == 'file'): ffmpeg_input_args += ' -re ' elif (urlparse.urlparse( enc_params['input']['inputurl']).scheme == 'rtmp'): ffmpeg_input_args += ' -listen 1 ' if enc_params['input'][ 'input_interface'] == wc_capture.INPUT_INTERFACE_DECKLINK: ''' video capture format is yuv 422 audio sampling frequency is always 48kHz number of bits per audio sample is 16 number audio channels are 2 ''' ffmpeg_input_args += ' -bm_v210 0 -audio_depth 16 -channels 2 ' qbufsize = int(enc_params['input']['vid_width']) * \ int(enc_params['input']['vid_height']) * 2 * \ math.ceil(float(enc_params['input']['vid_framerate'])) qbufsize += 48000 * 2 * 2 ffmpeg_input_args += ' -queue_size %d ' % qbufsize ffmpeg_input_args += '-decklink_copyts 1 -audio_pts abs_wallclock -video_pts abs_wallclock ' ffmpeg_input_args += '-i %s ' % (enc_params['input']['inputurl']) ffmpeg_input_args += '-flags +global_header ' if enc_params['input']['input_interface'] != wc_capture.INPUT_INTERFACE_URL and\ enc_params['input']['input_interface'] != wc_capture.INPUT_INTERFACE_AVFOUNDATION: fps_num, fps_den = wc_utils.map_fps_num_den( enc_params['input']['vid_framerate']) ffmpeg_input_args += '-r %s/%s ' % (fps_num, fps_den) if (enc_params['output']['burn_tc'] == 'on'): ffmpeg_output_args += '-vf drawbox="x=0:y=0:width=%s:height=%s:color=white:t=fill",drawtext="x=15:y=20:fontfile=/usr/share/fonts/truetype/freefont/%s.ttf:fontsize=%s:fontcolor=black:'\ %(enc_params['output']['drawbox_width'], enc_params['output']['drawbox_height'], enc_params['output']['fonttype'], enc_params['output']['fontsize']) if enc_params['input'][ 'input_interface'] == wc_capture.INPUT_INTERFACE_URL and ( urlparse.urlparse( enc_params['input']['inputurl']).scheme == 'file'): ffmpeg_output_args += 'expansion=strftime:text=\'%H\\:%M\\:%S\'"' else: ffmpeg_output_args += 'text=\'%%{pts\\:hms\\:%d\\:24HH}\'"' % ( (time.timezone * -1)) ffmpeg_output_args += ' -af aresample=async=1 ' num_vid_sub_streams = len(enc_params['video']['variants']) num_aud_sub_streams = len(enc_params['audio']) vid_config = enc_params['video']['variants'] for n in range(0, num_vid_sub_streams): ffmpeg_output_args += get_vcodec_args(enc_params, n) if enc_params['output']['create_muxed_av'] == 'on': num_aud_sub_streams = num_vid_sub_streams for n in range(0, num_vid_sub_streams): audio_ref_tag = vid_config[n]['audio_tag'] audio_bitrate = wc_utils.to_int( enc_params['audio'][audio_ref_tag]['bitrate']) audio_codec = enc_params['audio'][audio_ref_tag]['codec'] ffmpeg_output_args += get_acodec_args(n, audio_codec, audio_bitrate) else: for aud_tag in enc_params['audio']: aud_id = wc_utils.to_int(enc_params['audio'].keys().index(aud_tag)) audio_bitrate = wc_utils.to_int( enc_params['audio'][aud_tag]['bitrate']) audio_codec = enc_params['audio'][aud_tag]['codec'] ffmpeg_output_args += get_acodec_args(aud_id, audio_codec, audio_bitrate) if enc_params['output']['lhls'] == 'on': ffmpeg_output_args += ' -strict experimental' ffmpeg_output_args += ' -f tee ' for n in range(0, num_vid_sub_streams): ffmpeg_output_args += '-map 0:v ' for n in range(0, num_aud_sub_streams): if enc_params['input'][ 'input_interface'] == wc_capture.INPUT_INTERFACE_V4L2: ffmpeg_output_args += '-map 1:a? ' else: ffmpeg_output_args += '-map 0:a? ' if (out_type == 'HLS'): hls_ingest_url = enc_params['output']['ingest_url'].rstrip('/') ffmpeg_mux_args = get_hls_mux_args(enc_params, hls_ingest_url) if enc_params['output']['seg_in_subfolder'] == 'on': ffmpeg_out_url = '%s/variant_%%v/media.m3u8 ' % hls_ingest_url else: ffmpeg_out_url = '%s/media_%%v.m3u8 ' % hls_ingest_url ffmpeg_output_args += '"[%s]%s' % (ffmpeg_mux_args, ffmpeg_out_url) if enc_params['output']['b_ingest_url'] != '': hls_ingest_url = enc_params['output']['b_ingest_url'].rstrip('/') ffmpeg_mux_args = get_hls_mux_args(enc_params, hls_ingest_url) if enc_params['output']['seg_in_subfolder'] == 'on': ffmpeg_out_url = '%s/variant_%%v/media.m3u8 ' % hls_ingest_url else: ffmpeg_out_url = '%s/media_%%v.m3u8 ' % hls_ingest_url ffmpeg_output_args += '|[%s]%s' % (ffmpeg_mux_args, ffmpeg_out_url) ffmpeg_output_args += '"' elif (out_type == 'DASH' or out_type == 'CMAF'): dash_ingest_url = enc_params['output']['ingest_url'].rstrip('/') ffmpeg_mux_args = get_dash_mux_args(enc_params) ffmpeg_out_url = '%s/%s ' %(dash_ingest_url,\ enc_params['output']['dash_master_manifest']) ffmpeg_output_args += '"[%s]%s' % (ffmpeg_mux_args, ffmpeg_out_url) if enc_params['output']['b_ingest_url'] != '': dash_ingest_url = enc_params['output']['b_ingest_url'].rstrip('/') ffmpeg_mux_args = get_dash_mux_args(enc_params) ffmpeg_out_url = '%s/%s ' %(dash_ingest_url,\ enc_params['output']['dash_master_manifest']) ffmpeg_output_args += '|[%s]%s' % (ffmpeg_mux_args, ffmpeg_out_url) ffmpeg_output_args += '"' args = ffmpeg_input_args + ffmpeg_output_args return args
def validate_encoder_params(encoder_params): min_video_bitrate = 200 max_video_bitrate = 20000 min_audio_bitrate = 32 max_audio_bitrate = 320 min_segment_size = 1 max_segment_size = 60 min_video_width = 160 min_video_height = 120 max_video_width = 4096 max_video_height = 2160 num_devices = capture.get_devices(False) input_id = encoder_params['input_id'] out_type = encoder_params['output']['out_type'] # TODO(Karthick) : Security RISK. More input validation needs to done here if int(input_id) < 0 or \ int(input_id) > int(num_devices): msg = 'Invalid input_id:' + str(input_id) return False, msg if len(encoder_params['video']['variants']) <= 0: msg = 'No video bitrates specified' return False, msg num_vid_sub_streams = len(encoder_params['video']['variants']) num_aud_sub_streams = len(encoder_params['audio']) for n in range(0, num_vid_sub_streams): vid_config = encoder_params['video']['variants'] video_codec = vid_config[n]['codec'] if video_codec not in codecs.get_codecs(): msg = 'Selected codec ' + video_codec + ' not supported' return False, msg video_bitrate = wc_utils.to_int(vid_config[n]['bitrate']) try: video_width = int(vid_config[n]['video_width']) video_height = int(vid_config[n]['video_height']) except: msg = 'Invalid video_width or video_height:' + \ str(vid_config[n]['video_width']) + ' or ' + \ str(vid_config[n]['video_height']) return False, msg if (video_bitrate > max_video_bitrate) or (video_bitrate < min_video_bitrate): msg = 'Invalid video_bitrate:' + str(video_bitrate) return False, msg if (video_width != -1 and (video_width < min_video_width or video_width > max_video_width or (video_width % 2) != 0)): msg = 'Invalid video_width: ' + str(video_width) msg += ' min:' + str(min_video_width) + ' max:' + str( max_video_width) msg += ' and video_width is expected to be multiple of 2' return False, msg if (video_height != -1 and (video_height < min_video_height or video_height > max_video_height or (video_height % 2) != 0)): msg = 'Invalid video_height: ' + str(video_height) msg += ' min:' + str(min_video_height) + ' max:' + str( max_video_height) msg += ' and video_height is expected to be multiple of 2' return False, msg speed_preset = encoder_params['video']['speed_preset'] if speed_preset != 'slower' and speed_preset != 'slow' and speed_preset != 'medium' and \ speed_preset != 'fast' and speed_preset != 'faster' and speed_preset != 'veryfast' and \ speed_preset != 'superfast' and speed_preset != 'ultrafast': msg = 'Invalid speed_preset: ' + str(speed_preset) return False, msg if encoder_params['video']['rate_control'] != 'cbr' and \ encoder_params['video']['rate_control'] != 'vbr': msg = 'Invalid rate_control: ' + str( encoder_params['video']['rate_control']) return False, msg if (encoder_params['video']['enable_cc'] != 'on') and \ (encoder_params['video']['enable_cc'] != 'off'): msg = ' Invalid enable_cc flag ' + str( encoder_params['video']['enable_cc']) return False, msg if (encoder_params['video']['num_b_frame'] > 8): msg = ' Invalid number of B frames, max supported is 8 ' return False, msg for aud_tag in encoder_params['audio']: audio_bitrate = wc_utils.to_int( encoder_params['audio'][aud_tag]['bitrate']) audio_codec = encoder_params['audio'][aud_tag]['codec'] if audio_codec != 'aac': msg = ' Invalid audio_codec:' + str(audio_codec) return False, msg if (audio_bitrate > max_audio_bitrate) or (audio_bitrate < min_audio_bitrate): msg = ' Invalid audio_bitrate:' + str(audio_bitrate) return False, msg if (encoder_params['output']['burn_tc'] != 'on') and \ (encoder_params['output']['burn_tc'] != 'off'): msg = ' Invalid burn_tc flag ' + str( encoder_params['output']['burn_tc']) return False, msg if (encoder_params['output']['create_muxed_av'] != 'on') and \ (encoder_params['output']['create_muxed_av'] != 'off'): msg = ' Invalid create_muxed_av flag ' + str( encoder_params['output']['create_muxed_av']) return False, msg if (out_type != 'HLS') and (encoder_params['output']['create_muxed_av'] == 'on'): msg = ' Muxed AV flag is not expected to be set for non HLS format ' + out_type return False, msg if (encoder_params['output']['enable_abs_seg_path'] != 'on') and \ (encoder_params['output']['enable_abs_seg_path'] != 'off'): msg = ' Invalid enable_abs_seg_path flag ' + str( encoder_params['output']['enable_abs_seg_path']) return False, msg if (out_type != 'HLS') and (encoder_params['output']['enable_abs_seg_path'] == 'on'): msg = ' Enable absolute segment path flag is not expected to be set for non HLS format ' + out_type return False, msg if encoder_params['output']['enable_abs_seg_path'] == 'on': parsed_url = urlparse.urlparse( encoder_params['output']['abs_seg_path_base_url']) if False == (bool(parsed_url.scheme) and bool(parsed_url.netloc)): msg = ' Invalid absolute segment path base URL ' + str( encoder_params['output']['abs_seg_path_base_url']) return False, msg if not encoder_params['output']['abs_seg_path_base_url'].endswith('/'): encoder_params['output']['abs_seg_path_base_url'] += '/' if (encoder_params['output']['seg_in_subfolder'] != 'on') and \ (encoder_params['output']['seg_in_subfolder'] != 'off'): msg = ' Invalid seg_in_subfolder flag ' + str( encoder_params['output']['seg_in_subfolder']) return False, msg if (out_type != 'HLS') and (out_type != 'DASH') and \ (out_type != 'CMAF'): msg = ' Invalid out_type ' + str(out_type) return False, msg if (out_type == 'HLS' or out_type == 'DASH' or out_type == 'CMAF'): segment_size = wc_utils.to_int( encoder_params['output']['segment_size']) if segment_size > max_segment_size or \ segment_size < min_segment_size: msg = ' Invalid segment size: ' + str(segment_size) return False, msg if out_type == 'HLS' or out_type == 'DASH' or out_type == 'CMAF': parsed_url = urlparse.urlparse(encoder_params['output']['ingest_url']) if False == (bool(parsed_url.scheme) and bool(parsed_url.netloc)): msg = ' Invalid ingest_url ' + str( encoder_params['output']['ingest_url']) return False, msg if out_type == 'DASH' or out_type == 'CMAF': if (encoder_params['output']['dash_chunked'] != 'on') and \ (encoder_params['output']['dash_chunked'] != 'off'): msg = ' Invalid dash_chunked flag ' + str( encoder_params['output']['dash_chunked']) return False, msg if out_type == 'CMAF': if (encoder_params['output']['lhls'] != 'on') and \ (encoder_params['output']['lhls'] != 'off'): msg = ' Invalid LHLS flag ' + str( encoder_params['output']['lhls']) return False, msg else: if (encoder_params['output']['lhls'] != 'off'): msg = ' Invalid LHLS flag ' + str( encoder_params['output']['lhls']) return False, msg return True, ''
def start_encoder(enc_params): print 'Starting encoder' """ WSGI Application that gets called with the set environment and the response generation function. """ default_config = { "input_id": "-1", "video": { "speed_preset": "fast", "rate_control": "cbr", "enable_cc": "on", "num_b_frame": 8, "variants": [{ "codec": "libx264", "bitrate": "-1", "video_width": "-1", "video_height": "-1", "audio_tag": "0" }] }, "audio": { "0": { "bitrate": "-1", "codec": "aac" } }, "output": { "out_type": "HLS", "segment_size": "5", "burn_tc": "off", "create_muxed_av": "off", "enable_abs_seg_path": "off", "abs_seg_path_base_url": "", "seg_in_subfolder": "off", "ingest_url": "", "b_ingest_url": "", "dash_chunked": "off", "lhls": "off", "hls_master_manifest": "master.m3u8", "dash_master_manifest": "out.mpd" }, } ffmpeg_proc_name = 'ffmpeg ' store_default_config(default_config, enc_params) status, msg = validate_encoder_params(enc_params) if False == status: reason = 'Bad Request: ' + msg return 400, reason input_id = str(enc_params['input_id']) segment_size = wc_utils.to_int(enc_params['output']['segment_size']) out_type = enc_params['output']['out_type'] print 'Inside start_encoder.py input_id:' + str(enc_params['input_id']) #Kill any existing process stopencoder.stop_encoder(input_id) #Store the json input in a file store_load_input_cfg.store_json_cfg(enc_params) enc_params['input'] = {} enc_params['input']['input_interface'] = capture.get_input_interface( int(input_id)) enc_params['input']['inputurl'] = capture.get_inputurl(int(input_id)) ffmpeg_format_args = '' if enc_params['input']['input_interface'] == capture.INPUT_INTERFACE_URL: ffmpeg_format_args += ' -timeout 1000000 -analyzeduration 5000000 ' else: ffmpeg_format_args += ' -probesize 10M -f ' + enc_params['input'][ 'input_interface'] ffmpeg_format_args += ' -i %s' % (enc_params['input']['inputurl']) vid_w, vid_h, vid_fr, scantype, device_status = capture.find_input_format( ffmpeg_proc_name + ffmpeg_format_args) if device_status != 'Active': print 'Input signal detection failed: ' + str(device_status) enc_params['input']['vid_width'] = vid_w enc_params['input']['vid_height'] = vid_h enc_params['input']['vid_framerate'] = vid_fr enc_params['input']['vid_scantype'] = scantype enc_params['output']['user_agent'] = USER_AGENT enc_params['output']['drawbox_width'] = 500 enc_params['output']['drawbox_height'] = 88 enc_params['output']['fonttype'] = "FreeSerif" enc_params['output']['fontsize'] = 80 args = wc_ffmpeg_args.get_args(enc_params) print ffmpeg_proc_name + args proc = subprocess.Popen(shlex.split(ffmpeg_proc_name + args)) pid = proc.pid current_time = time.time() * 1000000 for n in range(0, len(enc_params['video']['variants'])): cfg = { 'TIME': int(current_time), #current time 'InputID': int(input_id), #Input ID 'SubStreamID': int(n), #Substream ID 'ProcessID': pid, #ProcessID 'VidInWidth': int(vid_w), #Input video width 'VidInHt': int(vid_h), #Input video height 'InScanType': str(scantype), #Input scantype 'VidInFrameRate': str(vid_fr), #Input framerate 'FrameRate': str(vid_fr), #Output framerate } configdb.insert_stream_config(cfg) print 'All Ok, encoder started successfully' return 200, 'OK'