@@ -1799,7 +1799,7 @@ Set the size of the canvas used to render subtitles.
@section Advanced options
@table @option
-@item -map [-]@var{input_file_id}[:@var{stream_specifier}][?] | @var{[linklabel]} (@emph{output})
+@item -map [-]@var{input_file_id}[:@var{stream_specifier}][:@var{view_specifier}][?] | @var{[linklabel]} (@emph{output})
Create one or more streams in the output file. This option has two forms for
specifying the data source(s): the first selects one or more streams from some
@@ -1814,6 +1814,26 @@ only those streams that match the specifier are used (see the
A @code{-} character before the stream identifier creates a "negative" mapping.
It disables matching streams from already created mappings.
+An optional @var{view_specifier} may be given after the stream specifier, which
+for multiview video specifies the view to be used. The view specifier may have
+one of the following formats:
+@table @option
+@item view:@var{view_id}
+select a view by its ID; @var{view_id} may be set to 'all' to use all the views
+interleaved into one stream;
+
+@item vidx:@var{view_idx}
+select a view by its index; i.e. 0 is the base view, 1 is the first non-base
+view, etc.
+
+@item vpos:@var{position}
+select a view by its display position; @var{position} may be @code{left} or
+@code{right}
+@end table
+The default for transcoding is to only use the base view, i.e. the equivalent of
+@code{vidx:0}. For streamcopy, view specifiers are not supported and all views
+are always copied.
+
A trailing @code{?} after the stream index will allow the map to be
optional: if the map matches no streams the map will be ignored instead
of failing. Note the map will still fail if an invalid input file index
@@ -2206,11 +2226,15 @@ distinguished by the format of the corresponding link label:
@item
To connect an input stream, use @code{[file_index:stream_specifier]} (i.e. the
same syntax as @option{-map}). If @var{stream_specifier} matches multiple
-streams, the first one will be used.
+streams, the first one will be used. For multiview video, the stream specifier
+may be followed by the view specifier, see documentation for the @option{-map}
+option for its syntax.
@item
To connect a loopback decoder use [dec:@var{dec_idx}], where @var{dec_idx} is
-the index of the loopback decoder to be connected to given input.
+the index of the loopback decoder to be connected to given input. For multiview
+video, the decoder index may be followed by the view specifier, see
+documentation for the @option{-map} option for its syntax.
@item
To connect an output from another complex filtergraph, use its link label. E.g
@@ -988,7 +988,7 @@ FILE *get_preset_file(char *filename, size_t filename_size,
return f;
}
-static int cmdutils_isalnum(char c)
+int cmdutils_isalnum(char c)
{
return (c >= '0' && c <= '9') ||
(c >= 'A' && c <= 'Z') ||
@@ -554,4 +554,6 @@ void remove_avoptions(AVDictionary **a, AVDictionary *b);
/* Check if any keys exist in dictionary m */
int check_avoptions(AVDictionary *m);
+int cmdutils_isalnum(char c);
+
#endif /* FFTOOLS_CMDUTILS_H */
@@ -112,12 +112,32 @@ typedef struct HWDevice {
AVBufferRef *device_ref;
} HWDevice;
+enum ViewSpecifierType {
+ // no specifier given
+ VIEW_SPECIFIER_TYPE_NONE = 0,
+ // val is view index
+ VIEW_SPECIFIER_TYPE_IDX,
+ // val is view ID
+ VIEW_SPECIFIER_TYPE_ID,
+ // specify view by its position, val is AV_STEREO3D_VIEW_LEFT/RIGHT
+ VIEW_SPECIFIER_TYPE_POS,
+ // use all views, val is ignored
+ VIEW_SPECIFIER_TYPE_ALL,
+};
+
+typedef struct ViewSpecifier {
+ enum ViewSpecifierType type;
+ unsigned val;
+} ViewSpecifier;
+
/* select an input stream for an output stream */
typedef struct StreamMap {
int disabled; /* 1 is this mapping is disabled by a negative map */
int file_index;
int stream_index;
char *linklabel; /* name of an output link, for mapping lavfi outputs */
+
+ ViewSpecifier vs;
} StreamMap;
typedef struct OptionsContext {
@@ -311,6 +331,10 @@ typedef struct OutputFilterOptions {
int sample_rate;
AVChannelLayout ch_layout;
+
+ // for simple filtergraphs only, view specifier passed
+ // along to the decoder
+ const ViewSpecifier *vs;
} OutputFilterOptions;
typedef struct InputFilter {
@@ -810,7 +834,21 @@ void dec_free(Decoder **pdec);
*
* @param opts filtergraph input options, to be filled by this function
*/
-int dec_filter_add(Decoder *dec, InputFilter *ifilter, InputFilterOptions *opts);
+int dec_filter_add(Decoder *dec, InputFilter *ifilter, InputFilterOptions *opts,
+ const ViewSpecifier *vs, SchedulerNode *src);
+
+/*
+ * For multiview video, request output of the view(s) determined by vs.
+ * May be called multiple times.
+ *
+ * If this function is never called, only the base view is output. If it is
+ * called at least once, only the views requested are output.
+ *
+ * @param src scheduler node from which the frames corresponding vs
+ * will originate
+ */
+int dec_request_view(Decoder *dec, const ViewSpecifier *vs,
+ SchedulerNode *src);
int enc_alloc(Encoder **penc, const AVCodec *codec,
Scheduler *sch, unsigned sch_idx);
@@ -840,7 +878,8 @@ void ifile_close(InputFile **f);
int ist_output_add(InputStream *ist, OutputStream *ost);
int ist_filter_add(InputStream *ist, InputFilter *ifilter, int is_simple,
- InputFilterOptions *opts);
+ const ViewSpecifier *vs, InputFilterOptions *opts,
+ SchedulerNode *src);
/**
* Find an unused input stream of given type.
@@ -868,6 +907,8 @@ void opt_match_per_stream_int64(void *logctx, const SpecifierOptList *sol,
void opt_match_per_stream_dbl(void *logctx, const SpecifierOptList *sol,
AVFormatContext *fc, AVStream *st, double *out);
+int view_specifier_parse(const char **pspec, ViewSpecifier *vs);
+
int muxer_thread(void *arg);
int encoder_thread(void *arg);
@@ -16,6 +16,8 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
+#include <strings.h>
+
#include "libavutil/avassert.h"
#include "libavutil/avstring.h"
#include "libavutil/dict.h"
@@ -25,6 +27,7 @@
#include "libavutil/opt.h"
#include "libavutil/pixdesc.h"
#include "libavutil/pixfmt.h"
+#include "libavutil/stereo3d.h"
#include "libavutil/time.h"
#include "libavutil/timestamp.h"
@@ -39,6 +42,7 @@ typedef struct DecoderPriv {
AVCodecContext *dec_ctx;
AVFrame *frame;
+ AVFrame *frame_tmp_ref;
AVPacket *pkt;
// override output video sample aspect ratio with this value
@@ -77,6 +81,23 @@ typedef struct DecoderPriv {
char log_name[32];
char *parent_name;
+ // user specified decoder multiview options manually
+ int multiview_user_config;
+
+ struct {
+ ViewSpecifier vs;
+ unsigned out_idx;
+ } *views_requested;
+ int nb_views_requested;
+
+ /* A map of view ID to decoder outputs.
+ * MUST NOT be accessed outside of get_format()/get_buffer() */
+ struct {
+ unsigned id;
+ unsigned out_mask;
+ } *view_map;
+ int nb_view_map;
+
struct {
AVDictionary *opts;
const AVCodec *codec;
@@ -106,6 +127,7 @@ void dec_free(Decoder **pdec)
avcodec_free_context(&dp->dec_ctx);
av_frame_free(&dp->frame);
+ av_frame_free(&dp->frame_tmp_ref);
av_packet_free(&dp->pkt);
av_dict_free(&dp->standalone_init.opts);
@@ -116,6 +138,9 @@ void dec_free(Decoder **pdec)
av_freep(&dp->parent_name);
+ av_freep(&dp->views_requested);
+ av_freep(&dp->view_map);
+
av_freep(pdec);
}
@@ -357,7 +382,8 @@ fail:
return err;
}
-static int video_frame_process(DecoderPriv *dp, AVFrame *frame)
+static int video_frame_process(DecoderPriv *dp, AVFrame *frame,
+ unsigned *outputs_mask)
{
#if FFMPEG_OPT_TOP
if (dp->flags & DECODER_FLAG_TOP_FIELD_FIRST) {
@@ -419,6 +445,9 @@ static int video_frame_process(DecoderPriv *dp, AVFrame *frame)
}
}
+ if (dp->nb_view_map)
+ *outputs_mask = (uintptr_t)frame->opaque;
+
return 0;
}
@@ -715,6 +744,7 @@ static int packet_decode(DecoderPriv *dp, AVPacket *pkt, AVFrame *frame)
while (1) {
FrameData *fd;
+ unsigned outputs_mask = 1;
av_frame_unref(frame);
@@ -763,7 +793,7 @@ static int packet_decode(DecoderPriv *dp, AVPacket *pkt, AVFrame *frame)
audio_ts_process(dp, frame);
} else {
- ret = video_frame_process(dp, frame);
+ ret = video_frame_process(dp, frame, &outputs_mask);
if (ret < 0) {
av_log(dp, AV_LOG_FATAL,
"Error while processing the decoded data\n");
@@ -773,10 +803,28 @@ static int packet_decode(DecoderPriv *dp, AVPacket *pkt, AVFrame *frame)
dp->dec.frames_decoded++;
- ret = sch_dec_send(dp->sch, dp->sch_idx, 0, frame);
- if (ret < 0) {
- av_frame_unref(frame);
- return ret == AVERROR_EOF ? AVERROR_EXIT : ret;
+ for (int i = 0; i < av_popcount(outputs_mask); i++) {
+ AVFrame *to_send = frame;
+ int pos;
+
+ av_assert0(outputs_mask);
+ pos = ffs(outputs_mask) - 1;
+ outputs_mask &= ~(1U << pos);
+
+ // this is not the last output and sch_dec_send() consumes the frame
+ // given to it, so make a temporary reference
+ if (outputs_mask) {
+ to_send = dp->frame_tmp_ref;
+ ret = av_frame_ref(to_send, frame);
+ if (ret < 0)
+ return ret;
+ }
+
+ ret = sch_dec_send(dp->sch, dp->sch_idx, pos, to_send);
+ if (ret < 0) {
+ av_frame_unref(to_send);
+ return ret == AVERROR_EOF ? AVERROR_EXIT : ret;
+ }
}
}
}
@@ -975,10 +1023,270 @@ finish:
return ret;
}
+int dec_request_view(Decoder *d, const ViewSpecifier *vs,
+ SchedulerNode *src)
+{
+ DecoderPriv *dp = dp_from_dec(d);
+ unsigned out_idx = 0;
+ int ret;
+
+ if (dp->multiview_user_config) {
+ if (!vs || vs->type == VIEW_SPECIFIER_TYPE_NONE) {
+ *src = SCH_DEC_OUT(dp->sch_idx, 0);
+ return 0;
+ }
+
+ av_log(dp, AV_LOG_ERROR, "Manually selecting views with -view_ids "
+ "or -output_layer_set cannot be combined with view selection "
+ "via stream specifiers. It is strongly recommended you always "
+ "use stream specifiers only.\n");
+ return AVERROR(EINVAL);
+ }
+
+ // when multiview_user_config is not set, NONE specifier is treated
+ // as requesting the base view
+ vs = (vs && vs->type != VIEW_SPECIFIER_TYPE_NONE) ? vs :
+ &(ViewSpecifier){ .type = VIEW_SPECIFIER_TYPE_IDX, .val = 0 };
+
+ // check if the specifier matches an already-existing one
+ for (int i = 0; i < dp->nb_views_requested; i++) {
+ const ViewSpecifier *vs1 = &dp->views_requested[i].vs;
+
+ if (vs->type == vs1->type &&
+ (vs->type == VIEW_SPECIFIER_TYPE_ALL || vs->val == vs1->val)) {
+ *src = SCH_DEC_OUT(dp->sch_idx, dp->views_requested[i].out_idx);
+ return 0;
+ }
+ }
+
+ ret = GROW_ARRAY(dp->views_requested, dp->nb_views_requested);
+ if (ret < 0)
+ return ret;
+
+ if (dp->nb_views_requested > 1) {
+ ret = sch_add_dec_output(dp->sch, dp->sch_idx);
+ if (ret < 0)
+ return ret;
+ out_idx = ret;
+ }
+
+ dp->views_requested[dp->nb_views_requested - 1].out_idx = out_idx;
+ dp->views_requested[dp->nb_views_requested - 1].vs = *vs;
+
+ *src = SCH_DEC_OUT(dp->sch_idx,
+ dp->views_requested[dp->nb_views_requested - 1].out_idx);
+
+ return 0;
+}
+
+static int setup_multiview(DecoderPriv *dp, AVCodecContext *dec_ctx)
+{
+ unsigned views_wanted = 0;
+
+ unsigned nb_view_ids_av, nb_view_ids;
+ unsigned *view_ids_av = NULL, *view_pos_av = NULL;
+ int *view_ids = NULL;
+ int ret;
+
+ // no views/only base view were requested - do nothing
+ if (!dp->nb_views_requested ||
+ (dp->nb_views_requested == 1 &&
+ dp->views_requested[0].vs.type == VIEW_SPECIFIER_TYPE_IDX &&
+ dp->views_requested[0].vs.val == 0))
+ return 0;
+
+ av_freep(&dp->view_map);
+ dp->nb_view_map = 0;
+
+ // retrieve views available in current CVS
+ ret = av_opt_get_array_size(dec_ctx, "view_ids_available",
+ AV_OPT_SEARCH_CHILDREN, &nb_view_ids_av);
+ if (ret < 0) {
+ av_log(dp, AV_LOG_ERROR,
+ "Multiview decoding requested, but decoder '%s' does not "
+ "support it\n", dec_ctx->codec->name);
+ return AVERROR(ENOSYS);
+ }
+
+ if (nb_view_ids_av) {
+ unsigned nb_view_pos_av;
+
+ if (nb_view_ids_av >= sizeof(views_wanted) * 8) {
+ av_log(dp, AV_LOG_ERROR, "Too many views in video: %u\n", nb_view_ids_av);
+ ret = AVERROR(ENOSYS);
+ goto fail;
+ }
+
+ view_ids_av = av_calloc(nb_view_ids_av, sizeof(*view_ids_av));
+ if (!view_ids_av) {
+ ret = AVERROR(ENOMEM);
+ goto fail;
+ }
+
+ ret = av_opt_get_array(dec_ctx, "view_ids_available",
+ AV_OPT_SEARCH_CHILDREN, 0, nb_view_ids_av,
+ AV_OPT_TYPE_UINT, view_ids_av);
+ if (ret < 0)
+ goto fail;
+
+ ret = av_opt_get_array_size(dec_ctx, "view_pos_available",
+ AV_OPT_SEARCH_CHILDREN, &nb_view_pos_av);
+ if (ret >= 0 && nb_view_pos_av == nb_view_ids_av) {
+ view_pos_av = av_calloc(nb_view_ids_av, sizeof(*view_pos_av));
+ if (!view_pos_av) {
+ ret = AVERROR(ENOMEM);
+ goto fail;
+ }
+
+ ret = av_opt_get_array(dec_ctx, "view_pos_available",
+ AV_OPT_SEARCH_CHILDREN, 0, nb_view_ids_av,
+ AV_OPT_TYPE_UINT, view_pos_av);
+ if (ret < 0)
+ goto fail;
+ }
+ } else {
+ // assume there is a single view with ID=0
+ nb_view_ids_av = 1;
+ view_ids_av = av_calloc(nb_view_ids_av, sizeof(*view_ids_av));
+ view_pos_av = av_calloc(nb_view_ids_av, sizeof(*view_pos_av));
+ if (!view_ids_av || !view_pos_av) {
+ ret = AVERROR(ENOMEM);
+ goto fail;
+ }
+ view_pos_av[0] = AV_STEREO3D_VIEW_UNSPEC;
+ }
+
+ dp->view_map = av_calloc(nb_view_ids_av, sizeof(*dp->view_map));
+ if (!dp->view_map) {
+ ret = AVERROR(ENOMEM);
+ goto fail;
+ }
+ dp->nb_view_map = nb_view_ids_av;
+
+ for (int i = 0; i < dp->nb_view_map; i++)
+ dp->view_map[i].id = view_ids_av[i];
+
+ // figure out which views should go to which output
+ for (int i = 0; i < dp->nb_views_requested; i++) {
+ const ViewSpecifier *vs = &dp->views_requested[i].vs;
+
+ switch (vs->type) {
+ case VIEW_SPECIFIER_TYPE_IDX:
+ if (vs->val >= nb_view_ids_av) {
+ av_log(dp, AV_LOG_WARNING,
+ "View with index %u requested, but only %u views available "
+ "in current video sequence (more views may or may not be "
+ "available in later sequences).\n",
+ vs->val, nb_view_ids_av);
+ continue;
+ }
+ views_wanted |= 1U << vs->val;
+ dp->view_map[vs->val].out_mask |= 1U << i;
+
+ break;
+ case VIEW_SPECIFIER_TYPE_ID: {
+ int view_idx = -1;
+
+ for (unsigned j = 0; j < nb_view_ids_av; j++) {
+ if (view_ids_av[j] == vs->val) {
+ view_idx = j;
+ break;
+ }
+ }
+ if (view_idx < 0) {
+ av_log(dp, AV_LOG_WARNING, "View with ID %u requested, "
+ "but is not available in the video sequence\n", vs->val);
+ continue;
+ }
+ views_wanted |= 1U << view_idx;
+ dp->view_map[view_idx].out_mask |= 1U << i;
+
+ break;
+ }
+ case VIEW_SPECIFIER_TYPE_POS: {
+ int view_idx = -1;
+
+ for (unsigned j = 0; view_pos_av && j < nb_view_ids_av; j++) {
+ if (view_pos_av[j] == vs->val) {
+ view_idx = j;
+ break;
+ }
+ }
+ if (view_idx < 0) {
+ av_log(dp, AV_LOG_WARNING, "View position %s requested, "
+ "but is not available in the video sequence\n", av_stereo3d_view_name(vs->val));
+ continue;
+ }
+ views_wanted |= 1U << view_idx;
+ dp->view_map[view_idx].out_mask |= 1U << i;
+
+ break;
+ }
+ case VIEW_SPECIFIER_TYPE_ALL:
+ views_wanted |= (1U << nb_view_ids_av) - 1;
+
+ for (int j = 0; j < dp->nb_view_map; j++)
+ dp->view_map[j].out_mask |= 1U << i;
+
+ break;
+ }
+ }
+ av_assert0(views_wanted);
+
+ // signal to decoder which views we want
+ nb_view_ids = av_popcount(views_wanted);
+ view_ids = av_malloc_array(nb_view_ids, sizeof(*view_ids));
+ if (!view_ids) {
+ ret = AVERROR(ENOMEM);
+ goto fail;
+ }
+
+ for (unsigned i = 0; i < nb_view_ids; i++) {
+ int pos;
+
+ av_assert0(views_wanted);
+ pos = ffs(views_wanted) - 1;
+ views_wanted &= ~(1U << pos);
+
+ view_ids[i] = view_ids_av[pos];
+ }
+
+ // unset view_ids in case we set it earlier
+ av_opt_set(dec_ctx, "view_ids", NULL, AV_OPT_SEARCH_CHILDREN);
+
+ ret = av_opt_set_array(dec_ctx, "view_ids", AV_OPT_SEARCH_CHILDREN,
+ 0, nb_view_ids, AV_OPT_TYPE_INT, view_ids);
+ if (ret < 0)
+ goto fail;
+
+ if (!dp->frame_tmp_ref) {
+ dp->frame_tmp_ref = av_frame_alloc();
+ if (!dp->frame_tmp_ref) {
+ ret = AVERROR(ENOMEM);
+ goto fail;
+ }
+ }
+
+fail:
+ av_freep(&view_ids_av);
+ av_freep(&view_pos_av);
+ av_freep(&view_ids);
+
+ return ret;
+}
+
static enum AVPixelFormat get_format(AVCodecContext *s, const enum AVPixelFormat *pix_fmts)
{
DecoderPriv *dp = s->opaque;
const enum AVPixelFormat *p;
+ int ret;
+
+ ret = setup_multiview(dp, s);
+ if (ret < 0) {
+ av_log(dp, AV_LOG_ERROR, "Error setting up multiview decoding: %s\n",
+ av_err2str(ret));
+ return AV_PIX_FMT_NONE;
+ }
for (p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) {
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(*p);
@@ -1009,6 +1317,27 @@ static enum AVPixelFormat get_format(AVCodecContext *s, const enum AVPixelFormat
return *p;
}
+static int get_buffer(AVCodecContext *dec_ctx, AVFrame *frame, int flags)
+{
+ DecoderPriv *dp = dec_ctx->opaque;
+
+ // for multiview video, store the output mask in frame opaque
+ if (dp->nb_view_map) {
+ int64_t view_id = 0;
+
+ av_opt_get_int(dec_ctx, "view_id_cur", AV_OPT_SEARCH_CHILDREN, &view_id);
+
+ for (int i = 0; i < dp->nb_view_map; i++) {
+ if (dp->view_map[i].id == view_id) {
+ frame->opaque = (void*)(uintptr_t)dp->view_map[i].out_mask;
+ break;
+ }
+ }
+ }
+
+ return avcodec_default_get_buffer2(dec_ctx, frame, flags);
+}
+
static HWDevice *hw_device_match_by_codec(const AVCodec *codec)
{
const AVCodecHWConfig *config;
@@ -1202,6 +1531,7 @@ static int dec_open(DecoderPriv *dp, AVDictionary **dec_opts,
dp->dec_ctx->opaque = dp;
dp->dec_ctx->get_format = get_format;
+ dp->dec_ctx->get_buffer2 = get_buffer;
dp->dec_ctx->pkt_timebase = o->time_base;
if (!av_dict_get(*dec_opts, "threads", NULL, 0))
@@ -1291,6 +1621,10 @@ int dec_init(Decoder **pdec, Scheduler *sch,
if (ret < 0)
return ret;
+ if (av_dict_get(*dec_opts, "view_ids", NULL, 0) ||
+ av_dict_get(*dec_opts, "output_layer_set", NULL, 0))
+ dp->multiview_user_config = 1;
+
ret = dec_open(dp, dec_opts, o, param_out);
if (ret < 0)
goto fail;
@@ -1363,6 +1697,10 @@ int dec_create(const OptionsContext *o, const char *arg, Scheduler *sch)
if (ret < 0)
return ret;
+ if (av_dict_get(dp->standalone_init.opts, "view_ids", NULL, 0) ||
+ av_dict_get(dp->standalone_init.opts, "output_layer_set", NULL, 0))
+ dp->multiview_user_config = 1;
+
if (o->codec_names.nb_opt) {
const char *name = o->codec_names.opt[o->codec_names.nb_opt - 1].u.str;
dp->standalone_init.codec = avcodec_find_decoder_by_name(name);
@@ -1375,7 +1713,8 @@ int dec_create(const OptionsContext *o, const char *arg, Scheduler *sch)
return 0;
}
-int dec_filter_add(Decoder *d, InputFilter *ifilter, InputFilterOptions *opts)
+int dec_filter_add(Decoder *d, InputFilter *ifilter, InputFilterOptions *opts,
+ const ViewSpecifier *vs, SchedulerNode *src)
{
DecoderPriv *dp = dp_from_dec(d);
char name[16];
@@ -1385,5 +1724,5 @@ int dec_filter_add(Decoder *d, InputFilter *ifilter, InputFilterOptions *opts)
if (!opts->name)
return AVERROR(ENOMEM);
- return dp->sch_idx;
+ return dec_request_view(d, vs, src);
}
@@ -874,7 +874,8 @@ void ifile_close(InputFile **pf)
av_freep(pf);
}
-static int ist_use(InputStream *ist, int decoding_needed)
+static int ist_use(InputStream *ist, int decoding_needed,
+ const ViewSpecifier *vs, SchedulerNode *src)
{
Demuxer *d = demuxer_from_ifile(ist->file);
DemuxStream *ds = ds_from_ist(ist);
@@ -961,15 +962,26 @@ static int ist_use(InputStream *ist, int decoding_needed)
d->have_audio_dec |= is_audio;
}
+ if (decoding_needed && ist->par->codec_type == AVMEDIA_TYPE_VIDEO) {
+ ret = dec_request_view(ist->decoder, vs, src);
+ if (ret < 0)
+ return ret;
+ } else {
+ *src = decoding_needed ?
+ SCH_DEC_OUT(ds->sch_idx_dec, 0) :
+ SCH_DSTREAM(d->f.index, ds->sch_idx_stream);
+ }
+
return 0;
}
int ist_output_add(InputStream *ist, OutputStream *ost)
{
DemuxStream *ds = ds_from_ist(ist);
+ SchedulerNode src;
int ret;
- ret = ist_use(ist, ost->enc ? DECODING_FOR_OST : 0);
+ ret = ist_use(ist, ost->enc ? DECODING_FOR_OST : 0, NULL, &src);
if (ret < 0)
return ret;
@@ -983,14 +995,16 @@ int ist_output_add(InputStream *ist, OutputStream *ost)
}
int ist_filter_add(InputStream *ist, InputFilter *ifilter, int is_simple,
- InputFilterOptions *opts)
+ const ViewSpecifier *vs, InputFilterOptions *opts,
+ SchedulerNode *src)
{
Demuxer *d = demuxer_from_ifile(ist->file);
DemuxStream *ds = ds_from_ist(ist);
int64_t tsoffset = 0;
int ret;
- ret = ist_use(ist, is_simple ? DECODING_FOR_OST : DECODING_FOR_FILTER);
+ ret = ist_use(ist, is_simple ? DECODING_FOR_OST : DECODING_FOR_FILTER,
+ vs, src);
if (ret < 0)
return ret;
@@ -1074,7 +1088,7 @@ int ist_filter_add(InputStream *ist, InputFilter *ifilter, int is_simple,
opts->flags |= IFILTER_FLAG_AUTOROTATE * !!(ds->autorotate) |
IFILTER_FLAG_REINIT * !!(ds->reinit_filters);
- return ds->sch_idx_dec;
+ return 0;
}
static int choose_decoder(const OptionsContext *o, void *logctx,
@@ -676,11 +676,13 @@ static OutputFilter *ofilter_alloc(FilterGraph *fg, enum AVMediaType type)
return ofilter;
}
-static int ifilter_bind_ist(InputFilter *ifilter, InputStream *ist)
+static int ifilter_bind_ist(InputFilter *ifilter, InputStream *ist,
+ const ViewSpecifier *vs)
{
InputFilterPriv *ifp = ifp_from_ifilter(ifilter);
FilterGraphPriv *fgp = fgp_from_fg(ifilter->graph);
- int ret, dec_idx;
+ SchedulerNode src;
+ int ret;
av_assert0(!ifp->bound);
ifp->bound = 1;
@@ -698,13 +700,13 @@ static int ifilter_bind_ist(InputFilter *ifilter, InputStream *ist)
if (!ifp->opts.fallback)
return AVERROR(ENOMEM);
- dec_idx = ist_filter_add(ist, ifilter, filtergraph_is_simple(ifilter->graph),
- &ifp->opts);
- if (dec_idx < 0)
- return dec_idx;
+ ret = ist_filter_add(ist, ifilter, filtergraph_is_simple(ifilter->graph),
+ vs, &ifp->opts, &src);
+ if (ret < 0)
+ return ret;
- ret = sch_connect(fgp->sch, SCH_DEC_OUT(dec_idx, 0),
- SCH_FILTER_IN(fgp->sch_idx, ifp->index));
+ ret = sch_connect(fgp->sch,
+ src, SCH_FILTER_IN(fgp->sch_idx, ifp->index));
if (ret < 0)
return ret;
@@ -729,10 +731,12 @@ static int ifilter_bind_ist(InputFilter *ifilter, InputStream *ist)
return 0;
}
-static int ifilter_bind_dec(InputFilterPriv *ifp, Decoder *dec)
+static int ifilter_bind_dec(InputFilterPriv *ifp, Decoder *dec,
+ const ViewSpecifier *vs)
{
FilterGraphPriv *fgp = fgp_from_fg(ifp->ifilter.graph);
- int ret, dec_idx;
+ SchedulerNode src;
+ int ret;
av_assert0(!ifp->bound);
ifp->bound = 1;
@@ -745,12 +749,11 @@ static int ifilter_bind_dec(InputFilterPriv *ifp, Decoder *dec)
ifp->type_src = ifp->type;
- dec_idx = dec_filter_add(dec, &ifp->ifilter, &ifp->opts);
- if (dec_idx < 0)
- return dec_idx;
+ ret = dec_filter_add(dec, &ifp->ifilter, &ifp->opts, vs, &src);
+ if (ret < 0)
+ return ret;
- ret = sch_connect(fgp->sch, SCH_DEC_OUT(dec_idx, 0),
- SCH_FILTER_IN(fgp->sch_idx, ifp->index));
+ ret = sch_connect(fgp->sch, src, SCH_FILTER_IN(fgp->sch_idx, ifp->index));
if (ret < 0)
return ret;
@@ -1227,7 +1230,7 @@ int init_simple_filtergraph(InputStream *ist, OutputStream *ost,
ost->filter = fg->outputs[0];
- ret = ifilter_bind_ist(fg->inputs[0], ist);
+ ret = ifilter_bind_ist(fg->inputs[0], ist, opts->vs);
if (ret < 0)
return ret;
@@ -1251,28 +1254,38 @@ static int fg_complex_bind_input(FilterGraph *fg, InputFilter *ifilter)
InputFilterPriv *ifp = ifp_from_ifilter(ifilter);
InputStream *ist = NULL;
enum AVMediaType type = ifp->type;
+ ViewSpecifier vs = { .type = VIEW_SPECIFIER_TYPE_NONE };
+ const char *spec;
+ char *p;
int i, ret;
if (ifp->linklabel && !strncmp(ifp->linklabel, "dec:", 4)) {
// bind to a standalone decoder
int dec_idx;
- dec_idx = strtol(ifp->linklabel + 4, NULL, 0);
+ dec_idx = strtol(ifp->linklabel + 4, &p, 0);
if (dec_idx < 0 || dec_idx >= nb_decoders) {
av_log(fg, AV_LOG_ERROR, "Invalid decoder index %d in filtergraph description %s\n",
dec_idx, fgp->graph_desc);
return AVERROR(EINVAL);
}
- ret = ifilter_bind_dec(ifp, decoders[dec_idx]);
+ if (type == AVMEDIA_TYPE_VIDEO) {
+ spec = *p == ':' ? p + 1 : p;
+ ret = view_specifier_parse(&spec, &vs);
+ if (ret < 0)
+ return ret;
+ }
+
+ ret = ifilter_bind_dec(ifp, decoders[dec_idx], &vs);
if (ret < 0)
av_log(fg, AV_LOG_ERROR, "Error binding a decoder to filtergraph input %s\n",
ifilter->name);
return ret;
} else if (ifp->linklabel) {
+ StreamSpecifier ss;
AVFormatContext *s;
AVStream *st = NULL;
- char *p;
int file_idx;
// try finding an unbound filtergraph output with this label
@@ -1309,17 +1322,33 @@ static int fg_complex_bind_input(FilterGraph *fg, InputFilter *ifilter)
}
s = input_files[file_idx]->ctx;
+ ret = stream_specifier_parse(&ss, *p == ':' ? p + 1 : p, 1, fg);
+ if (ret < 0) {
+ av_log(fg, AV_LOG_ERROR, "Invalid stream specifier: %s\n", p);
+ return ret;
+ }
+
+ if (type == AVMEDIA_TYPE_VIDEO) {
+ spec = ss.remainder ? ss.remainder : "";
+ ret = view_specifier_parse(&spec, &vs);
+ if (ret < 0) {
+ stream_specifier_uninit(&ss);
+ return ret;
+ }
+ }
+
for (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 &&
type == AVMEDIA_TYPE_VIDEO /* sub2video hack */))
continue;
- if (check_stream_specifier(s, s->streams[i], *p == ':' ? p + 1 : p) == 1) {
+ if (stream_specifier_match(&ss, s, s->streams[i], fg)) {
st = s->streams[i];
break;
}
}
+ stream_specifier_uninit(&ss);
if (!st) {
av_log(fg, AV_LOG_FATAL, "Stream specifier '%s' in filtergraph description %s "
"matches no streams.\n", p, fgp->graph_desc);
@@ -1344,7 +1373,7 @@ static int fg_complex_bind_input(FilterGraph *fg, InputFilter *ifilter)
}
av_assert0(ist);
- ret = ifilter_bind_ist(ifilter, ist);
+ ret = ifilter_bind_ist(ifilter, ist, &vs);
if (ret < 0) {
av_log(fg, AV_LOG_ERROR,
"Error binding an input stream to complex filtergraph input %s.\n",
@@ -1025,7 +1025,7 @@ fail:
}
static int ost_add(Muxer *mux, const OptionsContext *o, enum AVMediaType type,
- InputStream *ist, OutputFilter *ofilter,
+ InputStream *ist, OutputFilter *ofilter, const ViewSpecifier *vs,
OutputStream **post)
{
AVFormatContext *oc = mux->fc;
@@ -1399,6 +1399,7 @@ static int ost_add(Muxer *mux, const OptionsContext *o, enum AVMediaType type,
.ch_layout = ost->enc_ctx->ch_layout,
.sws_opts = o->g->sws_dict,
.swr_opts = o->g->swr_opts,
+ .vs = vs,
.output_tb = enc_tb,
.trim_start_us = mux->of.start_time,
.trim_duration_us = mux->of.recording_time,
@@ -1546,7 +1547,7 @@ static int map_auto_video(Muxer *mux, const OptionsContext *o)
}
}
if (best_ist)
- return ost_add(mux, o, AVMEDIA_TYPE_VIDEO, best_ist, NULL, NULL);
+ return ost_add(mux, o, AVMEDIA_TYPE_VIDEO, best_ist, NULL, NULL, NULL);
return 0;
}
@@ -1590,7 +1591,7 @@ static int map_auto_audio(Muxer *mux, const OptionsContext *o)
}
}
if (best_ist)
- return ost_add(mux, o, AVMEDIA_TYPE_AUDIO, best_ist, NULL, NULL);
+ return ost_add(mux, o, AVMEDIA_TYPE_AUDIO, best_ist, NULL, NULL, NULL);
return 0;
}
@@ -1627,7 +1628,7 @@ static int map_auto_subtitle(Muxer *mux, const OptionsContext *o)
input_descriptor && output_descriptor &&
(!input_descriptor->props ||
!output_descriptor->props)) {
- return ost_add(mux, o, AVMEDIA_TYPE_SUBTITLE, ist, NULL, NULL);
+ return ost_add(mux, o, AVMEDIA_TYPE_SUBTITLE, ist, NULL, NULL, NULL);
}
}
@@ -1648,7 +1649,7 @@ static int map_auto_data(Muxer *mux, const OptionsContext *o)
continue;
if (ist->st->codecpar->codec_type == AVMEDIA_TYPE_DATA &&
ist->st->codecpar->codec_id == codec_id) {
- int ret = ost_add(mux, o, AVMEDIA_TYPE_DATA, ist, NULL, NULL);
+ int ret = ost_add(mux, o, AVMEDIA_TYPE_DATA, ist, NULL, NULL, NULL);
if (ret < 0)
return ret;
}
@@ -1690,10 +1691,13 @@ loop_end:
av_log(mux, AV_LOG_VERBOSE, "Creating output stream from an explicitly "
"mapped complex filtergraph %d, output [%s]\n", fg->index, map->linklabel);
- ret = ost_add(mux, o, ofilter->type, NULL, ofilter, NULL);
+ ret = ost_add(mux, o, ofilter->type, NULL, ofilter, NULL, NULL);
if (ret < 0)
return ret;
} else {
+ const ViewSpecifier *vs = map->vs.type == VIEW_SPECIFIER_TYPE_NONE ?
+ NULL : &map->vs;
+
ist = input_files[map->file_index]->streams[map->stream_index];
if (ist->user_set_discard == AVDISCARD_ALL) {
av_log(mux, AV_LOG_FATAL, "Stream #%d:%d is disabled and cannot be mapped.\n",
@@ -1724,7 +1728,14 @@ loop_end:
return 0;
}
- ret = ost_add(mux, o, ist->st->codecpar->codec_type, ist, NULL, NULL);
+ if (vs && ist->st->codecpar->codec_type != AVMEDIA_TYPE_VIDEO) {
+ av_log(mux, AV_LOG_ERROR,
+ "View specifier given for mapping a %s input stream\n",
+ av_get_media_type_string(ist->st->codecpar->codec_type));
+ return AVERROR(EINVAL);
+ }
+
+ ret = ost_add(mux, o, ist->st->codecpar->codec_type, ist, NULL, vs, NULL);
if (ret < 0)
return ret;
}
@@ -1794,7 +1805,7 @@ read_fail:
return AVERROR(ENOMEM);
}
- err = ost_add(mux, o, AVMEDIA_TYPE_ATTACHMENT, NULL, NULL, &ost);
+ err = ost_add(mux, o, AVMEDIA_TYPE_ATTACHMENT, NULL, NULL, NULL, &ost);
if (err < 0) {
av_free(attachment_filename);
av_freep(&attachment);
@@ -1849,7 +1860,7 @@ static int create_streams(Muxer *mux, const OptionsContext *o)
av_get_media_type_string(ofilter->type));
av_log(mux, AV_LOG_VERBOSE, "\n");
- ret = ost_add(mux, o, ofilter->type, NULL, ofilter, NULL);
+ ret = ost_add(mux, o, ofilter->type, NULL, ofilter, NULL, NULL);
if (ret < 0)
return ret;
}
@@ -46,6 +46,7 @@
#include "libavutil/mem.h"
#include "libavutil/opt.h"
#include "libavutil/parseutils.h"
+#include "libavutil/stereo3d.h"
HWDevice *filter_hw_device;
@@ -230,6 +231,59 @@ OPT_MATCH_PER_STREAM(int, int, OPT_TYPE_INT, i);
OPT_MATCH_PER_STREAM(int64, int64_t, OPT_TYPE_INT64, i64);
OPT_MATCH_PER_STREAM(dbl, double, OPT_TYPE_DOUBLE, dbl);
+int view_specifier_parse(const char **pspec, ViewSpecifier *vs)
+{
+ const char *spec = *pspec;
+ char *endptr;
+
+ vs->type = VIEW_SPECIFIER_TYPE_NONE;
+
+ if (!strncmp(spec, "view:", 5)) {
+ spec += 5;
+
+ if (!strncmp(spec, "all", 3)) {
+ spec += 3;
+ vs->type = VIEW_SPECIFIER_TYPE_ALL;
+ } else {
+ vs->type = VIEW_SPECIFIER_TYPE_ID;
+ vs->val = strtoul(spec, &endptr, 0);
+ if (endptr == spec) {
+ av_log(NULL, AV_LOG_ERROR, "Invalid view ID: %s\n", spec);
+ return AVERROR(EINVAL);
+ }
+ spec = endptr;
+ }
+ } else if (!strncmp(spec, "vidx:", 5)) {
+ spec += 5;
+ vs->type = VIEW_SPECIFIER_TYPE_IDX;
+ vs->val = strtoul(spec, &endptr, 0);
+ if (endptr == spec) {
+ av_log(NULL, AV_LOG_ERROR, "Invalid view index: %s\n", spec);
+ return AVERROR(EINVAL);
+ }
+ spec = endptr;
+ } else if (!strncmp(spec, "vpos:", 5)) {
+ spec += 5;
+ vs->type = VIEW_SPECIFIER_TYPE_POS;
+
+ if (!strncmp(spec, "left", 4) && !cmdutils_isalnum(spec[4])) {
+ spec += 4;
+ vs->val = AV_STEREO3D_VIEW_LEFT;
+ } else if (!strncmp(spec, "right", 5) && !cmdutils_isalnum(spec[5])) {
+ spec += 5;
+ vs->val = AV_STEREO3D_VIEW_RIGHT;
+ } else {
+ av_log(NULL, AV_LOG_ERROR, "Invalid view position: %s\n", spec);
+ return AVERROR(EINVAL);
+ }
+ } else
+ return 0;
+
+ *pspec = spec;
+
+ return 0;
+}
+
int parse_and_set_vsync(const char *arg, int *vsync_var, int file_idx, int st_idx, int is_global)
{
if (!av_strcasecmp(arg, "cfr")) *vsync_var = VSYNC_CFR;
@@ -454,6 +508,7 @@ static int opt_map(void *optctx, const char *opt, const char *arg)
goto fail;
}
} else {
+ ViewSpecifier vs;
char *endptr;
file_idx = strtol(arg, &endptr, 0);
@@ -470,12 +525,18 @@ static int opt_map(void *optctx, const char *opt, const char *arg)
goto fail;
}
- if (ss.remainder) {
- if (!strcmp(ss.remainder, "?"))
+ arg = ss.remainder ? ss.remainder : "";
+
+ ret = view_specifier_parse(&arg, &vs);
+ if (ret < 0)
+ goto fail;
+
+ if (*arg) {
+ if (!strcmp(arg, "?"))
allow_unused = 1;
else {
- av_log(NULL, AV_LOG_ERROR, "Trailing garbage after stream specifier: %s\n",
- ss.remainder);
+ av_log(NULL, AV_LOG_ERROR,
+ "Trailing garbage after stream specifier: %s\n", arg);
ret = AVERROR(EINVAL);
goto fail;
}
@@ -511,6 +572,7 @@ static int opt_map(void *optctx, const char *opt, const char *arg)
m->file_index = file_idx;
m->stream_index = i;
+ m->vs = vs;
}
}