项目场景:

最近学习使用ffmpeg在做一个视频缩略图的例子。在这里做一个记录。 在网上找了很多资料,加边框都是更改图片外围数据,这样图片会缺失一部分,不太符合我的需求。 按照这个思路,下面是一种实现在原始图片的基础上,在外部添加一个框。

参考大神的文章:https://blog.csdn.net/leixiaohua1020/article/details/15811977


需求描述:

  • 需要获取指定时间点处的视频缩略图。
  • 缩略图需要保持原有的长宽比列,缩放至480x360,缺失的部分使用纯色填充。

实现计划:

  1. 根据视频的分辨率和要求的480x360计算处,缩放后的视频分辨率。
  2. 视频需要解复用并且解码视频,使用ffmpeg直接软解。
  3. 视频需要缩放,考虑过avfilter过滤器,但是部分图片缩放后会花屏【还没有找到问题原因】。暂时先使用swscale来实现。
  4. 拿到缩放后的数据,分辨率可能不完全等于480x360,这个时候需要在数据外部填充一些纯色。

具体实现:

1.计算缩放后的视频分辨率

//视频原始大小
m_VideoWidth = pCodecCtx->width;
m_VideoHeight = pCodecCtx->height;//calculate
//m_width 和 m_height 是用户规定的大小,这里设置为480x360
//m_SwsWidth 和 m_SwsHeight是视频缩放后的大小
if (m_VideoWidth <= m_width && m_VideoHeight <= m_height)
{m_SwsWidth  = m_VideoWidth;m_SwsHeight = m_VideoHeight;
}else if(m_VideoWidth <= m_width && m_VideoHeight > m_height)
{float cc_h = (double)m_VideoHeight / m_height;m_SwsHeight = m_height;m_SwsWidth  = m_VideoWidth / cc_h;
}else if(m_VideoWidth > m_width && m_VideoHeight <= m_height)
{float cc_w = (double)m_VideoWidth / m_width;m_SwsWidth  = m_width;m_SwsHeight = m_VideoHeight / cc_w;
}else{//m_VideoWidth > m_width && m_VideoHeight > m_heightfloat cc_w = (double)m_VideoWidth / m_width;float cc_h = (double)m_VideoHeight / m_height;if(cc_w >= cc_h){m_SwsWidth  = m_width;m_SwsHeight = m_VideoHeight / cc_w;}else{m_SwsHeight = m_height;m_SwsWidth  = m_VideoWidth / cc_h;}
}printf("video:width = %d, height = %d.\n", m_VideoWidth, m_VideoHeight);
printf("user:width = %d, height = %d.\n", m_width, m_height);
printf("SWS:width = %d, height = %d.\n", m_SwsWidth, m_SwsHeight);

2.由于我使用的是ffmpeg 4.3.2版本,所以解码过程如下:

AVPacket packet;
AVFrame *frame;frame = av_frame_alloc();/* read all packets */while (1) {if ((ret = av_read_frame(fmt_ctx, &packet)) < 0)break;if (packet.stream_index == video_stream_index) {ret = avcodec_send_packet(dec_ctx, &packet);if (ret < 0) {av_log(NULL, AV_LOG_ERROR, "Error while sending a packet to the decoder\n");break;}while (ret >= 0) {ret = avcodec_receive_frame(dec_ctx, frame);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {break;} else if (ret < 0) {av_log(NULL, AV_LOG_ERROR, "Error while receiving a frame from the decoder\n");goto end;}//这里我们拿到了一包解码后的数据frame.av_frame_unref(frame);}}av_packet_unref(&packet);}

3.使用swscale进行格式转换和缩放。


AVFrame *pRgba = av_frame_alloc();int video_size = av_image_get_buffer_size(AV_PIX_FMT_RGBA, m_SwsWidth, m_SwsHeight, 1);
uint8_t * picture_buf = (uint8_t *)av_malloc(video_size);
//picture_buf 指向pRgba的data
av_image_fill_arrays(pRgba->data, pRgba->linesize, picture_buf, AV_PIX_FMT_RGBA, m_SwsWidth, m_SwsHeight, 1);
//视频像素数据格式转换上下文
SwsContext *sws_ctx = sws_getContext(dec_ctx->width, dec_ctx->height, dec_ctx->pix_fmt, /*INPUT  W H FMT*/m_SwsWidth, m_SwsHeight, AV_PIX_FMT_RGBA,                 /*OUTPUT W H FMT*/SWS_BICUBIC,                                              /*拉伸算法*/NULL, NULL, NULL);                                        /*其他参数*/
//将AVFrame转成视频像素数YUV420P格式
sws_scale(sws_ctx, (const uint8_t * const *)frame->data, frame->linesize,0, dec_ctx->height, pRgba->data, pRgba->linesize);
sws_freeContext(sws_ctx);
av_frame_free(&pRgba);

4.拿到picture_buf后,需要进行判断并填充。测试视频为3840x2160
了解rgba数据格式,参考:[FFmpeg] RGBA 和 YUV 存储方式

  • m_width == m_SwsWidth && m_height == m_SwsHeight:
    这个是最简单的,不需要任何处理。

  • m_width == m_SwsWidth && m_height > m_SwsHeight:
    宽度相同,但是高度不匹配,需要对上下分别填充 m_width * (m_height - m_SwsHeight)/2 边框。

/*** -------------* |    top    |* -------------* -------------* |    pic    |* -------------* -------------* |   buttom  |* -------------*/
int diff_h       = m_height - m_SwsHeight;
int diff_top     = diff_h/2;
int diff_buttom  = diff_h - diff_top; //这里是防止diff_h 是一个奇数。int pos = 0; //用来标记当前行的位置
//create top
for(int i = 0; i < diff_top; i++)
{pos = i * m_width * 4;for(int j  = 0; j < m_width; j++){m_picture_buf2[pos + j * 4 + 0] = 235;//Rm_picture_buf2[pos + j * 4 + 1] = 142;//Gm_picture_buf2[pos + j * 4 + 2] = 85;//Bm_picture_buf2[pos + j * 4 + 3] = 0;//A 0:完全透明  ===>>  1:完全不透明}
}
//original buf
memcpy(picture_buf2 + diff_top * m_width * 4, picture_buf, m_SwsWidth * m_SwsHeight * 4);//create buttom
for(int i = diff_top + m_SwsHeight; i < m_height; i++)
{pos = i * m_width * 4;for(int j = 0; j < m_width; j++){m_picture_buf2[pos + j * 4 + 0] = 235;//Rm_picture_buf2[pos + j * 4 + 1] = 142;//Gm_picture_buf2[pos + j * 4 + 2] = 85;//Bm_picture_buf2[pos + j * 4 + 3] = 0;//A 0:完全透明  ===>>  1:完全不透明}
}

原图缩放后:

加上边框后:

  • m_width > m_SwsWidth && m_height == m_SwsHeight:
    高度相同,但是宽度不匹配,需要对左右分别填充 (m_width - m_SwsWidth )/2 * m_height 边框。
/*** |--left--||--pic--||--right--|*/
int diff_w      = m_width - m_SwsWidth;
int diff_left   = diff_w/2;
int diff_right  = diff_w - diff_left;
int pos         = 0;//原图的位置
int pos_line    = 0;//行位置for(int i = 0; i < m_height; i++)
{pos_line = i * m_width * 4;for(int j = 0; j < m_width; j++){if(j < diff_left || j >= (diff_left + m_SwsWidth)){m_picture_buf2[pos_line + j * 4 + 0] = 235; //Rm_picture_buf2[pos_line + j * 4 + 1] = 142; //Gm_picture_buf2[pos_line + j * 4 + 2] = 85;  //Bm_picture_buf2[pos_line + j * 4 + 3] = 0;   //A}else{m_picture_buf2[pos_line + j * 4 + 0] = picture_buf[pos ++];//Rm_picture_buf2[pos_line + j * 4 + 1] = picture_buf[pos ++];//Gm_picture_buf2[pos_line + j * 4 + 2] = picture_buf[pos ++];//Bm_picture_buf2[pos_line + j * 4 + 3] = picture_buf[pos ++];//A}}
}
  • m_width > m_SwsWidth && m_height > m_SwsHeight:
    宽度和高度不匹配,需要对上下m_width * (m_height - m_SwsHeight)/2 边框,左右 (m_width - m_SwsWidth )/2 * m_SwsHeight。
/*** |----------------------top-----------------------|* |--left--||------------pic------------||--right--|* |-------------------buttom-----------------------|*/
//top & buttom 's height
int diff_h      = m_height - m_SwsHeight;
int diff_top    = diff_h/2;
int diff_buttom = diff_h - diff_top;
//left & right 's width
int diff_w      = m_width - m_SwsWidth;
int diff_left   = diff_w/2;
int diff_right  = diff_w - diff_left;
int pos         = 0;
int pos_line    = 0;for(int i = 0; i < m_height; i++)
{pos_line = i * m_width * 4;for(int j = 0; j < m_width; j++){if(j < diff_left || j >= (diff_left + m_SwsWidth) || i < diff_top || i >= (diff_top + m_SwsHeight)){m_picture_buf2[pos_line + j * 4 + 0] = 235; //Rm_picture_buf2[pos_line + j * 4 + 1] = 142; //Gm_picture_buf2[pos_line + j * 4 + 2] = 85;  //Bm_picture_buf2[pos_line + j * 4 + 3] = 0;   //A}else{m_picture_buf2[pos_line + j * 4 + 0] = picture_buf[pos ++];//Rm_picture_buf2[pos_line + j * 4 + 1] = picture_buf[pos ++];//Gm_picture_buf2[pos_line + j * 4 + 2] = picture_buf[pos ++];//Bm_picture_buf2[pos_line + j * 4 + 3] = picture_buf[pos ++];//A}}
}

学习记录:RGBA格式数据加边框相关推荐

  1. MySQL学习记录 (二) ----- SQL数据查询语句(DQL)

    相关文章: <MySQL学习记录 (一) ----- 有关数据库的基本概念和MySQL常用命令> <MySQL学习记录 (二) ----- SQL数据查询语句(DQL)> &l ...

  2. MySQL学习记录 (三) ----- SQL数据定义语句(DDL)

    相关文章: <MySQL学习记录 (一) ----- 有关数据库的基本概念和MySQL常用命令> <MySQL学习记录 (二) ----- SQL数据查询语句(DQL)> &l ...

  3. 小样本学习记录————利用所有数据的元学习Few-shot Text Classification with Distributional Signatures

    小样本学习记录----利用所有数据的元学习Few-shot Text Classification with Distributional Signatures 在计算机视觉中,低水平的模式是可以跨学 ...

  4. VQA任务学习记录1(附数据使用代码记录)

    0.前言 最近需要学习处理VQA任务特此记录,这个主要是对论文bottom-up and top-down()和bilinear attention network()中的代码部分的学习记录,目前也并 ...

  5. RPLIDAR思岚雷达学习记录--4--雷达数据实时保存

    数据持续输出测试 第三节中最后虽然可以读取到雷达的角度和距离数据,但是每次后面读取的数据都会覆盖之前的数据,因此尝试使用动态的文件名,这样每次保存数据都会使用不同的文件名,数据就不会被覆盖.决定使用当 ...

  6. pytorch学习(一)数据加载之前的预处理(UCSD数据集)

    最近在做有关视频异常检测方面的实验,需要用到UCSD数据集,pytorch自定义加载自己的数据集时需要将自己的数据的路径以及标签存放到txt文档中,方便后续的数据加载. 最后我会给出生成好的UCSD数 ...

  7. ROS学习-记录和回放数据

    本博客将介绍怎么使用ROS系统来记录数据到一个.bag文件中,然后回放数据,在运行系统中产生类似的效果. 记录数据(创建一个bag文件) 从一个运行的ROS系统中记录topic数据,并存储到bag文件 ...

  8. ios网络学习------8 xml格式数据的请求处理 用代码块封装

    #pragma mark 载入xml - (void)loadXML {//获取网络数据.NSLog(@"load xml");//从webserver载入数据NSString * ...

  9. PCL学习记录--点云数据的获取与可视化

    1.获取点云数据 (1)点云定义 pointCloud = std::make_shared<pcl::PointCloud<pcl::PointXYZ>>(); (2)获取相 ...

最新文章

  1. 【运营】盘点2014,有哪些O2O名牌被撕。
  2. MIT黑科技:全新芯片将语音识别功耗降低99%
  3. python 中反斜杠在字符串过长的正确用法
  4. CodeForces - 1494E A-Z Graph(构造+思维)
  5. 前端学习(2179):vue-router-router的由来和vue-router
  6. hdu 2149 巴什博弈
  7. 西安邮电大学卓越班c语言面试题,西安邮电大学C语言实验报告.docx
  8. 函数的参数个数是不固定_EXCEL这些序号技巧,你还真不一定都知道
  9. Java基础学习总结(51)——JAVA分层理解
  10. Moddable SDK为物联网开发提供JavaScript引擎:不到32KB
  11. 实验五 编写、调试具有多个段的程序
  12. ScrollView嵌套GridView,自定义Gridview动态设置Item的高度,屏幕适配
  13. Sublime Text2使用教程
  14. Python可视化-WordCloud生成云词图片
  15. 数据库查询语句遇到:Unknown column 'XXXX' in 'where clause'解决方法
  16. c#:list转datatable;xtraReport打印
  17. Python+PyCharm的一些基本设置:安装使用、注册码、显示行号、字体大小和快捷键等常用设置...
  18. 机器人运动学、动力学基础上利用MATLAB进行PID控制仿真
  19. MAC修改印象笔记背景色
  20. mysql如何创建组合索引

热门文章

  1. 我的世界服务器修改神兽几率,我的世界神奇宝贝mod神兽刷新率调整方法 神兽刷新率怎么增加...
  2. unittest+tomorrow+BeautifulReport实现自动化测试的多线程
  3. 前端常用方法之“array.reduc()”
  4. 中国IT研发实力最强的城市分析(转载)
  5. java+ElementUI前后端分离旅游项目第三天 预约管理
  6. python类中包含一个特殊的变量、它可以访问类的成员_区域联防的运用中遵循并贯彻以球为主的防守原则,做到球人区三者兼顾。( )...
  7. 阿里插件机制android,Android插件 - 阿里規約 Alibaba Java Coding Guidelines
  8. python设置背景图片大全_Python实例讲解 - tkinter canvas (设置背景图片及文字)
  9. 热插拔+远程控制,同为(TOWE)智能桌面PDU产品APZ-1026HR
  10. RPC(Remote Procedure Call)框架详解