diff options
author | Anton Khirnov <anton@khirnov.net> | 2022-08-22 14:53:40 +0200 |
---|---|---|
committer | Thilo Borgmann <thilo.borgmann@mail.de> | 2023-03-30 23:48:36 +0200 |
commit | 6e9d6a1d501ff74ce10da4e0e9dcfd72976d662d (patch) | |
tree | 694f098103c90a4d201166fc939bae9508441f86 | |
parent | 3980415627a187d188dc25669cea6b12912eb178 (diff) |
encode -> decode WIP
-rw-r--r-- | fftools/ffmpeg.c | 170 | ||||
-rw-r--r-- | fftools/ffmpeg.h | 15 | ||||
-rw-r--r-- | fftools/ffmpeg_demux.c | 23 | ||||
-rw-r--r-- | fftools/ffmpeg_filter.c | 58 | ||||
-rw-r--r-- | fftools/ffmpeg_mux.c | 22 | ||||
-rw-r--r-- | fftools/ffmpeg_opt.c | 53 |
6 files changed, 234 insertions, 107 deletions
diff --git a/fftools/ffmpeg.c b/fftools/ffmpeg.c index d721a5e721..0d60ca1fe3 100644 --- a/fftools/ffmpeg.c +++ b/fftools/ffmpeg.c @@ -668,6 +668,8 @@ static void close_output_stream(OutputStream *ost) if (ost->sq_idx_encode >= 0) sq_send(of->sq_encode, ost->sq_idx_encode, SQFRAME(NULL)); + if (ost->dec) + av_thread_message_queue_set_err_recv(ost->dec, AVERROR_EOF); } static int check_recording_time(OutputStream *ost, int64_t ts, AVRational tb) @@ -1352,6 +1354,74 @@ static void do_video_out(OutputFile *of, av_frame_move_ref(ost->last_frame, next_picture); } +static int ifilter_parameters_from_codecpar(InputFilter *ifilter, AVCodecParameters *par) +{ + int ret; + + // We never got any input. Set a fake format, which will + // come from libavformat. + ifilter->format = par->format; + ifilter->sample_rate = par->sample_rate; + ifilter->width = par->width; + ifilter->height = par->height; + ifilter->sample_aspect_ratio = par->sample_aspect_ratio; + ret = av_channel_layout_copy(&ifilter->ch_layout, &par->ch_layout); + if (ret < 0) + return ret; + + return 0; +} + +static void flush_encoder(OutputStream *ost) +{ + AVCodecContext *enc = ost->enc_ctx; + OutputFile *of = output_files[ost->file_index]; + int ret; + + + // Try to enable encoding with no input frames. + // Maybe we should just let encoding fail instead. + if (!ost->initialized) { + FilterGraph *fg = ost->filter->graph; + + av_log(NULL, AV_LOG_WARNING, + "Finishing stream %d:%d without any data written to it.\n", + ost->file_index, ost->st->index); + + if (ost->filter && !fg->graph) { + int x; + for (x = 0; x < fg->nb_inputs; x++) { + InputFilter *ifilter = fg->inputs[x]; + if (ifilter->format < 0 && + ifilter_parameters_from_codecpar(ifilter, ifilter->ist->par) < 0) { + av_log(NULL, AV_LOG_ERROR, "Error copying paramerets from input stream\n"); + exit_program(1); + } + } + + if (!ifilter_has_all_input_formats(fg)) + return; + + ret = configure_filtergraph(fg); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Error configuring filter graph\n"); + exit_program(1); + } + + of_output_packet(of, ost->pkt, ost, 1); + } + + init_output_stream_wrapper(ost, NULL, 1); + } + + if (enc->codec_type != AVMEDIA_TYPE_VIDEO && enc->codec_type != AVMEDIA_TYPE_AUDIO) + return; + + ret = submit_encode_frame(of, ost, NULL); + if (ret != AVERROR_EOF) + exit_program(1); +} + /** * Get and encode new output from any of the filtergraphs, without causing * activity. @@ -1397,6 +1467,7 @@ static int reap_filters(int flush) } else if (flush && ret == AVERROR_EOF) { if (av_buffersink_get_type(filter) == AVMEDIA_TYPE_VIDEO) do_video_out(of, ost, NULL); + flush_encoder(ost); } break; } @@ -1763,24 +1834,6 @@ static void print_report(int is_last_report, int64_t timer_start, int64_t cur_ti print_final_stats(total_size); } -static int ifilter_parameters_from_codecpar(InputFilter *ifilter, AVCodecParameters *par) -{ - int ret; - - // We never got any input. Set a fake format, which will - // come from libavformat. - ifilter->format = par->format; - ifilter->sample_rate = par->sample_rate; - ifilter->width = par->width; - ifilter->height = par->height; - ifilter->sample_aspect_ratio = par->sample_aspect_ratio; - ret = av_channel_layout_copy(&ifilter->ch_layout, &par->ch_layout); - if (ret < 0) - return ret; - - return 0; -} - static void flush_encoders(void) { int ret; @@ -1792,52 +1845,8 @@ static void flush_encoders(void) } for (OutputStream *ost = ost_iter(NULL); ost; ost = ost_iter(ost)) { - AVCodecContext *enc = ost->enc_ctx; - OutputFile *of = output_files[ost->file_index]; - - if (!enc) - continue; - - // Try to enable encoding with no input frames. - // Maybe we should just let encoding fail instead. - if (!ost->initialized) { - FilterGraph *fg = ost->filter->graph; - - av_log(ost, AV_LOG_WARNING, - "Finishing stream without any data written to it.\n"); - - if (ost->filter && !fg->graph) { - int x; - for (x = 0; x < fg->nb_inputs; x++) { - InputFilter *ifilter = fg->inputs[x]; - if (ifilter->format < 0 && - ifilter_parameters_from_codecpar(ifilter, ifilter->ist->par) < 0) { - av_log(ost, AV_LOG_ERROR, "Error copying paramerets from input stream\n"); - exit_program(1); - } - } - - if (!ifilter_has_all_input_formats(fg)) - continue; - - ret = configure_filtergraph(fg); - if (ret < 0) { - av_log(ost, AV_LOG_ERROR, "Error configuring filter graph\n"); - exit_program(1); - } - - of_output_packet(of, ost->pkt, ost, 1); - } - - init_output_stream_wrapper(ost, NULL, 1); - } - - if (enc->codec_type != AVMEDIA_TYPE_VIDEO && enc->codec_type != AVMEDIA_TYPE_AUDIO) - continue; - - ret = submit_encode_frame(of, ost, NULL); - if (ret != AVERROR_EOF) - exit_program(1); + if (ost->enc_ctx) + flush_encoder(ost); } } @@ -2130,6 +2139,8 @@ static int send_frame_to_filters(InputStream *ist, AVFrame *decoded_frame) av_assert1(ist->nb_filters > 0); /* ensure ret is initialized */ for (i = 0; i < ist->nb_filters; i++) { + //fprintf(stderr, "send frame to filters %d:%d %g\n", ist->file_index, ist->st->index, + // decoded_frame->pts * av_q2d(ist->st->time_base)); ret = ifilter_send_frame(ist->filters[i], decoded_frame, i < ist->nb_filters - 1); if (ret == AVERROR_EOF) ret = 0; /* ignore */ @@ -2216,6 +2227,8 @@ static int decode_video(InputStream *ist, AVPacket *pkt, int *got_output, int64_ if (ist->dts != AV_NOPTS_VALUE) dts = av_rescale_q(ist->dts, AV_TIME_BASE_Q, ist->st->time_base); if (pkt) { + //fprintf(stderr, "decode %d:%d %g\n", ist->file_index, ist->st->index, + // pkt->pts * av_q2d(ist->st->time_base)); pkt->dts = dts; // ffmpeg.c probably shouldn't do this } @@ -3488,15 +3501,24 @@ static OutputStream *choose_output(void) ost->initialized, ost->inputs_done, ost->finished); } - if (!ost->initialized && !ost->inputs_done) - return ost->unavailable ? NULL : ost; + if (!ost->initialized && !ost->inputs_done) { + if (ost->unavailable) + break; + return ost; + } if (!ost->finished && opts < opts_min) { opts_min = opts; ost_min = ost->unavailable ? NULL : ost; } } - return ost_min; + if (ost_min) + return ost_min; + for (OutputStream *ost = ost_iter(NULL); ost; ost = ost_iter(ost)) { + if (!ost->finished) + return ost; + } + return NULL; } static void set_tty_echo(int on) @@ -3769,8 +3791,10 @@ static int process_input(int file_index) is = ifile->ctx; ret = ifile_get_packet(ifile, &pkt); + //fprintf(stderr, "get input packet %d: %d\n", file_index, ret); if (ret == AVERROR(EAGAIN)) { + //fprintf(stderr, "eagain\n"); ifile->eagain = 1; return ret; } @@ -3931,6 +3955,7 @@ static int transcode_step(void) av_log(NULL, AV_LOG_VERBOSE, "No more inputs to read from, finishing.\n"); return AVERROR_EOF; } + //fprintf(stderr, "choose output %d:%d\n", ost->file_index, ost->index); if (ost->filter && !ost->filter->graph->graph) { if (ifilter_has_all_input_formats(ost->filter->graph)) { @@ -3969,8 +3994,10 @@ static int transcode_step(void) if ((ret = transcode_from_filter(ost->filter->graph, &ist)) < 0) return ret; - if (!ist) + if (!ist) { + reset_eagain(); return 0; + } } else if (ost->filter) { int i; for (i = 0; i < ost->filter->graph->nb_inputs; i++) { @@ -3989,10 +4016,13 @@ static int transcode_step(void) av_assert0(ist); } + //fprintf(stderr, "process input %d\n", ist->file_index); ret = process_input(ist->file_index); if (ret == AVERROR(EAGAIN)) { - if (input_files[ist->file_index]->eagain) + if (input_files[ist->file_index]->eagain) { + //fprintf(stderr, "unavailable %d:%d->%d:%d", ist->file_index, ist->st->index, ost->file_index, ost->index); ost->unavailable = 1; + } return 0; } @@ -4052,7 +4082,7 @@ static int transcode(void) process_input_packet(ist, NULL, 0); } } - flush_encoders(); + //flush_encoders(); term_exit(); diff --git a/fftools/ffmpeg.h b/fftools/ffmpeg.h index f1412f6446..53c055d273 100644 --- a/fftools/ffmpeg.h +++ b/fftools/ffmpeg.h @@ -81,6 +81,7 @@ typedef struct HWDevice { /* select an input stream for an output stream */ typedef struct StreamMap { int disabled; /* 1 is this mapping is disabled by a negative map */ + int decoder; int file_index; int stream_index; char *linklabel; /* name of an output link, for mapping lavfi outputs */ @@ -266,6 +267,8 @@ typedef struct OptionsContext { int nb_enc_stats_post_fmt; SpecifierOpt *mux_stats_fmt; int nb_mux_stats_fmt; + + int open_pass; } OptionsContext; typedef struct InputFilter { @@ -447,6 +450,12 @@ typedef struct LastFrameDuration { int64_t duration; } LastFrameDuration; +typedef struct Decoder { + AVThreadMessageQueue *fifo; + struct OutputFile *of; + struct OutputStream *ost; +} Decoder; + typedef struct InputFile { int index; @@ -482,6 +491,8 @@ typedef struct InputFile { * the last frame duration back to the demuxer thread */ AVThreadMessageQueue *audio_duration_queue; int audio_duration_queue_size; + + Decoder *dec; } InputFile; enum forced_keyframes_const { @@ -687,6 +698,8 @@ typedef struct OutputStream { * subtitles utilizing fix_sub_duration at random access points. */ unsigned int fix_sub_duration_heartbeat; + + AVThreadMessageQueue *dec; } OutputStream; typedef struct OutputFile { @@ -785,7 +798,7 @@ int configure_filtergraph(FilterGraph *fg); void check_filter_outputs(void); int filtergraph_is_simple(FilterGraph *fg); int init_simple_filtergraph(InputStream *ist, OutputStream *ost); -int init_complex_filtergraph(FilterGraph *fg); +int init_complex_filtergraph(FilterGraph *fg, int pass); void sub2video_update(InputStream *ist, int64_t heartbeat_pts, AVSubtitle *sub); diff --git a/fftools/ffmpeg_demux.c b/fftools/ffmpeg_demux.c index ffece60720..56ba0dd7c4 100644 --- a/fftools/ffmpeg_demux.c +++ b/fftools/ffmpeg_demux.c @@ -72,6 +72,8 @@ typedef struct Demuxer { int non_blocking; } Demuxer; +//#include <sys/prctl.h> + typedef struct DemuxMsg { AVPacket *pkt; int looping; @@ -241,6 +243,10 @@ static void *input_thread(void *arg) AVPacket *pkt; unsigned flags = d->non_blocking ? AV_THREAD_MESSAGE_NONBLOCK : 0; int ret = 0; + char thread_name[16]; + + //snprintf(thread_name, sizeof(thread_name), "demux%d", f->index); + //prctl(PR_SET_NAME, (unsigned long)thread_name, 0, 0, 0); pkt = av_packet_alloc(); if (!pkt) { @@ -253,7 +259,19 @@ static void *input_thread(void *arg) while (1) { DemuxMsg msg = { NULL }; - ret = av_read_frame(f->ctx, pkt); + if (f->dec) { + AVPacket *pkt1; + ret = av_thread_message_queue_recv(f->dec->fifo, &pkt1, 0); + if (ret >= 0) { + av_packet_move_ref(pkt, pkt1); + av_packet_free(&pkt1); + pkt->flags |= AV_PKT_FLAG_TRUSTED; + pkt->stream_index = 0; + //fprintf(stderr, "dec received %ld\n", pkt->pts); + f->streams[f->index]->st->time_base = f->dec->ost->mux_timebase; + } + } else + ret = av_read_frame(f->ctx, pkt); if (ret == AVERROR(EAGAIN)) { av_usleep(10000); @@ -372,6 +390,9 @@ static int thread_start(Demuxer *d) (f->ctx->pb ? !f->ctx->pb->seekable : strcmp(f->ctx->iformat->name, "lavfi"))) d->non_blocking = 1; + if (nb_input_files > 1 && + f->dec) + d->non_blocking = 1; ret = av_thread_message_queue_alloc(&d->in_thread_queue, d->thread_queue_size, sizeof(DemuxMsg)); if (ret < 0) diff --git a/fftools/ffmpeg_filter.c b/fftools/ffmpeg_filter.c index 1f5bbf6c4d..02ce6f8000 100644 --- a/fftools/ffmpeg_filter.c +++ b/fftools/ffmpeg_filter.c @@ -228,19 +228,10 @@ static char *describe_filter_link(FilterGraph *fg, AVFilterInOut *inout, int in) return res; } -static void init_input_filter(FilterGraph *fg, AVFilterInOut *in) +static InputStream *ifilter_get_ist(FilterGraph *fg, AVFilterInOut *in) { - InputStream *ist = NULL; enum AVMediaType type = avfilter_pad_get_type(in->filter_ctx->input_pads, in->pad_idx); - InputFilter *ifilter; - int i; - - // TODO: support other filter types - if (type != AVMEDIA_TYPE_VIDEO && type != AVMEDIA_TYPE_AUDIO) { - av_log(NULL, AV_LOG_FATAL, "Only video and audio filters supported " - "currently.\n"); - exit_program(1); - } + InputStream *ist; if (in->name) { AVFormatContext *s; @@ -251,11 +242,11 @@ static void init_input_filter(FilterGraph *fg, AVFilterInOut *in) if (file_idx < 0 || file_idx >= nb_input_files) { av_log(NULL, AV_LOG_FATAL, "Invalid file index %d in filtergraph description %s.\n", file_idx, fg->graph_desc); - exit_program(1); + return NULL; } s = input_files[file_idx]->ctx; - for (i = 0; i < s->nb_streams; i++) { + for (int i = 0; i < s->nb_streams; i++) { enum AVMediaType stream_type = s->streams[i]->codecpar->codec_type; if (stream_type != type && !(stream_type == AVMEDIA_TYPE_SUBTITLE && @@ -278,6 +269,7 @@ static void init_input_filter(FilterGraph *fg, AVFilterInOut *in) exit_program(1); } } else { + int i; /* find the first unused stream of corresponding type */ for (ist = ist_iter(NULL); ist; ist = ist_iter(ist)) { if (ist->user_set_discard == AVDISCARD_ALL) @@ -292,6 +284,24 @@ static void init_input_filter(FilterGraph *fg, AVFilterInOut *in) exit_program(1); } } + + return ist; +} + +static int init_input_filter(FilterGraph *fg, AVFilterInOut *in) +{ + InputStream *ist = NULL; + enum AVMediaType type = avfilter_pad_get_type(in->filter_ctx->input_pads, in->pad_idx); + InputFilter *ifilter; + + // TODO: support other filter types + if (type != AVMEDIA_TYPE_VIDEO && type != AVMEDIA_TYPE_AUDIO) { + av_log(NULL, AV_LOG_FATAL, "Only video and audio filters supported " + "currently.\n"); + exit_program(1); + } + + ist = ifilter_get_ist(fg, in); av_assert0(ist); ist->discard = 0; @@ -312,6 +322,8 @@ static void init_input_filter(FilterGraph *fg, AVFilterInOut *in) GROW_ARRAY(ist->filters, ist->nb_filters); ist->filters[ist->nb_filters - 1] = ifilter; + + return 0; } static int read_binary(const char *path, uint8_t **data, int *len) @@ -464,12 +476,15 @@ fail: return ret; } -int init_complex_filtergraph(FilterGraph *fg) +int init_complex_filtergraph(FilterGraph *fg, int pass) { AVFilterInOut *inputs, *outputs, *cur; AVFilterGraph *graph; int ret = 0; + if (fg->nb_inputs || fg->nb_outputs) + return 0; + /* this graph is only used for determining the kinds of inputs * and outputs we have, and is discarded on exit from this function */ graph = avfilter_graph_alloc(); @@ -481,8 +496,19 @@ int init_complex_filtergraph(FilterGraph *fg) if (ret < 0) goto fail; - for (cur = inputs; cur; cur = cur->next) - init_input_filter(fg, cur); + for (cur = inputs; cur; cur = cur->next) { + if (!ifilter_get_ist(fg, cur)) { + if (pass == 0) + return 0; + exit_program(1); + } + } + + for (cur = inputs; cur; cur = cur->next) { + ret = init_input_filter(fg, cur); + if (ret < 0) + return ret; + } for (cur = outputs; cur;) { OutputFilter *const ofilter = ALLOC_ARRAY_ELEM(fg->outputs, fg->nb_outputs); diff --git a/fftools/ffmpeg_mux.c b/fftools/ffmpeg_mux.c index cf58051949..5d433d5af4 100644 --- a/fftools/ffmpeg_mux.c +++ b/fftools/ffmpeg_mux.c @@ -26,6 +26,7 @@ #include "sync_queue.h" #include "thread_queue.h" +#include "libavutil/avassert.h" #include "libavutil/fifo.h" #include "libavutil/intreadwrite.h" #include "libavutil/log.h" @@ -38,6 +39,8 @@ #include "libavformat/avformat.h" #include "libavformat/avio.h" +//#include <sys/prctl.h> + int want_sdp = 1; static Muxer *mux_from_of(OutputFile *of) @@ -201,6 +204,10 @@ static void *muxer_thread(void *arg) OutputFile *of = &mux->of; AVPacket *pkt = NULL; int ret = 0; + //char thread_name[16]; + + //snprintf(thread_name, sizeof(thread_name), "mux%d", of->index); + //prctl(PR_SET_NAME, (unsigned long)thread_name, 0, 0, 0); pkt = av_packet_alloc(); if (!pkt) { @@ -222,6 +229,17 @@ static void *muxer_thread(void *arg) } ost = of->streams[stream_idx]; + + if (ost->dec) { + if (ret < 0) { + av_thread_message_queue_set_err_recv(ost->dec, AVERROR_EOF); + } else { + AVPacket *pkt1 = av_packet_clone(pkt); + ret = av_thread_message_queue_send(ost->dec, &pkt1, 0); + av_assert0(ret >= 0); + } + } + ret = sync_queue_process(mux, ost, ret < 0 ? NULL : pkt, &stream_eof); av_packet_unref(pkt); if (ret == AVERROR_EOF && stream_eof) @@ -430,8 +448,8 @@ static int thread_start(Muxer *mux) AVPacket *pkt; /* try to improve muxing time_base (only possible if nothing has been written yet) */ - if (!av_fifo_can_read(ms->muxing_queue)) - ost->mux_timebase = ost->st->time_base; + //if (!av_fifo_can_read(ms->muxing_queue)) + // ost->mux_timebase = ost->st->time_base; while (av_fifo_read(ms->muxing_queue, &pkt, 1) >= 0) { ret = thread_submit_packet(mux, ost, pkt); diff --git a/fftools/ffmpeg_opt.c b/fftools/ffmpeg_opt.c index 055275d813..51f2fcaf31 100644 --- a/fftools/ffmpeg_opt.c +++ b/fftools/ffmpeg_opt.c @@ -394,6 +394,12 @@ static int opt_map(void *optctx, const char *opt, const char *arg) av_log(NULL, AV_LOG_ERROR, "Invalid output link label: %s.\n", map); exit_program(1); } + } else if (map[0] == 'd' && map[1] == ':') { + GROW_ARRAY(o->stream_maps, o->nb_stream_maps); + m = &o->stream_maps[o->nb_stream_maps - 1]; + m->decoder = 1; + m->file_index = strtol(map + 2, &p, 0); + m->stream_index = strtol(p + 1, &p, 0); } else { if (allow_unused = strchr(map, '?')) *allow_unused = 0; @@ -745,12 +751,12 @@ static int opt_streamid(void *optctx, const char *opt, const char *arg) return 0; } -static int init_complex_filters(void) +static int init_complex_filters(int pass) { int i, ret = 0; for (i = 0; i < nb_filtergraphs; i++) { - ret = init_complex_filtergraph(filtergraphs[i]); + ret = init_complex_filtergraph(filtergraphs[i], pass); if (ret < 0) return ret; } @@ -1213,15 +1219,17 @@ void show_usage(void) enum OptGroup { GROUP_OUTFILE, GROUP_INFILE, + GROUP_DECODER, }; static const OptionGroupDef groups[] = { [GROUP_OUTFILE] = { "output url", NULL, OPT_OUTPUT }, [GROUP_INFILE] = { "input url", "i", OPT_INPUT }, + [GROUP_DECODER] = { "decoding process", "dec", OPT_INPUT }, }; -static int open_files(OptionGroupList *l, const char *inout, - int (*open_file)(const OptionsContext*, const char*)) +static int open_files(OptionGroupList *l, const char *inout, int pass, + int (*open_file)(OptionsContext*, const char*, int, int)) { int i, ret; @@ -1241,7 +1249,7 @@ static int open_files(OptionGroupList *l, const char *inout, } av_log(NULL, AV_LOG_DEBUG, "Opening an %s file: %s.\n", inout, g->arg); - ret = open_file(&o, g->arg); + ret = open_file(&o, g->arg, pass, i); uninit_options(&o); if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Error opening %s file %s.\n", @@ -1280,24 +1288,34 @@ int ffmpeg_parse_options(int argc, char **argv) term_init(); /* open input files */ - ret = open_files(&octx.groups[GROUP_INFILE], "input", ifile_open); + ret = open_files(&octx.groups[GROUP_INFILE], "input", 0, ifile_open); if (ret < 0) { av_log(NULL, AV_LOG_FATAL, "Error opening input files: "); goto fail; } - /* create the complex filtergraphs */ - ret = init_complex_filters(); - if (ret < 0) { - av_log(NULL, AV_LOG_FATAL, "Error initializing complex filters.\n"); - goto fail; - } + apply_sync_offsets(); - /* open output files */ - ret = open_files(&octx.groups[GROUP_OUTFILE], "output", of_open); - if (ret < 0) { - av_log(NULL, AV_LOG_FATAL, "Error opening output files: "); - goto fail; + for (int pass = 0; pass < 2; pass++) { + /* create the complex filtergraphs */ + ret = init_complex_filters(pass); + if (ret < 0 && pass == 1) { + av_log(NULL, AV_LOG_FATAL, "Error initializing complex filters.\n"); + goto fail; + } + + /* open output files */ + ret = open_files(&octx.groups[GROUP_OUTFILE], "output", pass, of_open); + if (ret < 0) { + av_log(NULL, AV_LOG_FATAL, "Error opening output files: "); + goto fail; + } + + ret = open_files(&octx.groups[GROUP_DECODER], "decoder", pass, open_decoder); + if (ret < 0) { + av_log(NULL, AV_LOG_FATAL, "Error opening decoders: "); + goto fail; + } } correct_input_start_times(); @@ -1770,6 +1788,7 @@ const OptionDef options[] = { "initialise hardware device", "args" }, { "filter_hw_device", HAS_ARG | OPT_EXPERT, { .func_arg = opt_filter_hw_device }, "set hardware device used when filtering", "device" }, + { "open_pass", HAS_ARG | OPT_EXPERT | OPT_OUTPUT | OPT_INT | OPT_OFFSET, { .off = OFFSET(open_pass) } }, { NULL, }, }; |