1/* 2 * Copyright (c) 2013 Vittorio Giovara 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 GNU 14 * Lesser 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 * Generate a frame packed video, by combining two views in a single surface. 24 */ 25 26#include <string.h> 27 28#include "libavutil/imgutils.h" 29#include "libavutil/opt.h" 30#include "libavutil/pixdesc.h" 31#include "libavutil/rational.h" 32#include "libavutil/stereo3d.h" 33 34#include "avfilter.h" 35#include "formats.h" 36#include "internal.h" 37#include "video.h" 38 39#define LEFT 0 40#define RIGHT 1 41 42typedef struct FramepackContext { 43 const AVClass *class; 44 45 const AVPixFmtDescriptor *pix_desc; ///< agreed pixel format 46 47 enum AVStereo3DType format; ///< frame pack type output 48 49 AVFrame *input_views[2]; ///< input frames 50 51 int64_t double_pts; ///< new pts for frameseq mode 52} FramepackContext; 53 54static const enum AVPixelFormat formats_supported[] = { 55 AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUV444P, 56 AV_PIX_FMT_YUV410P, AV_PIX_FMT_YUVA420P, AV_PIX_FMT_YUVJ420P, 57 AV_PIX_FMT_YUVJ422P, AV_PIX_FMT_YUVJ444P, AV_PIX_FMT_YUVJ440P, 58 AV_PIX_FMT_NONE 59}; 60 61static int query_formats(AVFilterContext *ctx) 62{ 63 // this will ensure that formats are the same on all pads 64 ff_set_common_formats(ctx, ff_make_format_list(formats_supported)); 65 return 0; 66} 67 68static av_cold void framepack_uninit(AVFilterContext *ctx) 69{ 70 FramepackContext *s = ctx->priv; 71 72 // clean any leftover frame 73 av_frame_free(&s->input_views[LEFT]); 74 av_frame_free(&s->input_views[RIGHT]); 75} 76 77static int config_output(AVFilterLink *outlink) 78{ 79 AVFilterContext *ctx = outlink->src; 80 FramepackContext *s = outlink->src->priv; 81 82 int width = ctx->inputs[LEFT]->w; 83 int height = ctx->inputs[LEFT]->h; 84 AVRational time_base = ctx->inputs[LEFT]->time_base; 85 86 // check size and fps match on the other input 87 if (width != ctx->inputs[RIGHT]->w || 88 height != ctx->inputs[RIGHT]->h) { 89 av_log(ctx, AV_LOG_ERROR, 90 "Left and right sizes differ (%dx%d vs %dx%d).\n", 91 width, height, 92 ctx->inputs[RIGHT]->w, ctx->inputs[RIGHT]->h); 93 return AVERROR_INVALIDDATA; 94 } else if (av_cmp_q(time_base, ctx->inputs[RIGHT]->time_base) != 0) { 95 av_log(ctx, AV_LOG_ERROR, 96 "Left and right framerates differ (%d/%d vs %d/%d).\n", 97 time_base.num, time_base.den, 98 ctx->inputs[RIGHT]->time_base.num, 99 ctx->inputs[RIGHT]->time_base.den); 100 return AVERROR_INVALIDDATA; 101 } 102 103 s->pix_desc = av_pix_fmt_desc_get(outlink->format); 104 if (!s->pix_desc) 105 return AVERROR_BUG; 106 107 // modify output properties as needed 108 switch (s->format) { 109 case AV_STEREO3D_FRAMESEQUENCE: 110 time_base.den *= 2; 111 s->double_pts = AV_NOPTS_VALUE; 112 break; 113 case AV_STEREO3D_COLUMNS: 114 case AV_STEREO3D_SIDEBYSIDE: 115 width *= 2; 116 break; 117 case AV_STEREO3D_LINES: 118 case AV_STEREO3D_TOPBOTTOM: 119 height *= 2; 120 break; 121 default: 122 av_log(ctx, AV_LOG_ERROR, "Unknown packing mode."); 123 return AVERROR_INVALIDDATA; 124 } 125 126 outlink->w = width; 127 outlink->h = height; 128 outlink->time_base = time_base; 129 130 return 0; 131} 132 133static void horizontal_frame_pack(FramepackContext *s, 134 AVFrame *dst, 135 int interleaved) 136{ 137 int plane, i; 138 int length = dst->width / 2; 139 int lines = dst->height; 140 141 for (plane = 0; plane < s->pix_desc->nb_components; plane++) { 142 const uint8_t *leftp = s->input_views[LEFT]->data[plane]; 143 const uint8_t *rightp = s->input_views[RIGHT]->data[plane]; 144 uint8_t *dstp = dst->data[plane]; 145 146 if (plane == 1 || plane == 2) { 147 length = -(-(dst->width / 2) >> s->pix_desc->log2_chroma_w); 148 lines = -(-(dst->height) >> s->pix_desc->log2_chroma_h); 149 } 150 151 if (interleaved) { 152 for (i = 0; i < lines; i++) { 153 int j; 154 int k = 0; 155 156 for (j = 0; j < length; j++) { 157 dstp[k++] = leftp[j]; 158 dstp[k++] = rightp[j]; 159 } 160 161 dstp += dst->linesize[plane]; 162 leftp += s->input_views[LEFT]->linesize[plane]; 163 rightp += s->input_views[RIGHT]->linesize[plane]; 164 } 165 } else { 166 av_image_copy_plane(dst->data[plane], dst->linesize[plane], 167 leftp, s->input_views[LEFT]->linesize[plane], 168 length, lines); 169 av_image_copy_plane(dst->data[plane] + length, dst->linesize[plane], 170 rightp, s->input_views[RIGHT]->linesize[plane], 171 length, lines); 172 } 173 } 174} 175 176static void vertical_frame_pack(FramepackContext *s, 177 AVFrame *dst, 178 int interleaved) 179{ 180 int plane, offset; 181 int length = dst->width; 182 int lines = dst->height / 2; 183 184 for (plane = 0; plane < s->pix_desc->nb_components; plane++) { 185 if (plane == 1 || plane == 2) { 186 length = -(-(dst->width) >> s->pix_desc->log2_chroma_w); 187 lines = -(-(dst->height / 2) >> s->pix_desc->log2_chroma_h); 188 } 189 190 offset = interleaved ? dst->linesize[plane] : dst->linesize[plane] * lines; 191 192 av_image_copy_plane(dst->data[plane], 193 dst->linesize[plane] << interleaved, 194 s->input_views[LEFT]->data[plane], 195 s->input_views[LEFT]->linesize[plane], 196 length, lines); 197 av_image_copy_plane(dst->data[plane] + offset, 198 dst->linesize[plane] << interleaved, 199 s->input_views[RIGHT]->data[plane], 200 s->input_views[RIGHT]->linesize[plane], 201 length, lines); 202 } 203} 204 205static av_always_inline void spatial_frame_pack(FramepackContext *s, AVFrame *dst) 206{ 207 switch (s->format) { 208 case AV_STEREO3D_SIDEBYSIDE: 209 horizontal_frame_pack(s, dst, 0); 210 break; 211 case AV_STEREO3D_COLUMNS: 212 horizontal_frame_pack(s, dst, 1); 213 break; 214 case AV_STEREO3D_TOPBOTTOM: 215 vertical_frame_pack(s, dst, 0); 216 break; 217 case AV_STEREO3D_LINES: 218 vertical_frame_pack(s, dst, 1); 219 break; 220 } 221} 222 223static int filter_frame_left(AVFilterLink *inlink, AVFrame *frame) 224{ 225 FramepackContext *s = inlink->dst->priv; 226 s->input_views[LEFT] = frame; 227 return 0; 228} 229 230static int filter_frame_right(AVFilterLink *inlink, AVFrame *frame) 231{ 232 FramepackContext *s = inlink->dst->priv; 233 s->input_views[RIGHT] = frame; 234 return 0; 235} 236 237static int request_frame(AVFilterLink *outlink) 238{ 239 AVFilterContext *ctx = outlink->src; 240 FramepackContext *s = ctx->priv; 241 AVStereo3D *stereo; 242 int ret, i; 243 244 /* get a frame on the either input, stop as soon as a video ends */ 245 for (i = 0; i < 2; i++) { 246 if (!s->input_views[i]) { 247 ret = ff_request_frame(ctx->inputs[i]); 248 if (ret < 0) 249 return ret; 250 } 251 } 252 253 if (s->format == AV_STEREO3D_FRAMESEQUENCE) { 254 if (s->double_pts == AV_NOPTS_VALUE) 255 s->double_pts = s->input_views[LEFT]->pts; 256 257 for (i = 0; i < 2; i++) { 258 // set correct timestamps 259 s->input_views[i]->pts = s->double_pts++; 260 261 // set stereo3d side data 262 stereo = av_stereo3d_create_side_data(s->input_views[i]); 263 if (!stereo) 264 return AVERROR(ENOMEM); 265 stereo->type = s->format; 266 267 // filter the frame and immediately relinquish its pointer 268 ret = ff_filter_frame(outlink, s->input_views[i]); 269 s->input_views[i] = NULL; 270 if (ret < 0) 271 return ret; 272 } 273 return ret; 274 } else { 275 AVFrame *dst = ff_get_video_buffer(outlink, outlink->w, outlink->h); 276 if (!dst) 277 return AVERROR(ENOMEM); 278 279 spatial_frame_pack(s, dst); 280 281 // get any property from the original frame 282 ret = av_frame_copy_props(dst, s->input_views[LEFT]); 283 if (ret < 0) { 284 av_frame_free(&dst); 285 return ret; 286 } 287 288 for (i = 0; i < 2; i++) 289 av_frame_free(&s->input_views[i]); 290 291 // set stereo3d side data 292 stereo = av_stereo3d_create_side_data(dst); 293 if (!stereo) { 294 av_frame_free(&dst); 295 return AVERROR(ENOMEM); 296 } 297 stereo->type = s->format; 298 299 return ff_filter_frame(outlink, dst); 300 } 301} 302 303#define OFFSET(x) offsetof(FramepackContext, x) 304#define V AV_OPT_FLAG_VIDEO_PARAM 305static const AVOption options[] = { 306 { "format", "Frame pack output format", OFFSET(format), AV_OPT_TYPE_INT, 307 { .i64 = AV_STEREO3D_SIDEBYSIDE }, 0, INT_MAX, .flags = V, .unit = "format" }, 308 { "sbs", "Views are packed next to each other", 0, AV_OPT_TYPE_CONST, 309 { .i64 = AV_STEREO3D_SIDEBYSIDE }, INT_MIN, INT_MAX, .flags = V, .unit = "format" }, 310 { "tab", "Views are packed on top of each other", 0, AV_OPT_TYPE_CONST, 311 { .i64 = AV_STEREO3D_TOPBOTTOM }, INT_MIN, INT_MAX, .flags = V, .unit = "format" }, 312 { "frameseq", "Views are one after the other", 0, AV_OPT_TYPE_CONST, 313 { .i64 = AV_STEREO3D_FRAMESEQUENCE }, INT_MIN, INT_MAX, .flags = V, .unit = "format" }, 314 { "lines", "Views are interleaved by lines", 0, AV_OPT_TYPE_CONST, 315 { .i64 = AV_STEREO3D_LINES }, INT_MIN, INT_MAX, .flags = V, .unit = "format" }, 316 { "columns", "Views are interleaved by columns", 0, AV_OPT_TYPE_CONST, 317 { .i64 = AV_STEREO3D_COLUMNS }, INT_MIN, INT_MAX, .flags = V, .unit = "format" }, 318 { NULL }, 319}; 320 321static const AVClass framepack_class = { 322 .class_name = "framepack", 323 .item_name = av_default_item_name, 324 .option = options, 325 .version = LIBAVUTIL_VERSION_INT, 326}; 327 328static const AVFilterPad framepack_inputs[] = { 329 { 330 .name = "left", 331 .type = AVMEDIA_TYPE_VIDEO, 332 .filter_frame = filter_frame_left, 333 .needs_fifo = 1, 334 }, 335 { 336 .name = "right", 337 .type = AVMEDIA_TYPE_VIDEO, 338 .filter_frame = filter_frame_right, 339 .needs_fifo = 1, 340 }, 341 { NULL } 342}; 343 344static const AVFilterPad framepack_outputs[] = { 345 { 346 .name = "packed", 347 .type = AVMEDIA_TYPE_VIDEO, 348 .config_props = config_output, 349 .request_frame = request_frame, 350 }, 351 { NULL } 352}; 353 354AVFilter ff_vf_framepack = { 355 .name = "framepack", 356 .description = NULL_IF_CONFIG_SMALL("Generate a frame packed stereoscopic video."), 357 .priv_size = sizeof(FramepackContext), 358 .priv_class = &framepack_class, 359 .query_formats = query_formats, 360 .inputs = framepack_inputs, 361 .outputs = framepack_outputs, 362 .uninit = framepack_uninit, 363}; 364