From patchwork Sun Aug 21 19:26:36 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lukas Fellechner X-Patchwork-Id: 37376 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a05:6a20:139a:b0:8f:1db5:eae2 with SMTP id w26csp937509pzh; Sun, 21 Aug 2022 12:27:12 -0700 (PDT) X-Google-Smtp-Source: AA6agR5BHw8lr66VpT+gMOu1WHDiCnzOVYDfWsUqoI9kLLgLZwVhSQBq65M+RbbybBmyBlf1P56m X-Received: by 2002:a17:907:3d90:b0:73c:83de:eb82 with SMTP id he16-20020a1709073d9000b0073c83deeb82mr9273010ejc.343.1661110032586; Sun, 21 Aug 2022 12:27:12 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1661110032; cv=none; d=google.com; s=arc-20160816; b=fLzeuGUiY93h2mshJSDQRgkwH8mtgS4ZUW0lh7kOr7HXgCKu8xpVdAMqZE4dT+uDFU gQO79tsQAge+cxKju1WNgI2FmglIzaDS7+v3nliy+Emh0S6svN9OQS8K9JfQGlmdLlDk 3wu7uhvQuUZrN6h767ptOGCEQQTa5GSwl3RNHyWjstH7UP5VrdFyTIfzKcg9MLWMbtgP V4SnHwCj58l9iQoZWBHwtatLIgqYB3fm0NgQBzXIZP4wCkncelobVr03OFgiiLumlEAy BOu1VYA24/pfZHuJlpGGaPtrPmyAFRnjm/6CvOWZEfrvmElHuSB9ncjy421ECuHcml2A vIzw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=sender:errors-to:content-transfer-encoding:cc:reply-to :list-subscribe:list-help:list-post:list-archive:list-unsubscribe :list-id:precedence:subject:mime-version:references:in-reply-to :message-id:date:to:from:dkim-signature:delivered-to; bh=+gHquDaOYYiJhSSHUf1wlAZOjRWQAmwHW5Gy36RvP90=; b=ynoJ/pMSSUkeqByMNoz76vt+rbJaM40gPhkMg+0Zp4WD5X2Y7gcLQiQ1Osc7eDDjU6 WNxPw1/ycLSHpid7bhXgoXd3s4b8o5PbQ8SQn4cbeg2vg7Sup8r/7DnM4Ry35VG594eg 0ibKsx/rxnX+JG4MVATU5NFQZimDexKqLmpWHTu7//MATKxW20rrfHoTWSrgZ3WBmVrz 5a+71W0hIP8Elu3bJF32NHoZxIj34JQWgBlqKSfUXN5zYzA798f1PRMk/iC3/ojYfQWy xq3tCQYB+Gi/bRwpm5mVuwr9RUF1SW3KQ9MQCI/bWbHWaWXrkaRw98K3lg4vSfyFmXd6 ryew== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmx.net header.s=badeba3b8450 header.b=LjAydVF0; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org; dmarc=fail (p=NONE sp=QUARANTINE dis=NONE) header.from=gmx.net Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id ia26-20020a170907a07a00b0073d67a3d1aesi3399847ejc.219.2022.08.21.12.27.11; Sun, 21 Aug 2022 12:27:12 -0700 (PDT) Received-SPF: pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) client-ip=79.124.17.100; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmx.net header.s=badeba3b8450 header.b=LjAydVF0; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org; dmarc=fail (p=NONE sp=QUARANTINE dis=NONE) header.from=gmx.net Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 0863168B32B; Sun, 21 Aug 2022 22:27:08 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mout.gmx.net (unknown [212.227.17.20]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id B9F6668B32B for ; Sun, 21 Aug 2022 22:27:01 +0300 (EEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=gmx.net; s=badeba3b8450; t=1661110020; bh=5IoZG3XU9JSM4KE2Cv9WH4O/56SCIirzg2+4KhB1JsE=; h=X-UI-Sender-Class:From:To:Cc:Subject:Date:In-Reply-To:References; b=LjAydVF0MqyLN85yULWqnPKYgj4JyvciFzmemjCAZq5t0PVFTO5by7ghGJhHabOtm X3k1981rVF8p8wDLR5iRJdDhe12OItfZzsEC5LWBSZ7mmgVPz40m+FqBOTITh0Djd8 QyZsckdWgj2x5TRTFJaOuRbXQ/G8tIGkLvID2Yp0= X-UI-Sender-Class: 01bb95c1-4bf8-414a-932a-4f6e2808ef9c Received: from localhost.localdomain ([94.134.107.138]) by mail.gmx.net (mrgmx104 [212.227.17.168]) with ESMTPSA (Nemesis) id 1N4z6q-1pQVPM24iy-010wGs; Sun, 21 Aug 2022 21:27:00 +0200 From: Lukas Fellechner To: ffmpeg-devel@ffmpeg.org Date: Sun, 21 Aug 2022 21:26:36 +0200 Message-Id: <20220821192636.734-1-lukas.fellechner@gmx.net> X-Mailer: git-send-email 2.31.1.windows.1 In-Reply-To: References: MIME-Version: 1.0 X-Provags-ID: V03:K1:NkeKiCCk0MBmGlcTku83URjutU1lE24kJ/5gfUIjf3OBh/0+PEW rXvS+U7klk0c1U9n1yKlayd8E9q1y6PgTR40pv8O4T6uxV2DZsoMxOJe1mDPJFSp5AoYymO RSjRuxM9GWjmvX/b+g38oYilJ9sCITxjSVpyyN+u2dBRaftrEIU1CtV6zjGIZm2DVZrR1Eh rsJvNSYGYPPwctBHSMfUg== X-Spam-Flag: NO X-UI-Out-Filterresults: notjunk:1;V03:K0:CiuHDpuOVQE=:w3JHTdCr6SO4BJhs6EtaFK UkPXTR601XVCwe4/VdRkP4VjyXHGjYZAlIy8PNwh41WVZ+1f4q1BQLs9560KUhUy45PxfsB8B 6jmMM1hCrhfyD3c8hZZKtATNQ2jPY0k/sbiXCPVQU3F2/LbfNmqGWMArcEp3HYVudV178pRo3 WmNTeDuve/HkZCIpG1uuaX9DTzafj1mpa1coK+k96K8KHKiJjvqgxbub+DUYHgsU2958NqskZ ihdXFHZpcJl3YtlnycR4FrgJwD9EnLS9BkI7JOmJ1b8F+0W5ZGclQBSnx+CbYDDZaG7GAM4qt t889gA2F5NvcyB/f1/O9zZBYMBVddFk5wz330JJtABHCPnyDWxupQw1KM63d/CXpoDp5/EgKK 86EWBRgangeXwKVp5S+WQ8IJBT8Fd1A+B9068P2U8itfe59yux5eXJmRmDyYa/XDCRxHI1+0s oxQrbpJV1qUbW1TK0fQLp/CEqmAyUyl5whh49+uZ8VnN/qt4KxbNgo3AwNusa48xsC5VpcuDH WQ1kMsmT8WSlyli2ITemdB9o33413AVrNHLZgjiKOmY9ocvWgbXlYBLFvcX0LN5nTGVEeK4Gh euPhc0v5YXF84ddHlB6AT421guK24lXAgiPxry/Ot71KzKtrOQenFl920tf8ATQUQJWuFhPtn xCc4V7xhweSK9HDab+Co0+5Tn4nwPoQ/Kktr9AVJhr6qI6SCJSCZMdH6FdqyUv+edES/Oqco/ JrBSGiR2vAKnKeFU9DWpfSeIExud0ZWmSHAxmbEfqXiLlpImIi9/86M8wcnhoLLbTb1ezIuq7 uM0Qk+ruxcm0Kaj6uji+JP9WOaGze+jrGhYh0UwjtkDcrouMXxQhLkr3gjTJcFngbjHXOw3Kg tl/EB8xN1ZN1yPgjW78nu6qGD3HhbxTFODpSspWeuhHeebbjHHr+WJThZ5znnZuFAgza3304d vjqoL+43QmEXTew0/+j1W2HYzN7v/By/s/ITB8po8VvPBkZNvn2cMZqwWcdbIFACDNEWIzsyF 2mlg8QhAGnqqd+G/MvxlN6yULLSGCrQ6eRyvga4to4huHMMSJnwF2cDpRGqU91+822XocUYcb XWMSvtBju8Nv/s/UDQecu42lxjOUp51gyHohjd1UbDSLtsRBnAjwKtDCA== Subject: [FFmpeg-devel] [PATCH v2] lavf/dashdec: Multithreaded DASH initialization X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Cc: Lukas Fellechner Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" X-TUID: 9eNkbG/iy4Uu Initializing DASH streams is currently slow, because each individual stream is opened and probed sequentially. With DASH streams often having somewhere between 10-20 streams, this can easily take up to half a minute. This patch adds an "init-threads" option, specifying the max number of threads to use. Multiple worker threads are spun up to massively bring down init times. --- libavformat/dashdec.c | 432 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 386 insertions(+), 46 deletions(-) -- 2.31.1.windows.1 diff --git a/libavformat/dashdec.c b/libavformat/dashdec.c index 63bf7e96a5..7eca3e3415 100644 --- a/libavformat/dashdec.c +++ b/libavformat/dashdec.c @@ -24,6 +24,7 @@ #include "libavutil/opt.h" #include "libavutil/time.h" #include "libavutil/parseutils.h" +#include "libavutil/thread.h" #include "internal.h" #include "avio_internal.h" #include "dash.h" @@ -152,6 +153,8 @@ typedef struct DASHContext { int max_url_size; char *cenc_decryption_key; + int init_threads; + /* Flags for init section*/ int is_init_section_common_video; int is_init_section_common_audio; @@ -1918,22 +1921,40 @@ fail: return ret; } -static int open_demux_for_component(AVFormatContext *s, struct representation *pls) +static int open_demux_for_component(AVFormatContext* s, struct representation* pls) +{ + int ret = 0; + + ret = begin_open_demux_for_component(s, pls); + if (ret < 0) + return ret; + + ret = end_open_demux_for_component(s, pls); + + return ret; +} + +static int begin_open_demux_for_component(AVFormatContext* s, struct representation* pls) { int ret = 0; - int i; pls->parent = s; - pls->cur_seq_no = calc_cur_seg_no(s, pls); + pls->cur_seq_no = calc_cur_seg_no(s, pls); if (!pls->last_seq_no) { pls->last_seq_no = calc_max_seg_no(pls, s->priv_data); } ret = reopen_demux_for_component(s, pls); - if (ret < 0) { - goto fail; - } + + return ret; +} + +static int end_open_demux_for_component(AVFormatContext* s, struct representation* pls) +{ + int ret = 0; + int i; + for (i = 0; i < pls->ctx->nb_streams; i++) { AVStream *st = avformat_new_stream(s, NULL); AVStream *ist = pls->ctx->streams[i]; @@ -2015,6 +2036,135 @@ static void move_metadata(AVStream *st, const char *key, char **value) } } +#if HAVE_THREADS + +struct work_pool_data +{ + AVFormatContext* ctx; + struct representation* pls; + struct representation* common_pls; + pthread_mutex_t* common_mutex; + pthread_cond_t* common_condition; + int is_common; + int is_started; + int result; +}; + +struct thread_data +{ + pthread_t thread; + pthread_mutex_t* mutex; + struct work_pool_data* work_pool; + int work_pool_size; + int is_started; +}; + +static void *worker_thread(void *ptr) +{ + int ret = 0; + int i; + struct thread_data* thread_data = (struct thread_data*)ptr; + struct work_pool_data* work_pool = NULL; + struct work_pool_data* data = NULL; + for (;;) { + + // get next work item + pthread_mutex_lock(thread_data->mutex); + data = NULL; + work_pool = thread_data->work_pool; + for (i = 0; i < thread_data->work_pool_size; i++) { + if (!work_pool->is_started) { + data = work_pool; + data->is_started = 1; + break; + } + work_pool++; + } + pthread_mutex_unlock(thread_data->mutex); + + if (!data) { + // no more work to do + return NULL; + } + + // if we are common section provider, init and signal + if (data->is_common) { + data->pls->parent = data->ctx; + ret = update_init_section(data->pls); + if (ret < 0) { + pthread_cond_signal(data->common_condition); + goto end; + } + else + ret = AVERROR(pthread_cond_signal(data->common_condition)); + } + + // if we depend on common section provider, wait for signal and copy + if (data->common_pls) { + ret = AVERROR(pthread_cond_wait(data->common_condition, data->common_mutex)); + if (ret < 0) + goto end; + + if (!data->common_pls->init_sec_buf) { + goto end; + ret = AVERROR(EFAULT); + } + + ret = copy_init_section(data->pls, data->common_pls); + if (ret < 0) + goto end; + } + + ret = begin_open_demux_for_component(data->ctx, data->pls); + if (ret < 0) + goto end; + + end: + data->result = ret; + } + + + return NULL; +} + +static void create_work_pool_data(AVFormatContext* ctx, int stream_index, + struct representation* pls, struct representation* common_pls, + struct work_pool_data* init_data, pthread_mutex_t* common_mutex, + pthread_cond_t* common_condition) +{ + init_data->ctx = ctx; + init_data->pls = pls; + init_data->pls->stream_index = stream_index; + init_data->common_condition = common_condition; + init_data->common_mutex = common_mutex; + init_data->result = -1; + + if (pls == common_pls) { + init_data->is_common = 1; + } + else if (common_pls) { + init_data->common_pls = common_pls; + } +} + +static int start_thread(struct thread_data *thread_data, + struct work_pool_data *work_pool, int work_pool_size, pthread_mutex_t *mutex) +{ + int ret; + + thread_data->mutex = mutex; + thread_data->work_pool = work_pool; + thread_data->work_pool_size = work_pool_size; + + ret = AVERROR(pthread_create(&thread_data->thread, NULL, worker_thread, (void*)thread_data)); + if (ret == 0) + thread_data->is_started = 1; + + return ret; +} + +#endif + static int dash_read_header(AVFormatContext *s) { DASHContext *c = s->priv_data; @@ -2040,63 +2190,252 @@ static int dash_read_header(AVFormatContext *s) av_dict_set(&c->avio_opts, "seekable", "0", 0); } - if(c->n_videos) + if (c->n_videos) c->is_init_section_common_video = is_common_init_section_exist(c->videos, c->n_videos); - /* Open the demuxer for video and audio components if available */ - for (i = 0; i < c->n_videos; i++) { - rep = c->videos[i]; - if (i > 0 && c->is_init_section_common_video) { - ret = copy_init_section(rep, c->videos[0]); + if (c->n_audios) + c->is_init_section_common_audio = is_common_init_section_exist(c->audios, c->n_audios); + + if (c->n_subtitles) + c->is_init_section_common_subtitle = is_common_init_section_exist(c->subtitles, c->n_subtitles); + + int threads = 0; + int nstreams = c->n_videos + c->n_audios + c->n_subtitles; + +#if HAVE_THREADS + threads = FFMIN(nstreams, c->init_threads); +#endif + + if (threads > 1) + { +#if HAVE_THREADS + // alloc data + struct work_pool_data* init_data = (struct work_pool_data*)av_mallocz(sizeof(struct work_pool_data) * nstreams); + if (!init_data) + return AVERROR(ENOMEM); + + struct thread_data* thread_data = (struct thread_data*)av_mallocz(sizeof(struct thread_data) * threads); + if (!thread_data) + return AVERROR(ENOMEM); + + // alloc mutex and conditions + pthread_mutex_t work_pool_mutex; + + pthread_mutex_t common_video_mutex; + pthread_cond_t common_video_cond; + + pthread_mutex_t common_audio_mutex; + pthread_cond_t common_audio_cond; + + pthread_mutex_t common_subtitle_mutex; + pthread_cond_t common_subtitle_cond; + + // init mutex and conditions + ret = AVERROR(pthread_mutex_init(&work_pool_mutex, NULL)); + if (ret < 0) + goto cleanup; + + if (c->is_init_section_common_video) { + ret = AVERROR(pthread_mutex_init(&common_video_mutex, NULL)); if (ret < 0) - return ret; + goto cleanup; + + ret = AVERROR(pthread_cond_init(&common_video_cond, NULL)); + if (ret < 0) + goto cleanup; } - ret = open_demux_for_component(s, rep); - if (ret) - return ret; - rep->stream_index = stream_index; - ++stream_index; - } + if (c->is_init_section_common_audio) { + ret = AVERROR(pthread_mutex_init(&common_audio_mutex, NULL)); + if (ret < 0) + goto cleanup; - if(c->n_audios) - c->is_init_section_common_audio = is_common_init_section_exist(c->audios, c->n_audios); + ret = AVERROR(pthread_cond_init(&common_audio_cond, NULL)); + if (ret < 0) + goto cleanup; + } - for (i = 0; i < c->n_audios; i++) { - rep = c->audios[i]; - if (i > 0 && c->is_init_section_common_audio) { - ret = copy_init_section(rep, c->audios[0]); + if (c->is_init_section_common_subtitle) { + ret = AVERROR(pthread_mutex_init(&common_subtitle_mutex, NULL)); if (ret < 0) - return ret; + goto cleanup; + + ret = AVERROR(pthread_cond_init(&common_subtitle_cond, NULL)); + if (ret < 0) + goto cleanup; } - ret = open_demux_for_component(s, rep); - if (ret) - return ret; - rep->stream_index = stream_index; - ++stream_index; - } + // init work pool data + struct work_pool_data* current_data = init_data; - if (c->n_subtitles) - c->is_init_section_common_subtitle = is_common_init_section_exist(c->subtitles, c->n_subtitles); + for (i = 0; i < c->n_videos; i++) { + create_work_pool_data(s, stream_index, c->videos[i], + c->is_init_section_common_video ? c->videos[0] : NULL, + current_data, &common_video_mutex, &common_video_cond); - for (i = 0; i < c->n_subtitles; i++) { - rep = c->subtitles[i]; - if (i > 0 && c->is_init_section_common_subtitle) { - ret = copy_init_section(rep, c->subtitles[0]); + stream_index++; + current_data++; + } + + for (i = 0; i < c->n_audios; i++) { + create_work_pool_data(s, stream_index, c->audios[i], + c->is_init_section_common_audio ? c->audios[0] : NULL, + current_data, &common_audio_mutex, &common_audio_cond); + + stream_index++; + current_data++; + } + + for (i = 0; i < c->n_subtitles; i++) { + create_work_pool_data(s, stream_index, c->subtitles[i], + c->is_init_section_common_subtitle ? c->subtitles[0] : NULL, + current_data, &common_subtitle_mutex, &common_subtitle_cond); + + stream_index++; + current_data++; + } + + // start threads + struct thread_data* current_thread = thread_data; + for (i = 0; i < threads; i++) { + ret = start_thread(current_thread, init_data, nstreams, &work_pool_mutex); if (ret < 0) - return ret; + goto cleanup; + + current_thread++; } - ret = open_demux_for_component(s, rep); - if (ret) - return ret; - rep->stream_index = stream_index; - ++stream_index; + cleanup: + // we need to cleanup even in case of errors, so we need to store results of init, run and cleanup + int initResult = ret; + int runResult = 0; + int cleanupResult = 0; + + // join threads + current_thread = thread_data; + for (i = 0; i < threads; i++) { + if (current_thread->is_started) { + ret = AVERROR(pthread_join(current_thread->thread, NULL)); + if (ret < 0) + cleanupResult = ret; + } + current_thread++; + } + + // finalize streams and collect results + current_data = init_data; + for (i = 0; i < nstreams; i++) { + if (current_data->result < 0) { + // thread ran into error: collect result + runResult = current_data->result; + } + else { + // thread success: create streams on AVFormatContext + ret = end_open_demux_for_component(s, current_data->pls); + if (ret < 0) + runResult = ret; + } + current_data++; + } + + // cleanup mutex and conditions + ret = AVERROR(pthread_mutex_destroy(&work_pool_mutex)); + if (ret < 0) + cleanupResult = ret; + + if (c->is_init_section_common_video) { + ret = AVERROR(pthread_mutex_destroy(&common_video_mutex)); + if (ret < 0) + cleanupResult = ret; + + ret = AVERROR(pthread_cond_destroy(&common_video_cond)); + if (ret < 0) + cleanupResult = ret; + } + + if (c->is_init_section_common_audio) { + ret = AVERROR(pthread_mutex_destroy(&common_audio_mutex)); + if (ret < 0) + cleanupResult = ret; + + ret = AVERROR(pthread_cond_destroy(&common_audio_cond)); + if (ret < 0) + cleanupResult = ret; + } + + if (c->is_init_section_common_subtitle) { + ret = AVERROR(pthread_mutex_destroy(&common_subtitle_mutex)); + if (ret < 0) + cleanupResult = ret; + + ret = AVERROR(pthread_cond_destroy(&common_subtitle_cond)); + if (ret < 0) + cleanupResult = ret; + } + + // return results if errors have occured in one of the phases + if (initResult < 0) + return initResult; + + if (runResult < 0) + return runResult; + + if (cleanupResult < 0) + return cleanupResult; + +#endif } + else + { + /* Open the demuxer for video and audio components if available */ + for (i = 0; i < c->n_videos; i++) { + rep = c->videos[i]; + if (i > 0 && c->is_init_section_common_video) { + ret = copy_init_section(rep, c->videos[0]); + if (ret < 0) + return ret; + } + ret = open_demux_for_component(s, rep); - if (!stream_index) - return AVERROR_INVALIDDATA; + if (ret) + return ret; + rep->stream_index = stream_index; + ++stream_index; + } + + for (i = 0; i < c->n_audios; i++) { + rep = c->audios[i]; + if (i > 0 && c->is_init_section_common_audio) { + ret = copy_init_section(rep, c->audios[0]); + if (ret < 0) + return ret; + } + ret = open_demux_for_component(s, rep); + + if (ret) + return ret; + rep->stream_index = stream_index; + ++stream_index; + } + + for (i = 0; i < c->n_subtitles; i++) { + rep = c->subtitles[i]; + if (i > 0 && c->is_init_section_common_subtitle) { + ret = copy_init_section(rep, c->subtitles[0]); + if (ret < 0) + return ret; + } + ret = open_demux_for_component(s, rep); + + if (ret) + return ret; + rep->stream_index = stream_index; + ++stream_index; + } + + if (!stream_index) + return AVERROR_INVALIDDATA; + } /* Create a program */ program = av_new_program(s, 0); @@ -2349,6 +2688,7 @@ static const AVOption dash_options[] = { {.str = "aac,m4a,m4s,m4v,mov,mp4,webm,ts"}, INT_MIN, INT_MAX, FLAGS}, { "cenc_decryption_key", "Media decryption key (hex)", OFFSET(cenc_decryption_key), AV_OPT_TYPE_STRING, {.str = NULL}, INT_MIN, INT_MAX, .flags = FLAGS }, + { "init_threads", "Number of threads to use for initializing the DASH stream", OFFSET(init_threads), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 64, FLAGS }, {NULL} };