Commit a1e05b04 authored by Anton Khirnov's avatar Anton Khirnov

lavfi: add trim and atrim filters.

parent b4729382
......@@ -13,6 +13,7 @@ version 10:
- new interlace filter
- JPEG 2000 decoder
- new asetpts filter (same as setpts, but for audio)
- new trim and atrim filters
version 9:
......
......@@ -345,6 +345,70 @@ with a negative pts due to encoder delay.
@end table
@section atrim
Trim the input so that the output contains one continuous subpart of the input.
This filter accepts the following options:
@table @option
@item start
Timestamp (in seconds) of the start of the kept section. I.e. the audio sample
with the timestamp @var{start} will be the first sample in the output.
@item end
Timestamp (in seconds) of the first audio sample that will be dropped. I.e. the
audio sample immediately preceding the one with the timestamp @var{end} will be
the last sample in the output.
@item start_pts
Same as @var{start}, except this option sets the start timestamp in samples
instead of seconds.
@item end_pts
Same as @var{end}, except this option sets the end timestamp in samples instead
of seconds.
@item duration
Maximum duration of the output in seconds.
@item start_sample
Number of the first sample that should be passed to output.
@item end_sample
Number of the first sample that should be dropped.
@end table
Note that the first two sets of the start/end options and the @option{duration}
option look at the frame timestamp, while the _sample options simply count the
samples that pass through the filter. So start/end_pts and start/end_sample will
give different results when the timestamps are wrong, inexact or do not start at
zero. Also note that this filter does not modify the timestamps. If you wish
that the output timestamps start at zero, insert the asetpts filter after the
atrim filter.
If multiple start or end options are set, this filter tries to be greedy and
keep all samples that match at least one of the specified constraints. To keep
only the part that matches all the constraints at once, chain multiple atrim
filters.
The defaults are such that all the input is kept. So it is possible to set e.g.
just the end values to keep everything before the specified time.
Examples:
@itemize
@item
drop everything except the second minute of input
@example
avconv -i INPUT -af atrim=60:120
@end example
@item
keep only the first 1000 samples
@example
avconv -i INPUT -af atrim=end_sample=1000
@end example
@end itemize
@section channelsplit
Split each channel in input audio stream into a separate output stream.
......@@ -2251,6 +2315,67 @@ l.r l.L
@end example
@end table
@section trim
Trim the input so that the output contains one continuous subpart of the input.
This filter accepts the following options:
@table @option
@item start
Timestamp (in seconds) of the start of the kept section. I.e. the frame with the
timestamp @var{start} will be the first frame in the output.
@item end
Timestamp (in seconds) of the first frame that will be dropped. I.e. the frame
immediately preceding the one with the timestamp @var{end} will be the last
frame in the output.
@item start_pts
Same as @var{start}, except this option sets the start timestamp in timebase
units instead of seconds.
@item end_pts
Same as @var{end}, except this option sets the end timestamp in timebase units
instead of seconds.
@item duration
Maximum duration of the output in seconds.
@item start_frame
Number of the first frame that should be passed to output.
@item end_frame
Number of the first frame that should be dropped.
@end table
Note that the first two sets of the start/end options and the @option{duration}
option look at the frame timestamp, while the _frame variants simply count the
frames that pass through the filter. Also note that this filter does not modify
the timestamps. If you wish that the output timestamps start at zero, insert a
setpts filter after the trim filter.
If multiple start or end options are set, this filter tries to be greedy and
keep all the frames that match at least one of the specified constraints. To keep
only the part that matches all the constraints at once, chain multiple trim
filters.
The defaults are such that all the input is kept. So it is possible to set e.g.
just the end values to keep everything before the specified time.
Examples:
@itemize
@item
drop everything except the second minute of input
@example
avconv -i INPUT -vf trim=60:120
@end example
@item
keep only the first second
@example
avconv -i INPUT -vf trim=duration=1
@end example
@end itemize
@section unsharp
Sharpen or blur the input video.
......
......@@ -31,6 +31,7 @@ OBJS-$(CONFIG_ASETPTS_FILTER) += setpts.o
OBJS-$(CONFIG_ASHOWINFO_FILTER) += af_ashowinfo.o
OBJS-$(CONFIG_ASPLIT_FILTER) += split.o
OBJS-$(CONFIG_ASYNCTS_FILTER) += af_asyncts.o
OBJS-$(CONFIG_ATRIM_FILTER) += trim.o
OBJS-$(CONFIG_CHANNELMAP_FILTER) += af_channelmap.o
OBJS-$(CONFIG_CHANNELSPLIT_FILTER) += af_channelsplit.o
OBJS-$(CONFIG_JOIN_FILTER) += af_join.o
......@@ -77,6 +78,7 @@ OBJS-$(CONFIG_SETTB_FILTER) += vf_settb.o
OBJS-$(CONFIG_SHOWINFO_FILTER) += vf_showinfo.o
OBJS-$(CONFIG_SPLIT_FILTER) += split.o
OBJS-$(CONFIG_TRANSPOSE_FILTER) += vf_transpose.o
OBJS-$(CONFIG_TRIM_FILTER) += trim.o
OBJS-$(CONFIG_UNSHARP_FILTER) += vf_unsharp.o
OBJS-$(CONFIG_VFLIP_FILTER) += vf_vflip.o
OBJS-$(CONFIG_YADIF_FILTER) += vf_yadif.o
......
......@@ -51,6 +51,7 @@ void avfilter_register_all(void)
REGISTER_FILTER(ASHOWINFO, ashowinfo, af);
REGISTER_FILTER(ASPLIT, asplit, af);
REGISTER_FILTER(ASYNCTS, asyncts, af);
REGISTER_FILTER(ATRIM, atrim, af);
REGISTER_FILTER(CHANNELMAP, channelmap, af);
REGISTER_FILTER(CHANNELSPLIT, channelsplit, af);
REGISTER_FILTER(JOIN, join, af);
......@@ -97,6 +98,7 @@ void avfilter_register_all(void)
REGISTER_FILTER(SHOWINFO, showinfo, vf);
REGISTER_FILTER(SPLIT, split, vf);
REGISTER_FILTER(TRANSPOSE, transpose, vf);
REGISTER_FILTER(TRIM, trim, vf);
REGISTER_FILTER(UNSHARP, unsharp, vf);
REGISTER_FILTER(VFLIP, vflip, vf);
REGISTER_FILTER(YADIF, yadif, vf);
......
/*
* This file is part of Libav.
*
* Libav is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* Libav is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with Libav; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <float.h>
#include <math.h>
#include "config.h"
#include "libavutil/avassert.h"
#include "libavutil/channel_layout.h"
#include "libavutil/common.h"
#include "libavutil/log.h"
#include "libavutil/mathematics.h"
#include "libavutil/opt.h"
#include "libavutil/samplefmt.h"
#include "audio.h"
#include "avfilter.h"
#include "internal.h"
typedef struct TrimContext {
const AVClass *class;
/*
* AVOptions
*/
double duration;
double start_time, end_time;
int64_t start_frame, end_frame;
/*
* in the link timebase for video,
* in 1/samplerate for audio
*/
int64_t start_pts, end_pts;
int64_t start_sample, end_sample;
/*
* number of video frames that arrived on this filter so far
*/
int64_t nb_frames;
/*
* number of audio samples that arrived on this filter so far
*/
int64_t nb_samples;
/*
* timestamp of the first frame in the output, in the timebase units
*/
int64_t first_pts;
/*
* duration in the timebase units
*/
int64_t duration_tb;
int64_t next_pts;
int eof;
int got_output;
} TrimContext;
static int init(AVFilterContext *ctx)
{
TrimContext *s = ctx->priv;
s->first_pts = AV_NOPTS_VALUE;
return 0;
}
static int config_input(AVFilterLink *inlink)
{
AVFilterContext *ctx = inlink->dst;
TrimContext *s = ctx->priv;
AVRational tb = (inlink->type == AVMEDIA_TYPE_VIDEO) ?
inlink->time_base : (AVRational){ 1, inlink->sample_rate };
if (s->start_time != DBL_MAX) {
int64_t start_pts = lrintf(s->start_time / av_q2d(tb));
if (s->start_pts == AV_NOPTS_VALUE || start_pts < s->start_pts)
s->start_pts = start_pts;
}
if (s->end_time != DBL_MAX) {
int64_t end_pts = lrintf(s->end_time / av_q2d(tb));
if (s->end_pts == AV_NOPTS_VALUE || end_pts > s->end_pts)
s->end_pts = end_pts;
}
if (s->duration)
s->duration_tb = lrintf(s->duration / av_q2d(tb));
return 0;
}
static int request_frame(AVFilterLink *outlink)
{
AVFilterContext *ctx = outlink->src;
TrimContext *s = ctx->priv;
int ret;
s->got_output = 0;
while (!s->got_output) {
if (s->eof)
return AVERROR_EOF;
ret = ff_request_frame(ctx->inputs[0]);
if (ret < 0)
return ret;
}
return 0;
}
#define OFFSET(x) offsetof(TrimContext, x)
#define COMMON_OPTS \
{ "start", "Timestamp in seconds of the first frame that " \
"should be passed", OFFSET(start_time), AV_OPT_TYPE_DOUBLE, { .dbl = DBL_MAX }, -DBL_MAX, DBL_MAX, FLAGS }, \
{ "end", "Timestamp in seconds of the first frame that " \
"should be dropped again", OFFSET(end_time), AV_OPT_TYPE_DOUBLE, { .dbl = DBL_MAX }, -DBL_MAX, DBL_MAX, FLAGS }, \
{ "start_pts", "Timestamp of the first frame that should be " \
" passed", OFFSET(start_pts), AV_OPT_TYPE_INT64, { .i64 = AV_NOPTS_VALUE }, INT64_MIN, INT64_MAX, FLAGS }, \
{ "end_pts", "Timestamp of the first frame that should be " \
"dropped again", OFFSET(end_pts), AV_OPT_TYPE_INT64, { .i64 = AV_NOPTS_VALUE }, INT64_MIN, INT64_MAX, FLAGS }, \
{ "duration", "Maximum duration of the output in seconds", OFFSET(duration), AV_OPT_TYPE_DOUBLE, { .dbl = 0 }, 0, DBL_MAX, FLAGS },
#if CONFIG_TRIM_FILTER
static int trim_filter_frame(AVFilterLink *inlink, AVFrame *frame)
{
AVFilterContext *ctx = inlink->dst;
TrimContext *s = ctx->priv;
int drop;
/* drop everything if EOF has already been returned */
if (s->eof) {
av_frame_free(&frame);
return 0;
}
if (s->start_frame >= 0 || s->start_pts != AV_NOPTS_VALUE) {
drop = 1;
if (s->start_frame >= 0 && s->nb_frames >= s->start_frame)
drop = 0;
if (s->start_pts != AV_NOPTS_VALUE && frame->pts != AV_NOPTS_VALUE &&
frame->pts >= s->start_pts)
drop = 0;
if (drop)
goto drop;
}
if (s->first_pts == AV_NOPTS_VALUE && frame->pts != AV_NOPTS_VALUE)
s->first_pts = frame->pts;
if (s->end_frame != INT64_MAX || s->end_pts != AV_NOPTS_VALUE || s->duration_tb) {
drop = 1;
if (s->end_frame != INT64_MAX && s->nb_frames < s->end_frame)
drop = 0;
if (s->end_pts != AV_NOPTS_VALUE && frame->pts != AV_NOPTS_VALUE &&
frame->pts < s->end_pts)
drop = 0;
if (s->duration_tb && frame->pts != AV_NOPTS_VALUE &&
frame->pts - s->first_pts < s->duration_tb)
drop = 0;
if (drop) {
s->eof = 1;
goto drop;
}
}
s->nb_frames++;
s->got_output = 1;
return ff_filter_frame(ctx->outputs[0], frame);
drop:
s->nb_frames++;
av_frame_free(&frame);
return 0;
}
#define FLAGS AV_OPT_FLAG_VIDEO_PARAM
static const AVOption trim_options[] = {
COMMON_OPTS
{ "start_frame", "Number of the first frame that should be passed "
"to the output", OFFSET(start_frame), AV_OPT_TYPE_INT64, { .i64 = -1 }, -1, INT64_MAX, FLAGS },
{ "end_frame", "Number of the first frame that should be dropped "
"again", OFFSET(end_frame), AV_OPT_TYPE_INT64, { .i64 = INT64_MAX }, 0, INT64_MAX, FLAGS },
{ NULL },
};
#undef FLAGS
static const AVClass trim_class = {
.class_name = "trim",
.item_name = av_default_item_name,
.option = trim_options,
.version = LIBAVUTIL_VERSION_INT,
};
static const AVFilterPad trim_inputs[] = {
{
.name = "default",
.type = AVMEDIA_TYPE_VIDEO,
.filter_frame = trim_filter_frame,
.config_props = config_input,
},
{ NULL }
};
static const AVFilterPad trim_outputs[] = {
{
.name = "default",
.type = AVMEDIA_TYPE_VIDEO,
.request_frame = request_frame,
},
{ NULL }
};
AVFilter avfilter_vf_trim = {
.name = "trim",
.description = NULL_IF_CONFIG_SMALL("Pick one continuous section from the input, drop the rest."),
.init = init,
.priv_size = sizeof(TrimContext),
.priv_class = &trim_class,
.inputs = trim_inputs,
.outputs = trim_outputs,
};
#endif // CONFIG_TRIM_FILTER
#if CONFIG_ATRIM_FILTER
static int atrim_filter_frame(AVFilterLink *inlink, AVFrame *frame)
{
AVFilterContext *ctx = inlink->dst;
TrimContext *s = ctx->priv;
int64_t start_sample, end_sample = frame->nb_samples;
int64_t pts;
int drop;
/* drop everything if EOF has already been returned */
if (s->eof) {
av_frame_free(&frame);
return 0;
}
if (frame->pts != AV_NOPTS_VALUE)
pts = av_rescale_q(frame->pts, inlink->time_base,
(AVRational){ 1, inlink->sample_rate });
else
pts = s->next_pts;
s->next_pts = pts + frame->nb_samples;
/* check if at least a part of the frame is after the start time */
if (s->start_sample < 0 && s->start_pts == AV_NOPTS_VALUE) {
start_sample = 0;
} else {
drop = 1;
start_sample = frame->nb_samples;
if (s->start_sample >= 0 &&
s->nb_samples + frame->nb_samples > s->start_sample) {
drop = 0;
start_sample = FFMIN(start_sample, s->start_sample - s->nb_samples);
}
if (s->start_pts != AV_NOPTS_VALUE && pts != AV_NOPTS_VALUE &&
pts + frame->nb_samples > s->start_pts) {
drop = 0;
start_sample = FFMIN(start_sample, s->start_pts - pts);
}
if (drop)
goto drop;
}
if (s->first_pts == AV_NOPTS_VALUE)
s->first_pts = pts + start_sample;
/* check if at least a part of the frame is before the end time */
if (s->end_sample == INT64_MAX && s->end_pts == AV_NOPTS_VALUE && !s->duration_tb) {
end_sample = frame->nb_samples;
} else {
drop = 1;
end_sample = 0;
if (s->end_sample != INT64_MAX &&
s->nb_samples < s->end_sample) {
drop = 0;
end_sample = FFMAX(end_sample, s->end_sample - s->nb_samples);
}
if (s->end_pts != AV_NOPTS_VALUE && pts != AV_NOPTS_VALUE &&
pts < s->end_pts) {
drop = 0;
end_sample = FFMAX(end_sample, s->end_pts - pts);
}
if (s->duration_tb && pts - s->first_pts < s->duration_tb) {
drop = 0;
end_sample = FFMAX(end_sample, s->first_pts + s->duration_tb - pts);
}
if (drop) {
s->eof = 1;
goto drop;
}
}
s->nb_samples += frame->nb_samples;
start_sample = FFMAX(0, start_sample);
end_sample = FFMIN(frame->nb_samples, end_sample);
av_assert0(start_sample < end_sample);
if (start_sample) {
AVFrame *out = ff_get_audio_buffer(ctx->outputs[0], end_sample - start_sample);
if (!out) {
av_frame_free(&frame);
return AVERROR(ENOMEM);
}
av_frame_copy_props(out, frame);
av_samples_copy(out->extended_data, frame->extended_data, 0, start_sample,
out->nb_samples, av_get_channel_layout_nb_channels(frame->channel_layout),
frame->format);
if (out->pts != AV_NOPTS_VALUE)
out->pts += av_rescale_q(start_sample, (AVRational){ 1, out->sample_rate },
inlink->time_base);
av_frame_free(&frame);
frame = out;
} else
frame->nb_samples = end_sample;
s->got_output = 1;
return ff_filter_frame(ctx->outputs[0], frame);
drop:
s->nb_samples += frame->nb_samples;
av_frame_free(&frame);
return 0;
}
#define FLAGS AV_OPT_FLAG_AUDIO_PARAM
static const AVOption atrim_options[] = {
COMMON_OPTS
{ "start_sample", "Number of the first audio sample that should be "
"passed to the output", OFFSET(start_sample), AV_OPT_TYPE_INT64, { .i64 = -1 }, -1, INT64_MAX, FLAGS },
{ "end_sample", "Number of the first audio sample that should be "
"dropped again", OFFSET(end_sample), AV_OPT_TYPE_INT64, { .i64 = INT64_MAX }, 0, INT64_MAX, FLAGS },
{ NULL },
};
#undef FLAGS
static const AVClass atrim_class = {
.class_name = "atrim",
.item_name = av_default_item_name,
.option = atrim_options,
.version = LIBAVUTIL_VERSION_INT,
};
static const AVFilterPad atrim_inputs[] = {
{
.name = "default",
.type = AVMEDIA_TYPE_AUDIO,
.filter_frame = atrim_filter_frame,
.config_props = config_input,
},
{ NULL }
};
static const AVFilterPad atrim_outputs[] = {
{
.name = "default",
.type = AVMEDIA_TYPE_AUDIO,
.request_frame = request_frame,
},
{ NULL }
};
AVFilter avfilter_af_atrim = {
.name = "atrim",
.description = NULL_IF_CONFIG_SMALL("Pick one continuous section from the input, drop the rest."),
.init = init,
.priv_size = sizeof(TrimContext),
.priv_class = &atrim_class,
.inputs = atrim_inputs,
.outputs = atrim_outputs,
};
#endif // CONFIG_ATRIM_FILTER
......@@ -25,6 +25,22 @@ fate-filter-asyncts: CMD = pcm -analyzeduration 10000000 -i $(SRC) -af asyncts
fate-filter-asyncts: CMP = oneoff
fate-filter-asyncts: REF = $(SAMPLES)/nellymoser/nellymoser-discont.pcm
FATE_ATRIM += fate-filter-atrim-duration
fate-filter-atrim-duration: CMD = framecrc -i $(SRC) -af atrim=start=0.1:duration=0.01
FATE_ATRIM += fate-filter-atrim-mixed
fate-filter-atrim-mixed: CMD = framecrc -i $(SRC) -af atrim=start=0.05:start_sample=1025:end=0.1:end_sample=4411
FATE_ATRIM += fate-filter-atrim-samples
fate-filter-atrim-samples: CMD = framecrc -i $(SRC) -af atrim=start_sample=26:end_sample=80
FATE_ATRIM += fate-filter-atrim-time
fate-filter-atrim-time: CMD = framecrc -i $(SRC) -af atrim=0.1:0.2
$(FATE_ATRIM): tests/data/asynth-44100-2.wav
$(FATE_ATRIM): SRC = $(TARGET_PATH)/tests/data/asynth-44100-2.wav
FATE_FILTER-$(call FILTERDEMDECENCMUX, ATRIM, WAV, PCM_S16LE, PCM_S16LE, WAV) += $(FATE_ATRIM)
FATE_AFILTER-$(call FILTERDEMDECENCMUX, CHANNELMAP, WAV, PCM_S16LE, PCM_S16LE, WAV) += fate-filter-channelmap
fate-filter-channelmap: SRC = $(TARGET_PATH)/tests/data/asynth-44100-6.wav
fate-filter-channelmap: tests/data/asynth-44100-6.wav
......
......@@ -42,6 +42,20 @@ fate-filter-setpts: CMD = framecrc -c:v pgmyuv -i $(SRC) -filter_script $(SRC_PA
FATE_FILTER_VSYNTH-$(CONFIG_TRANSPOSE_FILTER) += fate-filter-transpose
fate-filter-transpose: CMD = framecrc -c:v pgmyuv -i $(SRC) -vf transpose
FATE_TRIM += fate-filter-trim-duration
fate-filter-trim-duration: CMD = framecrc -i $(SRC) -vf trim=start=0.4:duration=0.05
FATE_TRIM += fate-filter-trim-frame
fate-filter-trim-frame: CMD = framecrc -i $(SRC) -vf trim=start_frame=3:end_frame=10
FATE_TRIM += fate-filter-trim-mixed
fate-filter-trim-mixed: CMD = framecrc -i $(SRC) -vf trim=start=0.2:end=0.4:start_frame=1:end_frame=3
FATE_TRIM += fate-filter-trim-time
fate-filter-trim-time: CMD = framecrc -i $(SRC) -vf trim=0:0.1
FATE_FILTER_VSYNTH-$(CONFIG_TRIM_FILTER) += $(FATE_TRIM)
FATE_FILTER_VSYNTH-$(CONFIG_UNSHARP_FILTER) += fate-filter-unsharp
fate-filter-unsharp: CMD = framecrc -c:v pgmyuv -i $(SRC) -vf unsharp
......
#tb 0: 1/44100
0, 4410, 4410, 441, 1764, 0x61e374f7
#tb 0: 1/44100
0, 1025, 1025, 1023, 4092, 0x78560a4c
0, 2048, 2048, 1024, 4096, 0xc477fa99
0, 3072, 3072, 1024, 4096, 0x3bc0f14f
0, 4096, 4096, 315, 1260, 0xe4b26b50
#tb 0: 1/44100
0, 26, 26, 54, 216, 0x6b376c6c
#tb 0: 1/44100
0, 4410, 4410, 710, 2840, 0x658982a3
0, 5120, 5120, 1024, 4096, 0xfd6a0070
0, 6144, 6144, 1024, 4096, 0x0b01f4cf
0, 7168, 7168, 1024, 4096, 0x6716fd93
0, 8192, 8192, 628, 2512, 0xda5ddff8
#tb 0: 1/25
0, 10, 10, 1, 152064, 0xb45c4760
#tb 0: 1/25
0, 3, 3, 1, 152064, 0xceb080b0
0, 4, 4, 1, 152064, 0x473db652
0, 5, 5, 1, 152064, 0x287da8e6
0, 6, 6, 1, 152064, 0x68b47c23
0, 7, 7, 1, 152064, 0xe9028bac
0, 8, 8, 1, 152064, 0x28ff8026
0, 9, 9, 1, 152064, 0x2d7c3915
#tb 0: 1/25
0, 1, 1, 1, 152064, 0x7f5f6551
0, 2, 2, 1, 152064, 0xc566f64a
0, 3, 3, 1, 152064, 0xceb080b0
0, 4, 4, 1, 152064, 0x473db652
0, 5, 5, 1, 152064, 0x287da8e6
0, 6, 6, 1, 152064, 0x68b47c23
0, 7, 7, 1, 152064, 0xe9028bac
0, 8, 8, 1, 152064, 0x28ff8026
0, 9, 9, 1, 152064, 0x2d7c3915
#tb 0: 1/25
0, 0, 0, 1, 152064, 0x6e4f89ef
0, 1, 1, 1, 152064, 0x7f5f6551
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment