| @@ -0,0 +1,209 @@ |
| | 1 | /* |
| | 2 | * Copyright (c) 2011 Nicolas George <nicolas.george@normalesup.org> |
| | 3 | * |
| | 4 | * This file is part of FFmpeg. |
| | 5 | * |
| | 6 | * FFmpeg is free software; you can redistribute it and/or |
| | 7 | * modify it under the terms of the GNU Lesser General Public |
| | 8 | * License as published by the Free Software Foundation; either |
| | 9 | * version 2.1 of the License, or (at your option) any later version. |
| | 10 | * |
| | 11 | * FFmpeg is distributed in the hope that it will be useful, |
| | 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| | 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| | 14 | * GNU General Public License for more details. |
| | 15 | * |
| | 16 | * You should have received a copy of the GNU Lesser General Public |
| | 17 | * License along with FFmpeg; if not, write to the Free Software |
| | 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| | 19 | */ |
| | 20 | |
| | 21 | /** |
| | 22 | * @file |
| | 23 | * Stream (de)synchronization filter |
| | 24 | */ |
| | 25 | |
| | 26 | #include "libavutil/eval.h" |
| | 27 | #include "avfilter.h" |
| | 28 | #include "internal.h" |
| | 29 | |
| | 30 | #define QUEUE_SIZE 16 |
| | 31 | |
| | 32 | static const char * const var_names[] = { |
| | 33 | "b1", "b2", |
| | 34 | "s1", "s2", |
| | 35 | "t1", "t2", |
| | 36 | NULL |
| | 37 | }; |
| | 38 | |
| | 39 | enum var_name { |
| | 40 | VAR_B1, VAR_B2, |
| | 41 | VAR_S1, VAR_S2, |
| | 42 | VAR_T1, VAR_T2, |
| | 43 | VAR_NB |
| | 44 | }; |
| | 45 | |
| | 46 | typedef struct { |
| | 47 | AVExpr *expr; |
| | 48 | double var_values[VAR_NB]; |
| | 49 | struct buf_queue { |
| | 50 | AVFilterBufferRef *buf[QUEUE_SIZE]; |
| | 51 | unsigned tail, nb; |
| | 52 | /* buf[tail] is the oldest, |
| | 53 | buf[(tail + nb) % QUEUE_SIZE] is where the next is added */ |
| | 54 | } queue[2]; |
| | 55 | int req[2]; |
| | 56 | int next_out; |
| | 57 | int eof; /* bitmask, one bit for each stream */ |
| | 58 | } AStreamSyncContext; |
| | 59 | |
| | 60 | static const char *default_expr = "t1-t2"; |
| | 61 | |
| | 62 | static av_cold int init(AVFilterContext *ctx, const char *args0, void *opaque) |
| | 63 | { |
| | 64 | AStreamSyncContext *as = ctx->priv; |
| | 65 | const char *expr = args0 ? args0 : default_expr; |
| | 66 | int r, i; |
| | 67 | |
| | 68 | r = av_expr_parse(&as->expr, expr, var_names, |
| | 69 | NULL, NULL, NULL, NULL, 0, ctx); |
| | 70 | if (r < 0) { |
| | 71 | av_log(ctx, AV_LOG_ERROR, "Error in expression \"%s\"\n", expr); |
| | 72 | return r; |
| | 73 | } |
| | 74 | for (i = 0; i < 42; i++) |
| | 75 | av_expr_eval(as->expr, as->var_values, NULL); /* exercize prng */ |
| | 76 | return 0; |
| | 77 | } |
| | 78 | |
| | 79 | static int query_formats(AVFilterContext *ctx) |
| | 80 | { |
| | 81 | int i; |
| | 82 | AVFilterFormats *formats; |
| | 83 | |
| | 84 | for (i = 0; i < 2; i++) { |
| | 85 | formats = ctx->inputs[i]->in_formats; |
| | 86 | avfilter_formats_ref(formats, &ctx->inputs[i]->out_formats); |
| | 87 | avfilter_formats_ref(formats, &ctx->outputs[i]->in_formats); |
| | 88 | formats = ctx->inputs[i]->in_packing; |
| | 89 | avfilter_formats_ref(formats, &ctx->inputs[i]->out_packing); |
| | 90 | avfilter_formats_ref(formats, &ctx->outputs[i]->in_packing); |
| | 91 | formats = ctx->inputs[i]->in_chlayouts; |
| | 92 | avfilter_formats_ref(formats, &ctx->inputs[i]->out_chlayouts); |
| | 93 | avfilter_formats_ref(formats, &ctx->outputs[i]->in_chlayouts); |
| | 94 | } |
| | 95 | return 0; |
| | 96 | } |
| | 97 | |
| | 98 | static int config_output(AVFilterLink *outlink) |
| | 99 | { |
| | 100 | AVFilterContext *ctx = outlink->src; |
| | 101 | int id = outlink == ctx->outputs[1]; |
| | 102 | int i; |
| | 103 | |
| | 104 | outlink->sample_rate = ctx->inputs[id]->sample_rate; |
| | 105 | outlink->time_base = ctx->inputs[id]->time_base; |
| | 106 | return 0; |
| | 107 | } |
| | 108 | |
| | 109 | static void send_out(AVFilterContext *ctx, int out_id) |
| | 110 | { |
| | 111 | AStreamSyncContext *as = ctx->priv; |
| | 112 | struct buf_queue *queue = &as->queue[out_id]; |
| | 113 | AVFilterBufferRef *buf = queue->buf[queue->tail]; |
| | 114 | |
| | 115 | queue->buf[queue->tail] = NULL; |
| | 116 | as->var_values[VAR_B1 + out_id]++; |
| | 117 | as->var_values[VAR_S1 + out_id] += buf->audio->nb_samples; |
| | 118 | if (buf->pts != AV_NOPTS_VALUE) |
| | 119 | as->var_values[VAR_T1 + out_id] = |
| | 120 | av_q2d(ctx->outputs[out_id]->time_base) * buf->pts; |
| | 121 | as->var_values[VAR_T1 + out_id] += buf->audio->nb_samples / |
| | 122 | (double)ctx->inputs[out_id]->sample_rate; |
| | 123 | avfilter_filter_samples(ctx->outputs[out_id], buf); |
| | 124 | queue->nb--; |
| | 125 | queue->tail = (queue->tail + 1) % QUEUE_SIZE; |
| | 126 | if (as->req[out_id]) |
| | 127 | as->req[out_id]--; |
| | 128 | } |
| | 129 | |
| | 130 | static void send_next(AVFilterContext *ctx) |
| | 131 | { |
| | 132 | AStreamSyncContext *as = ctx->priv; |
| | 133 | int i; |
| | 134 | |
| | 135 | while (1) { |
| | 136 | if (!as->queue[as->next_out].nb) |
| | 137 | break; |
| | 138 | send_out(ctx, as->next_out); |
| | 139 | if (!as->eof) |
| | 140 | as->next_out = av_expr_eval(as->expr, as->var_values, NULL) >= 0; |
| | 141 | } |
| | 142 | for (i = 0; i < 2; i++) |
| | 143 | if (as->queue[i].nb == QUEUE_SIZE) |
| | 144 | send_out(ctx, i); |
| | 145 | } |
| | 146 | |
| | 147 | static int request_frame(AVFilterLink *outlink) |
| | 148 | { |
| | 149 | AVFilterContext *ctx = outlink->src; |
| | 150 | AStreamSyncContext *as = ctx->priv; |
| | 151 | int id = outlink == ctx->outputs[1]; |
| | 152 | |
| | 153 | as->req[id]++; |
| | 154 | while (as->req[id] && !(as->eof & (1 << id))) { |
| | 155 | if (as->queue[as->next_out].nb) { |
| | 156 | send_next(ctx); |
| | 157 | } else { |
| | 158 | as->eof |= 1 << as->next_out; |
| | 159 | avfilter_request_frame(ctx->inputs[as->next_out]); |
| | 160 | if (as->eof & (1 << as->next_out)) |
| | 161 | as->next_out = !as->next_out; |
| | 162 | } |
| | 163 | } |
| | 164 | return 0; |
| | 165 | } |
| | 166 | |
| | 167 | static void filter_samples(AVFilterLink *inlink, AVFilterBufferRef *insamples) |
| | 168 | { |
| | 169 | AVFilterContext *ctx = inlink->dst; |
| | 170 | AStreamSyncContext *as = ctx->priv; |
| | 171 | int id = inlink == ctx->inputs[1]; |
| | 172 | |
| | 173 | as->queue[id].buf[(as->queue[id].tail + as->queue[id].nb++) % QUEUE_SIZE] = |
| | 174 | insamples; |
| | 175 | as->eof &= ~(1 << id); |
| | 176 | send_next(ctx); |
| | 177 | } |
| | 178 | |
| | 179 | AVFilter avfilter_af_astreamsync = { |
| | 180 | .name = "astreamsync", |
| | 181 | .description = NULL_IF_CONFIG_SMALL("Copy two streams of audio data " |
| | 182 | "in a configurable order."), |
| | 183 | .priv_size = sizeof(AStreamSyncContext), |
| | 184 | .init = init, |
| | 185 | .query_formats = query_formats, |
| | 186 | |
| | 187 | .inputs = (const AVFilterPad[]) { |
| | 188 | { .name = "in1", |
| | 189 | .type = AVMEDIA_TYPE_AUDIO, |
| | 190 | .filter_samples = filter_samples, |
| | 191 | .min_perms = AV_PERM_READ, }, |
| | 192 | { .name = "in2", |
| | 193 | .type = AVMEDIA_TYPE_AUDIO, |
| | 194 | .filter_samples = filter_samples, |
| | 195 | .min_perms = AV_PERM_READ, }, |
| | 196 | { .name = NULL } |
| | 197 | }, |
| | 198 | .outputs = (const AVFilterPad[]) { |
| | 199 | { .name = "out1", |
| | 200 | .type = AVMEDIA_TYPE_AUDIO, |
| | 201 | .config_props = config_output, |
| | 202 | .request_frame = request_frame, }, |
| | 203 | { .name = "out2", |
| | 204 | .type = AVMEDIA_TYPE_AUDIO, |
| | 205 | .config_props = config_output, |
| | 206 | .request_frame = request_frame, }, |
| | 207 | { .name = NULL } |
| | 208 | }, |
| | 209 | }; |