用手机或者平板拍摄的视频文件,很可能是旋转的,比如分辨率是1280x720,确是垂直的,相当于分辨率变成了720x1280,如果不做旋转处理的话,那脑袋必须歪着看才行,这样看起来太难受,所以一定要想办法解析到视频的旋转角度,然后根据这个角度重新绘制。在窗体那边也需要调整对应的分辨率,一般都是宽度高度互换。其实早期的很多播放器比如vlc2版本的播放器也是不支持旋转的,从vlc3开始内置会自动给旋转,估计这种场景越来越多,毕竟现在智能手机大行其道,用手机拍摄的视频很多都是竖屏的。
在ffmpeg中旋转frame帧数据,有多种方式,方式一是直接通过运算逐行取出数据,重新组织旋转后的视频帧数据;方式二通过滤镜来实现。最开始还没学会用ffmpeg的滤镜的时候,用的就是方式一,通俗易懂,但是很傻,尤其是运算很占CPU,毕竟for循环来个很多次挨个取数据又重新组织数据。自从学会用ffmpeg滤镜以后,从滤镜大全中找到了居然也支持旋转,而且任意角度旋转都可以,甚至镜像操作,这就非常强大了,马上将这个架构的旋转部分全部换成了滤镜旋转,连之前用于旋转的中间过渡帧数据变量都不需要,代码更精简,功能更强大,拓展性更好,这其实就是一个不断精进迭代的过程,第一步解决从无到有的过程,后面才是持续不断的完善。
int FFmpegFilter::initFilter(AbstractVideoThread *thread, AVStream *stream, AVCodecContext *avctx, FilterData &filterData)
{int result = -1;if (!filterData.enable) {return result;}//貌似硬解码不支持滤镜if (filterData.formatIn == AV_PIX_FMT_NV12) {return result;}//先释放相关资源freeFilter(filterData);//获取滤镜字符串QString filters = getFilter(filterData);if (filters.isEmpty()) {return result;}//输入帧序列的参数信息QStringList listArg;listArg << QString("video_size=%1x%2").arg(avctx->width).arg(avctx->height);listArg << QString("pix_fmt=%1").arg(avctx->pix_fmt);listArg << QString("time_base=%1/%2").arg(stream->time_base.num).arg(stream->time_base.den);listArg << QString("pixel_aspect=%1/%2").arg(avctx->sample_aspect_ratio.num).arg(avctx->sample_aspect_ratio.den);QString args = listArg.join(":");//输入帧格式enum AVPixelFormat pix_fmts[] = {filterData.formatIn, AV_PIX_FMT_NONE};//获取要使用的滤镜const AVFilter *filterSrc = avfilter_get_by_name("buffer");const AVFilter *filterSink = avfilter_get_by_name("buffersink");//创建输入输出滤镜参数AVFilterInOut *inputs = avfilter_inout_alloc();AVFilterInOut *outputs = avfilter_inout_alloc();//创建滤镜容器filterData.filterGraph = avfilter_graph_alloc();if (!inputs || !outputs || !filterData.filterGraph) {result = AVERROR(ENOMEM);goto end;}//创建输入滤镜result = avfilter_graph_create_filter(&filterData.filterSrcCtx, filterSrc, "in", args.toUtf8().constData(), NULL, filterData.filterGraph);if (result < 0) {thread->debug("滤镜处理", QString("创建输入滤镜失败: %1").arg(FFmpegHelper::getError(result)), "");goto end;}//创建输出滤镜result = avfilter_graph_create_filter(&filterData.filterSinkCtx, filterSink, "out", NULL, NULL, filterData.filterGraph);if (result < 0) {thread->debug("滤镜处理", QString("创建输出滤镜失败: %1").arg(FFmpegHelper::getError(result)), "");goto end;}//设置输出滤镜格式result = av_opt_set_int_list(filterData.filterSinkCtx, "pix_fmts", pix_fmts, filterData.formatOut, AV_OPT_SEARCH_CHILDREN);if (result < 0) {thread->debug("滤镜处理", QString("设置输出滤镜格式: %1").arg(FFmpegHelper::getError(result)), "");goto end;}//设置滤镜的参数outputs->name = av_strdup("in");outputs->filter_ctx = filterData.filterSrcCtx;outputs->pad_idx = 0;outputs->next = NULL;inputs->name = av_strdup("out");inputs->filter_ctx = filterData.filterSinkCtx;inputs->pad_idx = 0;inputs->next = NULL;//初始化滤镜result = avfilter_graph_parse_ptr(filterData.filterGraph, filters.toUtf8().constData(), &inputs, &outputs, NULL);if (result < 0) {thread->debug("滤镜处理", QString("初始化滤镜失败: %1").arg(FFmpegHelper::getError(result)), "");goto end;}//应用滤镜配置result = avfilter_graph_config(filterData.filterGraph, NULL);if (result < 0) {thread->debug("滤镜处理", QString("应用滤镜配置失败: %1").arg(FFmpegHelper::getError(result)), "");goto end;}end://释放对应的输入输出avfilter_inout_free(&inputs);avfilter_inout_free(&outputs);filterData.isOk = (result >= 0);return result;
}void FFmpegFilter::freeFilter(FilterData &filterData)
{if (filterData.isOk) {filterData.enable = true;filterData.init = true;filterData.isOk = false;avfilter_free(filterData.filterSrcCtx);avfilter_free(filterData.filterSinkCtx);avfilter_graph_free(&filterData.filterGraph);filterData.filterSrcCtx = NULL;filterData.filterSinkCtx = NULL;filterData.filterGraph = NULL;}
}