yading@10: /* yading@10: * Copyright (c) 2012 Nicolas George yading@10: * yading@10: * This file is part of FFmpeg. yading@10: * yading@10: * FFmpeg is free software; you can redistribute it and/or yading@10: * modify it under the terms of the GNU Lesser General Public yading@10: * License as published by the Free Software Foundation; either yading@10: * version 2.1 of the License, or (at your option) any later version. yading@10: * yading@10: * FFmpeg is distributed in the hope that it will be useful, yading@10: * but WITHOUT ANY WARRANTY; without even the implied warranty of yading@10: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. yading@10: * See the GNU Lesser General Public License for more details. yading@10: * yading@10: * You should have received a copy of the GNU Lesser General Public License yading@10: * along with FFmpeg; if not, write to the Free Software Foundation, Inc., yading@10: * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA yading@10: */ yading@10: yading@10: /** yading@10: * @file yading@10: * concat audio-video filter yading@10: */ yading@10: yading@10: #include "libavutil/avassert.h" yading@10: #include "libavutil/avstring.h" yading@10: #include "libavutil/channel_layout.h" yading@10: #include "libavutil/opt.h" yading@10: #include "avfilter.h" yading@10: #define FF_BUFQUEUE_SIZE 256 yading@10: #include "bufferqueue.h" yading@10: #include "internal.h" yading@10: #include "video.h" yading@10: #include "audio.h" yading@10: yading@10: #define TYPE_ALL 2 yading@10: yading@10: typedef struct { yading@10: const AVClass *class; yading@10: unsigned nb_streams[TYPE_ALL]; /**< number of out streams of each type */ yading@10: unsigned nb_segments; yading@10: unsigned cur_idx; /**< index of the first input of current segment */ yading@10: int64_t delta_ts; /**< timestamp to add to produce output timestamps */ yading@10: unsigned nb_in_active; /**< number of active inputs in current segment */ yading@10: unsigned unsafe; yading@10: struct concat_in { yading@10: int64_t pts; yading@10: int64_t nb_frames; yading@10: unsigned eof; yading@10: struct FFBufQueue queue; yading@10: } *in; yading@10: } ConcatContext; yading@10: yading@10: #define OFFSET(x) offsetof(ConcatContext, x) yading@10: #define A AV_OPT_FLAG_AUDIO_PARAM yading@10: #define F AV_OPT_FLAG_FILTERING_PARAM yading@10: #define V AV_OPT_FLAG_VIDEO_PARAM yading@10: yading@10: static const AVOption concat_options[] = { yading@10: { "n", "specify the number of segments", OFFSET(nb_segments), yading@10: AV_OPT_TYPE_INT, { .i64 = 2 }, 2, INT_MAX, V|A|F}, yading@10: { "v", "specify the number of video streams", yading@10: OFFSET(nb_streams[AVMEDIA_TYPE_VIDEO]), yading@10: AV_OPT_TYPE_INT, { .i64 = 1 }, 0, INT_MAX, V|F }, yading@10: { "a", "specify the number of audio streams", yading@10: OFFSET(nb_streams[AVMEDIA_TYPE_AUDIO]), yading@10: AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, A|F}, yading@10: { "unsafe", "enable unsafe mode", yading@10: OFFSET(unsafe), yading@10: AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, A|A|F}, yading@10: { 0 } yading@10: }; yading@10: yading@10: AVFILTER_DEFINE_CLASS(concat); yading@10: yading@10: static int query_formats(AVFilterContext *ctx) yading@10: { yading@10: ConcatContext *cat = ctx->priv; yading@10: unsigned type, nb_str, idx0 = 0, idx, str, seg; yading@10: AVFilterFormats *formats, *rates = NULL; yading@10: AVFilterChannelLayouts *layouts = NULL; yading@10: yading@10: for (type = 0; type < TYPE_ALL; type++) { yading@10: nb_str = cat->nb_streams[type]; yading@10: for (str = 0; str < nb_str; str++) { yading@10: idx = idx0; yading@10: yading@10: /* Set the output formats */ yading@10: formats = ff_all_formats(type); yading@10: if (!formats) yading@10: return AVERROR(ENOMEM); yading@10: ff_formats_ref(formats, &ctx->outputs[idx]->in_formats); yading@10: if (type == AVMEDIA_TYPE_AUDIO) { yading@10: rates = ff_all_samplerates(); yading@10: if (!rates) yading@10: return AVERROR(ENOMEM); yading@10: ff_formats_ref(rates, &ctx->outputs[idx]->in_samplerates); yading@10: layouts = ff_all_channel_layouts(); yading@10: if (!layouts) yading@10: return AVERROR(ENOMEM); yading@10: ff_channel_layouts_ref(layouts, &ctx->outputs[idx]->in_channel_layouts); yading@10: } yading@10: yading@10: /* Set the same formats for each corresponding input */ yading@10: for (seg = 0; seg < cat->nb_segments; seg++) { yading@10: ff_formats_ref(formats, &ctx->inputs[idx]->out_formats); yading@10: if (type == AVMEDIA_TYPE_AUDIO) { yading@10: ff_formats_ref(rates, &ctx->inputs[idx]->out_samplerates); yading@10: ff_channel_layouts_ref(layouts, &ctx->inputs[idx]->out_channel_layouts); yading@10: } yading@10: idx += ctx->nb_outputs; yading@10: } yading@10: yading@10: idx0++; yading@10: } yading@10: } yading@10: return 0; yading@10: } yading@10: yading@10: static int config_output(AVFilterLink *outlink) yading@10: { yading@10: AVFilterContext *ctx = outlink->src; yading@10: ConcatContext *cat = ctx->priv; yading@10: unsigned out_no = FF_OUTLINK_IDX(outlink); yading@10: unsigned in_no = out_no, seg; yading@10: AVFilterLink *inlink = ctx->inputs[in_no]; yading@10: yading@10: /* enhancement: find a common one */ yading@10: outlink->time_base = AV_TIME_BASE_Q; yading@10: outlink->w = inlink->w; yading@10: outlink->h = inlink->h; yading@10: outlink->sample_aspect_ratio = inlink->sample_aspect_ratio; yading@10: outlink->format = inlink->format; yading@10: for (seg = 1; seg < cat->nb_segments; seg++) { yading@10: inlink = ctx->inputs[in_no += ctx->nb_outputs]; yading@10: /* possible enhancement: unsafe mode, do not check */ yading@10: if (outlink->w != inlink->w || yading@10: outlink->h != inlink->h || yading@10: outlink->sample_aspect_ratio.num != inlink->sample_aspect_ratio.num || yading@10: outlink->sample_aspect_ratio.den != inlink->sample_aspect_ratio.den) { yading@10: av_log(ctx, AV_LOG_ERROR, "Input link %s parameters " yading@10: "(size %dx%d, SAR %d:%d) do not match the corresponding " yading@10: "output link %s parameters (%dx%d, SAR %d:%d)\n", yading@10: ctx->input_pads[in_no].name, inlink->w, inlink->h, yading@10: inlink->sample_aspect_ratio.num, yading@10: inlink->sample_aspect_ratio.den, yading@10: ctx->input_pads[out_no].name, outlink->w, outlink->h, yading@10: outlink->sample_aspect_ratio.num, yading@10: outlink->sample_aspect_ratio.den); yading@10: if (!cat->unsafe) yading@10: return AVERROR(EINVAL); yading@10: } yading@10: } yading@10: yading@10: return 0; yading@10: } yading@10: yading@10: static int push_frame(AVFilterContext *ctx, unsigned in_no, AVFrame *buf) yading@10: { yading@10: ConcatContext *cat = ctx->priv; yading@10: unsigned out_no = in_no % ctx->nb_outputs; yading@10: AVFilterLink * inlink = ctx-> inputs[ in_no]; yading@10: AVFilterLink *outlink = ctx->outputs[out_no]; yading@10: struct concat_in *in = &cat->in[in_no]; yading@10: yading@10: buf->pts = av_rescale_q(buf->pts, inlink->time_base, outlink->time_base); yading@10: in->pts = buf->pts; yading@10: in->nb_frames++; yading@10: /* add duration to input PTS */ yading@10: if (inlink->sample_rate) yading@10: /* use number of audio samples */ yading@10: in->pts += av_rescale_q(buf->nb_samples, yading@10: (AVRational){ 1, inlink->sample_rate }, yading@10: outlink->time_base); yading@10: else if (in->nb_frames >= 2) yading@10: /* use mean duration */ yading@10: in->pts = av_rescale(in->pts, in->nb_frames, in->nb_frames - 1); yading@10: yading@10: buf->pts += cat->delta_ts; yading@10: return ff_filter_frame(outlink, buf); yading@10: } yading@10: yading@10: static int process_frame(AVFilterLink *inlink, AVFrame *buf) yading@10: { yading@10: AVFilterContext *ctx = inlink->dst; yading@10: ConcatContext *cat = ctx->priv; yading@10: unsigned in_no = FF_INLINK_IDX(inlink); yading@10: yading@10: if (in_no < cat->cur_idx) { yading@10: av_log(ctx, AV_LOG_ERROR, "Frame after EOF on input %s\n", yading@10: ctx->input_pads[in_no].name); yading@10: av_frame_free(&buf); yading@10: } else if (in_no >= cat->cur_idx + ctx->nb_outputs) { yading@10: ff_bufqueue_add(ctx, &cat->in[in_no].queue, buf); yading@10: } else { yading@10: return push_frame(ctx, in_no, buf); yading@10: } yading@10: return 0; yading@10: } yading@10: yading@10: static AVFrame *get_video_buffer(AVFilterLink *inlink, int w, int h) yading@10: { yading@10: AVFilterContext *ctx = inlink->dst; yading@10: unsigned in_no = FF_INLINK_IDX(inlink); yading@10: AVFilterLink *outlink = ctx->outputs[in_no % ctx->nb_outputs]; yading@10: yading@10: return ff_get_video_buffer(outlink, w, h); yading@10: } yading@10: yading@10: static AVFrame *get_audio_buffer(AVFilterLink *inlink, int nb_samples) yading@10: { yading@10: AVFilterContext *ctx = inlink->dst; yading@10: unsigned in_no = FF_INLINK_IDX(inlink); yading@10: AVFilterLink *outlink = ctx->outputs[in_no % ctx->nb_outputs]; yading@10: yading@10: return ff_get_audio_buffer(outlink, nb_samples); yading@10: } yading@10: yading@10: static int filter_frame(AVFilterLink *inlink, AVFrame *buf) yading@10: { yading@10: return process_frame(inlink, buf); yading@10: } yading@10: yading@10: static void close_input(AVFilterContext *ctx, unsigned in_no) yading@10: { yading@10: ConcatContext *cat = ctx->priv; yading@10: yading@10: cat->in[in_no].eof = 1; yading@10: cat->nb_in_active--; yading@10: av_log(ctx, AV_LOG_VERBOSE, "EOF on %s, %d streams left in segment.\n", yading@10: ctx->input_pads[in_no].name, cat->nb_in_active); yading@10: } yading@10: yading@10: static void find_next_delta_ts(AVFilterContext *ctx, int64_t *seg_delta) yading@10: { yading@10: ConcatContext *cat = ctx->priv; yading@10: unsigned i = cat->cur_idx; yading@10: unsigned imax = i + ctx->nb_outputs; yading@10: int64_t pts; yading@10: yading@10: pts = cat->in[i++].pts; yading@10: for (; i < imax; i++) yading@10: pts = FFMAX(pts, cat->in[i].pts); yading@10: cat->delta_ts += pts; yading@10: *seg_delta = pts; yading@10: } yading@10: yading@10: static int send_silence(AVFilterContext *ctx, unsigned in_no, unsigned out_no, yading@10: int64_t seg_delta) yading@10: { yading@10: ConcatContext *cat = ctx->priv; yading@10: AVFilterLink *outlink = ctx->outputs[out_no]; yading@10: int64_t base_pts = cat->in[in_no].pts + cat->delta_ts - seg_delta; yading@10: int64_t nb_samples, sent = 0; yading@10: int frame_nb_samples, ret; yading@10: AVRational rate_tb = { 1, ctx->inputs[in_no]->sample_rate }; yading@10: AVFrame *buf; yading@10: int nb_channels = av_get_channel_layout_nb_channels(outlink->channel_layout); yading@10: yading@10: if (!rate_tb.den) yading@10: return AVERROR_BUG; yading@10: nb_samples = av_rescale_q(seg_delta - cat->in[in_no].pts, yading@10: outlink->time_base, rate_tb); yading@10: frame_nb_samples = FFMAX(9600, rate_tb.den / 5); /* arbitrary */ yading@10: while (nb_samples) { yading@10: frame_nb_samples = FFMIN(frame_nb_samples, nb_samples); yading@10: buf = ff_get_audio_buffer(outlink, frame_nb_samples); yading@10: if (!buf) yading@10: return AVERROR(ENOMEM); yading@10: av_samples_set_silence(buf->extended_data, 0, frame_nb_samples, yading@10: nb_channels, outlink->format); yading@10: buf->pts = base_pts + av_rescale_q(sent, rate_tb, outlink->time_base); yading@10: ret = ff_filter_frame(outlink, buf); yading@10: if (ret < 0) yading@10: return ret; yading@10: sent += frame_nb_samples; yading@10: nb_samples -= frame_nb_samples; yading@10: } yading@10: return 0; yading@10: } yading@10: yading@10: static int flush_segment(AVFilterContext *ctx) yading@10: { yading@10: int ret; yading@10: ConcatContext *cat = ctx->priv; yading@10: unsigned str, str_max; yading@10: int64_t seg_delta; yading@10: yading@10: find_next_delta_ts(ctx, &seg_delta); yading@10: cat->cur_idx += ctx->nb_outputs; yading@10: cat->nb_in_active = ctx->nb_outputs; yading@10: av_log(ctx, AV_LOG_VERBOSE, "Segment finished at pts=%"PRId64"\n", yading@10: cat->delta_ts); yading@10: yading@10: if (cat->cur_idx < ctx->nb_inputs) { yading@10: /* pad audio streams with silence */ yading@10: str = cat->nb_streams[AVMEDIA_TYPE_VIDEO]; yading@10: str_max = str + cat->nb_streams[AVMEDIA_TYPE_AUDIO]; yading@10: for (; str < str_max; str++) { yading@10: ret = send_silence(ctx, cat->cur_idx - ctx->nb_outputs + str, str, yading@10: seg_delta); yading@10: if (ret < 0) yading@10: return ret; yading@10: } yading@10: /* flush queued buffers */ yading@10: /* possible enhancement: flush in PTS order */ yading@10: str_max = cat->cur_idx + ctx->nb_outputs; yading@10: for (str = cat->cur_idx; str < str_max; str++) { yading@10: while (cat->in[str].queue.available) { yading@10: ret = push_frame(ctx, str, ff_bufqueue_get(&cat->in[str].queue)); yading@10: if (ret < 0) yading@10: return ret; yading@10: } yading@10: } yading@10: } yading@10: return 0; yading@10: } yading@10: yading@10: static int request_frame(AVFilterLink *outlink) yading@10: { yading@10: AVFilterContext *ctx = outlink->src; yading@10: ConcatContext *cat = ctx->priv; yading@10: unsigned out_no = FF_OUTLINK_IDX(outlink); yading@10: unsigned in_no = out_no + cat->cur_idx; yading@10: unsigned str, str_max; yading@10: int ret; yading@10: yading@10: while (1) { yading@10: if (in_no >= ctx->nb_inputs) yading@10: return AVERROR_EOF; yading@10: if (!cat->in[in_no].eof) { yading@10: ret = ff_request_frame(ctx->inputs[in_no]); yading@10: if (ret != AVERROR_EOF) yading@10: return ret; yading@10: close_input(ctx, in_no); yading@10: } yading@10: /* cycle on all inputs to finish the segment */ yading@10: /* possible enhancement: request in PTS order */ yading@10: str_max = cat->cur_idx + ctx->nb_outputs - 1; yading@10: for (str = cat->cur_idx; cat->nb_in_active; yading@10: str = str == str_max ? cat->cur_idx : str + 1) { yading@10: if (cat->in[str].eof) yading@10: continue; yading@10: ret = ff_request_frame(ctx->inputs[str]); yading@10: if (ret == AVERROR_EOF) yading@10: close_input(ctx, str); yading@10: else if (ret < 0) yading@10: return ret; yading@10: } yading@10: ret = flush_segment(ctx); yading@10: if (ret < 0) yading@10: return ret; yading@10: in_no += ctx->nb_outputs; yading@10: } yading@10: } yading@10: yading@10: static av_cold int init(AVFilterContext *ctx) yading@10: { yading@10: ConcatContext *cat = ctx->priv; yading@10: unsigned seg, type, str; yading@10: yading@10: /* create input pads */ yading@10: for (seg = 0; seg < cat->nb_segments; seg++) { yading@10: for (type = 0; type < TYPE_ALL; type++) { yading@10: for (str = 0; str < cat->nb_streams[type]; str++) { yading@10: AVFilterPad pad = { yading@10: .type = type, yading@10: .get_video_buffer = get_video_buffer, yading@10: .get_audio_buffer = get_audio_buffer, yading@10: .filter_frame = filter_frame, yading@10: }; yading@10: pad.name = av_asprintf("in%d:%c%d", seg, "va"[type], str); yading@10: ff_insert_inpad(ctx, ctx->nb_inputs, &pad); yading@10: } yading@10: } yading@10: } yading@10: /* create output pads */ yading@10: for (type = 0; type < TYPE_ALL; type++) { yading@10: for (str = 0; str < cat->nb_streams[type]; str++) { yading@10: AVFilterPad pad = { yading@10: .type = type, yading@10: .config_props = config_output, yading@10: .request_frame = request_frame, yading@10: }; yading@10: pad.name = av_asprintf("out:%c%d", "va"[type], str); yading@10: ff_insert_outpad(ctx, ctx->nb_outputs, &pad); yading@10: } yading@10: } yading@10: yading@10: cat->in = av_calloc(ctx->nb_inputs, sizeof(*cat->in)); yading@10: if (!cat->in) yading@10: return AVERROR(ENOMEM); yading@10: cat->nb_in_active = ctx->nb_outputs; yading@10: return 0; yading@10: } yading@10: yading@10: static av_cold void uninit(AVFilterContext *ctx) yading@10: { yading@10: ConcatContext *cat = ctx->priv; yading@10: unsigned i; yading@10: yading@10: for (i = 0; i < ctx->nb_inputs; i++) { yading@10: av_freep(&ctx->input_pads[i].name); yading@10: ff_bufqueue_discard_all(&cat->in[i].queue); yading@10: } yading@10: for (i = 0; i < ctx->nb_outputs; i++) yading@10: av_freep(&ctx->output_pads[i].name); yading@10: av_free(cat->in); yading@10: } yading@10: yading@10: AVFilter avfilter_avf_concat = { yading@10: .name = "concat", yading@10: .description = NULL_IF_CONFIG_SMALL("Concatenate audio and video streams."), yading@10: .init = init, yading@10: .uninit = uninit, yading@10: .query_formats = query_formats, yading@10: .priv_size = sizeof(ConcatContext), yading@10: .inputs = NULL, yading@10: .outputs = NULL, yading@10: .priv_class = &concat_class, yading@10: .flags = AVFILTER_FLAG_DYNAMIC_INPUTS | AVFILTER_FLAG_DYNAMIC_OUTPUTS, yading@10: };