/* * Copyright (C) 2010-2015 Mamadou DIOP. * * This file is part of Open Source Doubango Framework. * * DOUBANGO is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * DOUBANGO 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with DOUBANGO. * */ /**@file tdav_converter_video.c * @brief Video converter. * * @author Mamadou Diop * @author Alex Vishnev (Added support for rotation) */ #include "tinydav/video/tdav_converter_video.h" #include "tsk_memory.h" #include "tsk_debug.h" // FIXME: FFmpeg implementation do not support "scale_rotated_frames" option #if HAVE_LIBYUV #include using namespace libyuv; typedef struct tdav_converter_video_libyuv_s { TMEDIA_DECLARE_CONVERTER_VIDEO; enum FourCC srcFormat; enum FourCC dstFormat; tsk_bool_t toI420; tsk_bool_t fromI420; struct{ uint8* ptr; int size; }chroma; struct{ uint8* ptr; int size; }rotate; struct{ uint8* ptr; int size; }scale; struct{ uint8* ptr; int size; }mirror; } tdav_converter_video_libyuv_t; #define TDAV_CONVERTER_VIDEO_LIBYUV(self) ((tdav_converter_video_libyuv_t*)(self)) #define LIBYUV_INPUT_BUFFER_PADDING_SIZE 32 static inline tsk_bool_t _tdav_converter_video_libyuv_is_chroma_varsize(tmedia_chroma_t chroma) { return chroma == tmedia_chroma_mjpeg; } static inline tsk_size_t _tdav_converter_video_libyuv_get_size(tmedia_chroma_t chroma, tsk_size_t w, tsk_size_t h) { switch (chroma){ case tmedia_chroma_rgb24: case tmedia_chroma_bgr24: return (w * h * 3); case tmedia_chroma_rgb565le: return ((w * h) << 1); case tmedia_chroma_rgb32: return ((w * h) << 2); case tmedia_chroma_nv21: return ((w * h * 3) >> 1); case tmedia_chroma_nv12: return ((w * h * 3) >> 1); case tmedia_chroma_yuv422p: return ((w * h) << 1); case tmedia_chroma_uyvy422: case tmedia_chroma_yuyv422: return ((w * h) << 1); case tmedia_chroma_yuv420p: return ((w * h * 3) >> 1); case tmedia_chroma_mjpeg: return 0; default: TSK_DEBUG_ERROR("Invalid chroma %d", (int)chroma); return 0; } } static inline enum FourCC _tdav_converter_video_libyuv_get_pixfmt(tmedia_chroma_t chroma) { switch (chroma){ case tmedia_chroma_rgb24: case tmedia_chroma_bgr24: return FOURCC_24BG; case tmedia_chroma_rgb565le: return FOURCC_RGBP; case tmedia_chroma_rgb32: return FOURCC_ARGB; case tmedia_chroma_nv21: return FOURCC_NV21; case tmedia_chroma_nv12: return FOURCC_NV12; case tmedia_chroma_yuv422p: return FOURCC_I422; case tmedia_chroma_uyvy422: return FOURCC_UYVY; case tmedia_chroma_yuyv422: return FOURCC_YUY2; case tmedia_chroma_yuv420p: return FOURCC_I420; case tmedia_chroma_mjpeg: return FOURCC_MJPG; default: TSK_DEBUG_ERROR("Invalid chroma %d", (int)chroma); return FOURCC_ANY; } } static int tdav_converter_video_libyuv_init(tmedia_converter_video_t* self, tsk_size_t srcWidth, tsk_size_t srcHeight, tmedia_chroma_t srcChroma, tsk_size_t dstWidth, tsk_size_t dstHeight, tmedia_chroma_t dstChroma) { TSK_DEBUG_INFO("Initializing new LibYUV Video Converter src=(%dx%d@%d) dst=(%dx%d@%d)", srcWidth, srcHeight, srcChroma, dstWidth, dstHeight, dstChroma); if ((TDAV_CONVERTER_VIDEO_LIBYUV(self)->srcFormat = _tdav_converter_video_libyuv_get_pixfmt(srcChroma)) == FOURCC_ANY){ TSK_DEBUG_ERROR("Invalid source chroma"); return -2; } if ((TDAV_CONVERTER_VIDEO_LIBYUV(self)->dstFormat = _tdav_converter_video_libyuv_get_pixfmt(dstChroma)) == FOURCC_ANY){ TSK_DEBUG_ERROR("Invalid destination chroma"); return -3; } TDAV_CONVERTER_VIDEO_LIBYUV(self)->toI420 = (TDAV_CONVERTER_VIDEO_LIBYUV(self)->dstFormat == FOURCC_I420); TDAV_CONVERTER_VIDEO_LIBYUV(self)->fromI420 = (TDAV_CONVERTER_VIDEO_LIBYUV(self)->srcFormat == FOURCC_I420); if (!TDAV_CONVERTER_VIDEO_LIBYUV(self)->toI420 && !TDAV_CONVERTER_VIDEO_LIBYUV(self)->fromI420) { TSK_DEBUG_ERROR("LIBYUV only support from/to I420"); return -1; } return 0; } static tsk_size_t tdav_converter_video_libyuv_process(tmedia_converter_video_t* _self, const void* buffer, tsk_size_t buffer_size, void** output, tsk_size_t* output_max_size) { #define RESIZE_BUFFER(buff, curr_size, new_size) \ if((int)(curr_size) < (new_size)){ \ if(!((buff) = (uint8*)tsk_realloc((buff), (new_size)))){ \ (curr_size) = 0; \ TSK_DEBUG_ERROR("Failed to allocate buffer"); \ return 0; \ } \ (curr_size) = (new_size); \ } static const int crop_x = 0; static const int crop_y = 0; int ret; tdav_converter_video_libyuv_t* self = TDAV_CONVERTER_VIDEO_LIBYUV(_self); tsk_bool_t scale = ((_self->dstWidth != _self->srcWidth) || (_self->dstHeight != _self->srcHeight)); int s, ls, src_y_stride, src_u_stride, src_v_stride, dst_y_stride, dst_u_stride, dst_v_stride; int src_w, src_h, dst_w, dst_h; uint8 *dst_y, *dst_u, *dst_v, *src_y, *src_u, *src_v; RotationMode rotation = kRotate0; switch (_self->rotation){ case 90: rotation = kRotate90; break; case 180: rotation = kRotate180; break; case 270: rotation = kRotate270; break; } //rotation = kRotate0; // not square and rotaion=270/90 -> requires scaling unless disabled if ((rotation == kRotate90 || rotation == kRotate270) && _self->scale_rotated_frames){ scale |= (_self->dstWidth != _self->dstHeight) && (rotation == kRotate90 || rotation == kRotate270); } src_w = (int)_self->srcWidth, src_h = (int)_self->srcHeight; if (self->toI420) { tsk_size_t x_in_size; // check input size x_in_size = _tdav_converter_video_libyuv_is_chroma_varsize(_self->srcChroma) ? buffer_size : _tdav_converter_video_libyuv_get_size(_self->srcChroma, src_w, src_h); if (x_in_size > buffer_size) { // Ignore any extra data. For example, "CVPixelBufferGetDataSize()" will return size padded with 8 extra bytes for RGB32. TSK_DEBUG_ERROR("Invalid input size: %u>%u", x_in_size, buffer_size); return 0; } dst_w = src_w, dst_h = src_h; // because no scaling when converting to I420 ls = src_w * src_h; s = ((ls * 3) >> 1); if (scale || rotation != kRotate0){ RESIZE_BUFFER(self->chroma.ptr, self->chroma.size, s); dst_y = self->chroma.ptr; } else{ RESIZE_BUFFER((*output), (*output_max_size), s); dst_y = (uint8*)*output; } dst_u = (dst_y + ls); dst_v = dst_u + (ls >> 2); src_y_stride = dst_y_stride = src_w; src_u_stride = src_v_stride = dst_u_stride = dst_v_stride = ((dst_y_stride + 1) >> 1); // convert to I420 without scaling or rotation ret = ConvertToI420( (const uint8*)buffer, (int)x_in_size, dst_y, dst_y_stride, dst_u, dst_u_stride, dst_v, dst_v_stride, crop_x, crop_y, (int)_self->srcWidth, (int)(_self->flip ? (_self->srcHeight * -1) : _self->srcHeight), // vertical flip (int)_self->srcWidth, (int)_self->srcHeight, kRotate0, (uint32)self->srcFormat); // mirror: horizontal flip (front camera video) if (_self->mirror) { RESIZE_BUFFER(self->mirror.ptr, self->mirror.size, s); ret = I420Mirror( dst_y, dst_y_stride, dst_u, dst_u_stride, dst_v, dst_v_stride, self->mirror.ptr, dst_y_stride, (self->mirror.ptr + ls), dst_u_stride, (self->mirror.ptr + ls + (ls >> 2)), dst_v_stride, (int)_self->srcWidth, (int)_self->srcHeight); memcpy(dst_y, self->mirror.ptr, s); } if (ret){ TSK_DEBUG_ERROR("ConvertToI420 failed with error code = %d, in_size:%u", ret, x_in_size); return 0; } // rotate if (rotation != kRotate0){ dst_w = (int)((rotation == kRotate90 || rotation == kRotate270) ? _self->srcHeight : _self->srcWidth); dst_h = (int)((rotation == kRotate90 || rotation == kRotate270) ? _self->srcWidth : _self->srcHeight); src_y = dst_y, src_u = dst_u, src_v = dst_v; src_y_stride = src_y_stride, src_u_stride = src_u_stride, src_v_stride = src_v_stride; dst_y_stride = dst_w; dst_u_stride = dst_v_stride = ((dst_y_stride + 1) >> 1); if (scale){ RESIZE_BUFFER(self->rotate.ptr, self->rotate.size, s); dst_y = self->rotate.ptr; } else{// last step RESIZE_BUFFER((*output), (*output_max_size), s); dst_y = (uint8*)*output; } dst_u = (dst_y + ls); dst_v = dst_u + (ls >> 2); ret = I420Rotate( src_y, src_y_stride, src_u, src_u_stride, src_v, src_v_stride, dst_y, dst_y_stride, dst_u, dst_u_stride, dst_v, dst_v_stride, (int)_self->srcWidth, (int)_self->srcHeight, rotation); if (ret){ TSK_DEBUG_ERROR("I420Rotate failed with error code = %d", ret); return 0; } // scale to fit ratio, pad, crop then copy if ((rotation == kRotate90 || rotation == kRotate270) && _self->scale_rotated_frames){ int iwidth = (int)_self->srcHeight; int iheight = (int)_self->srcWidth; src_y = dst_y, src_u = dst_u, src_v = dst_v; src_w = dst_w, src_h = dst_h; src_y_stride = dst_y_stride, src_u_stride = dst_u_stride, src_v_stride = dst_v_stride; if (_self->dstWidth != _self->dstHeight) { if (iwidth * _self->srcHeight > iheight * _self->srcWidth) { iwidth = (int)((iheight * _self->srcWidth / _self->srcHeight) & ~1); int iwidth_offset = (int)((_self->srcHeight - iwidth) >> 1); src_y += iwidth_offset; src_u += iwidth_offset >> 1; src_v += iwidth_offset >> 1; } else if (iwidth * _self->srcHeight < iheight * _self->srcWidth) { iheight = (int)(iwidth * _self->srcHeight / _self->srcWidth); int iheight_offset = (int)((_self->srcWidth - iheight) >> 2); iheight_offset <<= 1; src_y += iheight_offset * src_y_stride; src_u += (iheight_offset >> 1) * src_u_stride; src_v += (iheight_offset >> 1) * src_v_stride; } src_w = iwidth, src_h = iheight; src_y_stride = src_w; src_u_stride = src_v_stride = ((src_y_stride + 1) >> 1); dst_w = (int)_self->dstWidth; dst_h = (int)_self->dstHeight; ls = dst_w * dst_h; s = ((ls * 3) >> 1); RESIZE_BUFFER((*output), (*output_max_size), s); dst_y_stride = dst_w; dst_u_stride = dst_v_stride = ((dst_y_stride + 1) >> 1); uint8* dst_y = (uint8*)*output; uint8* dst_u = (dst_y + ls); uint8* dst_v = dst_u + (ls >> 2); ret = I420Scale( src_y, src_y_stride, src_u, src_u_stride, src_v, src_v_stride, src_w, src_h, dst_y, dst_y_stride, dst_u, dst_u_stride, dst_v, dst_v_stride, dst_w, dst_h, kFilterBox); if (ret){ TSK_DEBUG_ERROR("I420Scale failed with error code = %d", ret); return 0; } return s; } } } // scale if (scale){ src_w = dst_w, src_h = dst_h; dst_w = (int)(((rotation == kRotate90 || rotation == kRotate270) && !_self->scale_rotated_frames) ? _self->dstHeight : _self->dstWidth); dst_h = (int)(((rotation == kRotate90 || rotation == kRotate270) && !_self->scale_rotated_frames) ? _self->dstWidth : _self->dstHeight); src_y = dst_y, src_u = dst_u, src_v = dst_v; src_y_stride = dst_y_stride, src_u_stride = dst_u_stride, src_v_stride = dst_v_stride; dst_y_stride = dst_w; dst_u_stride = dst_v_stride = ((dst_y_stride + 1) >> 1); ls = dst_w * dst_h; s = ((ls * 3) >> 1); RESIZE_BUFFER((*output), (*output_max_size), s); dst_y = (uint8*)*output; dst_u = (dst_y + ls); dst_v = dst_u + (ls >> 2); ret = I420Scale( src_y, src_y_stride, src_u, src_u_stride, src_v, src_v_stride, src_w, src_h, dst_y, dst_y_stride, dst_u, dst_u_stride, dst_v, dst_v_stride, dst_w, dst_h, kFilterNone); if (ret){ TSK_DEBUG_ERROR("I420Scale failed with error code = %d", ret); return 0; } } return ((dst_w * dst_h * 3) >> 1); } else if (self->fromI420){ static const int dst_sample_stride = 0; dst_w = (int)_self->dstWidth, dst_h = (int)_self->dstHeight; src_y = (uint8*)buffer; src_u = (src_y + (src_w * src_h)); src_v = (src_u + ((src_w * src_h) >> 2)); src_y_stride = src_w; src_u_stride = src_v_stride = ((src_y_stride + 1) >> 1); // mirror: horizontal flip (front camera video) if ((_self->mirror)) { ls = src_w * src_h; s = ((ls * 3) >> 1); if (s < (int)buffer_size) { // security check RESIZE_BUFFER(self->mirror.ptr, self->mirror.size, s); ret = I420Mirror( src_y, src_y_stride, src_u, src_u_stride, src_v, src_v_stride, self->mirror.ptr, src_y_stride, (self->mirror.ptr + ls), src_u_stride, (self->mirror.ptr + ls + (ls >> 2)), src_v_stride, src_w, src_h); memcpy(src_y, self->mirror.ptr, s); } } if (scale){ ls = dst_w * dst_h; s = ((ls * 3) >> 1); RESIZE_BUFFER(self->scale.ptr, self->scale.size, s); dst_y = self->scale.ptr; dst_u = (dst_y + (dst_w * dst_h)); dst_v = (dst_u + ((dst_w * dst_h) >> 2)); dst_y_stride = dst_w; dst_u_stride = dst_v_stride = ((dst_y_stride + 1) >> 1); ret = I420Scale( src_y, src_y_stride, src_u, src_u_stride, src_v, src_v_stride, src_w, src_h, dst_y, dst_y_stride, dst_u, dst_u_stride, dst_v, dst_v_stride, dst_w, dst_h, kFilterNone); if (ret){ TSK_DEBUG_ERROR("I420Scale failed with error code = %d", ret); return 0; } src_y = dst_y; src_u = (dst_y + ls); src_v = (dst_u + (ls >> 2)); src_y_stride = dst_y_stride; src_u_stride = src_v_stride = ((src_y_stride + 1) >> 1); } s = (int)_tdav_converter_video_libyuv_get_size(_self->dstChroma, _self->srcWidth, _self->srcHeight); RESIZE_BUFFER((*output), (*output_max_size), s); ret = ConvertFromI420( src_y, src_y_stride, src_u, src_u_stride, src_v, src_v_stride, (uint8*)*output, dst_sample_stride, (int)_self->dstWidth, (_self->flip ? ((int)_self->dstHeight * -1) : (int)_self->dstHeight), // vertical flip (uint32)self->dstFormat); if (ret){ TSK_DEBUG_ERROR("ConvertFromI420 failed with error code = %d", ret); return 0; } return s; } // Must be from/to I420 TSK_DEBUG_ERROR("Not expected code called"); return 0; } static tsk_object_t* tdav_converter_video_libyuv_ctor(tsk_object_t * self, va_list * app) { tdav_converter_video_libyuv_t *converter = (tdav_converter_video_libyuv_t *)self; if (converter){ } return self; } static tsk_object_t* tdav_converter_video_libyuv_dtor(tsk_object_t * self) { tdav_converter_video_libyuv_t *converter = (tdav_converter_video_libyuv_t *)self; if (converter){ TSK_FREE(converter->chroma.ptr); TSK_FREE(converter->rotate.ptr); TSK_FREE(converter->scale.ptr); TSK_FREE(converter->mirror.ptr); } return self; } static const tsk_object_def_t tdav_converter_video_libyuv_def_s = { sizeof(tdav_converter_video_libyuv_t), tdav_converter_video_libyuv_ctor, tdav_converter_video_libyuv_dtor, tsk_null, }; const tsk_object_def_t *tdav_converter_video_libyuv_def_t = &tdav_converter_video_libyuv_def_s; static const tmedia_converter_video_plugin_def_t tdav_converter_video_libyuv_plugin_def_s = { &tdav_converter_video_libyuv_def_s, tdav_converter_video_libyuv_init, tdav_converter_video_libyuv_process }; const tmedia_converter_video_plugin_def_t *tdav_converter_video_libyuv_plugin_def_t = &tdav_converter_video_libyuv_plugin_def_s; #endif /* HAVE_LIBYUV */ #if HAVE_FFMPEG || HAVE_SWSSCALE #ifndef INT64_C # define INT64_C(c) (c ## LL) # define UINT64_C(c) (c ## ULL) #endif #ifdef __cplusplus extern "C" { #endif #include #include #ifdef __cplusplus } #endif typedef struct tdav_converter_video_ffmpeg_s { TMEDIA_DECLARE_CONVERTER_VIDEO; struct SwsContext *context; enum PixelFormat srcFormat; enum PixelFormat dstFormat; AVFrame* srcFrame; AVFrame* dstFrame; struct { struct SwsContext *context; AVFrame* frame; uint8_t* buffer; tsk_size_t buffer_size; } rot; } tdav_converter_video_ffmpeg_t; #define TDAV_CONVERTER_VIDEO_FFMPEG(self) ((tdav_converter_video_ffmpeg_t*)(self)) // use macro for performance reasons keep (called (15x3) times per seconds) #define _tdav_converter_video_ffmpeg_rotate90(srcw, srch, srcdata, dstdata) \ { \ register int i,j; \ register int newx = 0; \ for (i = 0; i < (int)(srcw); i ++ ){ \ for( j = (int)srch-1; j >=0; j -- ){ \ (dstdata)[newx++] = (srcdata)[j * (srcw) + i]; \ } \ } \ } #define _tdav_converter_video_ffmpeg_flip(frame, height) \ frame->data[0] += frame->linesize[0] * (height -1); \ frame->data[1] += frame->linesize[1] * ((height -1)>>1); \ frame->data[2] += frame->linesize[2] * ((height -1)>>1); \ \ frame->linesize[0] *= -1; \ frame->linesize[1] *= -1; \ frame->linesize[2] *= -1; static inline enum PixelFormat _tdav_converter_video_ffmpeg_get_pixfmt(tmedia_chroma_t chroma) { switch(chroma){ case tmedia_chroma_rgb24: return PIX_FMT_RGB24; case tmedia_chroma_bgr24: return PIX_FMT_BGR24; case tmedia_chroma_rgb32: return PIX_FMT_RGB32; case tmedia_chroma_rgb565le: return PIX_FMT_RGB565LE; case tmedia_chroma_rgb565be: return PIX_FMT_RGB565BE; case tmedia_chroma_nv21: return PIX_FMT_NV21; case tmedia_chroma_nv12: return PIX_FMT_NV12; case tmedia_chroma_yuv422p: return PIX_FMT_YUV422P; case tmedia_chroma_uyvy422: return PIX_FMT_UYVY422; case tmedia_chroma_yuyv422: return PIX_FMT_YUYV422; case tmedia_chroma_yuv420p: return PIX_FMT_YUV420P; default: TSK_DEBUG_ERROR("Invalid chroma %d", (int)chroma); return PIX_FMT_NONE; } } static int tdav_converter_video_ffmpeg_init(tmedia_converter_video_t* self, tsk_size_t srcWidth, tsk_size_t srcHeight, tmedia_chroma_t srcChroma, tsk_size_t dstWidth, tsk_size_t dstHeight, tmedia_chroma_t dstChroma) { TSK_DEBUG_INFO("Initializing new FFmpeg Video Converter src=(%dx%d@%d) dst=(%dx%d@%d)", srcWidth, srcHeight, srcChroma, dstWidth, dstHeight, dstChroma); if((TDAV_CONVERTER_VIDEO_FFMPEG(self)->srcFormat = _tdav_converter_video_ffmpeg_get_pixfmt(srcChroma)) == PIX_FMT_NONE){ TSK_DEBUG_ERROR("Invalid source chroma"); return -2; } if((TDAV_CONVERTER_VIDEO_FFMPEG(self)->dstFormat = _tdav_converter_video_ffmpeg_get_pixfmt(dstChroma)) == PIX_FMT_NONE){ TSK_DEBUG_ERROR("Invalid destination chroma"); return -3; } return 0; } static tsk_size_t tdav_converter_video_ffmpeg_process(tmedia_converter_video_t* _self, const void* buffer, tsk_size_t buffer_size, void** output, tsk_size_t* output_max_size) { int ret, size; tsk_bool_t _rotate = tsk_false; tdav_converter_video_ffmpeg_t* self = TDAV_CONVERTER_VIDEO_FFMPEG(_self); if (!self || !buffer || !output){ TSK_DEBUG_ERROR("Invalid parameter"); return 0; } /* Pictures */ if (!self->srcFrame){ if (!(self->srcFrame = avcodec_alloc_frame())){ TSK_DEBUG_ERROR("Failed to create picture"); return 0; } } if (!self->dstFrame){ if (!(self->dstFrame = avcodec_alloc_frame())){ TSK_DEBUG_ERROR("Failed to create picture"); return 0; } } size = avpicture_get_size(self->dstFormat, (int)_self->dstWidth, (int)_self->dstHeight); if ((int)*output_max_size < size){ if (!(*output = tsk_realloc(*output, (size + FF_INPUT_BUFFER_PADDING_SIZE)))){ *output_max_size = 0; TSK_DEBUG_ERROR("Failed to allocate buffer"); return 0; } *output_max_size = size; } /* Wrap the source buffer */ ret = avpicture_fill((AVPicture *)self->srcFrame, (uint8_t*)buffer, self->srcFormat, (int)_self->srcWidth, (int)_self->srcHeight); /* Wrap the destination buffer */ ret = avpicture_fill((AVPicture *)self->dstFrame, (uint8_t*)*output, self->dstFormat, (int)_self->dstWidth, (int)_self->dstHeight); /* === performs conversion === */ /* Context */ if (!self->context) { self->context = sws_getContext( (int)_self->srcWidth, (int)_self->srcHeight, self->srcFormat, (int)_self->dstWidth, (int)_self->dstHeight, self->dstFormat, SWS_FAST_BILINEAR, NULL, NULL, NULL); if (!self->context) { TSK_DEBUG_ERROR("Failed to create context"); return 0; } } /*FIXME: For now only 90\B0 rotation is supported this is why we always use libyuv on mobile devices */ _rotate = (PIX_FMT_YUV420P == self->dstFormat) && _self->rotation == 90; // if no rotation then, flip while scaling othersize do it after rotation if (!_rotate && _self->flip) { _tdav_converter_video_ffmpeg_flip(self->dstFrame, _self->dstHeight); } // chroma conversion and scaling ret = sws_scale(self->context, (const uint8_t* const*)self->srcFrame->data, self->srcFrame->linesize, 0, (int)_self->srcHeight, self->dstFrame->data, self->dstFrame->linesize); if (ret < 0){ TSK_FREE(*output); return 0; } // Rotation if (_rotate){ // because we rotated 90 width = original height, height = original width int w = (int)_self->dstHeight; int h = (int)_self->dstWidth; // allocation rotation frame if not already done if (!(self->rot.frame) && !(self->rot.frame = avcodec_alloc_frame())){ TSK_DEBUG_ERROR("failed to allocate rotation frame"); TSK_FREE(*output); return(0); } // allocate rotation temporary buffer size = avpicture_get_size(self->dstFormat, w, h); if (self->rot.buffer_size != size){ if (!(self->rot.buffer = (uint8_t *)av_realloc(self->rot.buffer, size))){ TSK_DEBUG_ERROR("failed to allocate new buffer for the frame"); self->rot.buffer_size = 0; return(0); } self->rot.buffer_size = size; } //wrap avpicture_fill((AVPicture *)self->rot.frame, self->rot.buffer, self->dstFormat, w, h); // rotate _tdav_converter_video_ffmpeg_rotate90(_self->dstWidth, _self->dstHeight, self->dstFrame->data[0], self->rot.frame->data[0]); _tdav_converter_video_ffmpeg_rotate90((_self->dstWidth >> 1), (_self->dstHeight >> 1), self->dstFrame->data[1], self->rot.frame->data[1]); _tdav_converter_video_ffmpeg_rotate90((_self->dstWidth >> 1), (_self->dstHeight >> 1), self->dstFrame->data[2], self->rot.frame->data[2]); // flip if (_self->flip){ _tdav_converter_video_ffmpeg_flip(self->rot.frame, h); } { static const int y_shift = 1; static const int x_shift = 1; int r_size, r_w, r_h, left_band, top_band; int pad = ((int)_self->dstWidth - w) > ((int)_self->dstHeight - h) ? ((int)_self->dstWidth - w) : ((int)_self->dstHeight - h); if (pad < 0){ pad = 0; } r_size; r_w = w + pad; r_h = h + pad; left_band = (int)((r_w - _self->dstWidth) / 2); top_band = (int)((r_h - _self->dstHeight) / 3); if (!self->rot.context){ if (!(self->rot.context = sws_getContext(w, h, self->dstFormat, r_w, r_h, self->dstFormat, SWS_FAST_BILINEAR, NULL, NULL, NULL))){ TSK_DEBUG_ERROR("Failed to create context"); TSK_FREE(*output); return 0; } } r_size = avpicture_get_size(self->dstFormat, r_w, r_h); if ((int)*output_max_size < r_size){ if (!(*output = tsk_realloc(*output, (r_size + FF_INPUT_BUFFER_PADDING_SIZE)))){ *output_max_size = 0; TSK_DEBUG_ERROR("Failed to allocate buffer"); return 0; } *output_max_size = r_size; } // re-wrap avpicture_fill((AVPicture *)self->dstFrame, (uint8_t*)*output, self->dstFormat, r_w, r_h); // pad sws_scale(self->rot.context, (const uint8_t* const*)self->rot.frame->data, self->rot.frame->linesize, 0, h, self->dstFrame->data, self->dstFrame->linesize); // crop self->dstFrame->data[0] = self->dstFrame->data[0] + (top_band * self->dstFrame->linesize[0]) + left_band; self->dstFrame->data[1] = self->dstFrame->data[1] + ((top_band >> y_shift) * self->dstFrame->linesize[1]) + (left_band >> x_shift); self->dstFrame->data[2] = self->dstFrame->data[2] + ((top_band >> y_shift) * self->dstFrame->linesize[2]) + (left_band >> x_shift); avpicture_layout((const AVPicture*)self->dstFrame, self->dstFormat, (int)_self->dstWidth, (int)_self->dstHeight, (unsigned char *)*output, (int)*output_max_size); } }//end of rotation return size; } //================================================================================================= // Video Converter object definition // static tsk_object_t* tdav_converter_video_ffmpeg_ctor(tsk_object_t * self, va_list * app) { tdav_converter_video_ffmpeg_t *converter = (tdav_converter_video_ffmpeg_t *)self; if(converter){ } return self; } static tsk_object_t* tdav_converter_video_ffmpeg_dtor(tsk_object_t * self) { tdav_converter_video_ffmpeg_t *converter = (tdav_converter_video_ffmpeg_t *)self; if(converter){ if(converter->context){ sws_freeContext(converter->context); } if(converter->srcFrame){ av_free(converter->srcFrame); } if(converter->dstFrame){ av_free(converter->dstFrame); } // Rotation if(converter->rot.context){ sws_freeContext(converter->rot.context); } if(converter->rot.frame){ av_free(converter->rot.frame); } if(converter->rot.buffer){ av_free(converter->rot.buffer); } } return self; } static const tsk_object_def_t tdav_converter_video_ffmpeg_def_s = { sizeof(tdav_converter_video_ffmpeg_t), tdav_converter_video_ffmpeg_ctor, tdav_converter_video_ffmpeg_dtor, tsk_null, }; const tsk_object_def_t *tdav_converter_video_ffmpeg_def_t = &tdav_converter_video_ffmpeg_def_s; /* plugin definition*/ static const tmedia_converter_video_plugin_def_t tdav_converter_video_ffmpeg_plugin_def_s = { &tdav_converter_video_ffmpeg_def_s, tdav_converter_video_ffmpeg_init, tdav_converter_video_ffmpeg_process }; const tmedia_converter_video_plugin_def_t *tdav_converter_video_ffmpeg_plugin_def_t = &tdav_converter_video_ffmpeg_plugin_def_s; #endif /* HAVE_FFMPEG || HAVE_SWSSCALE */