本文章旨在于个人记录参加GSoC2022的编程进展。
我参加了2022年的谷歌编程之夏,主要负责blender的VSE(视频编辑器)中的波形绘制优化。优化主要分为三个部分:

  • 多线程绘制,增加处理速度
  • 绘制时追踪playhead handle(视频播放头),优先绘制视频播放头的部分
  • 将波形数据缓存至.blender文件中,使得下一次加载时不再卡顿

以下是更多的技术细节,可能会比较混乱,因为是以笔记的形式记录的

Video Sequence Editor(VSE)的可能与项目相关的几个文件:

  • source/blender/sequencer/intern/strip_add.c添加strip(VSE序列)
  • source/blender/sequencer/intern/sound.cSound Strip实用功能,用于音频处理
  • source/blender/editors/space_sequencer/sequencer_draw.c绘制时间线和预览
  • source/blender/editors/space_sequence/sequencer_preview.c负责在sound strip中绘制waveform(波形)
    更多可以参考:VSE文件说明

在了解更多代码前,需要了解VSE界面的布局,以下是官网文档的一些图片
这个图像显示着Sequencer(序列编辑器)的布局。其中蓝色和绿色的条状组件便是Strip(序列)

关于什么是Editor,用户文档中有这样的描述。

Blender provides a number of different editors for displaying and modifying different aspects of data. An Editor is contained inside an Area which determines the size and placement of the editor with in the Blender window. Every area in Blender may contain any type of editor.
Blender提供了一系列不同编辑器来显示和修改不同类型的数据。编辑器被包含在决定着其在窗口中尺寸和布局的Area中。每一个area可以包含任意类型的编辑器


以上黄色框框包含的就是area,当然,也可以认为黄色方框内就是一个editor。

typedef struct Editing {/** Pointer to the current list of seq's being edited (can be within a meta strip). */ListBase *seqbasep;ListBase *displayed_channels;void *_pad0;/** Pointer to the top-most seq's. */ListBase seqbase;ListBase metastack;ListBase channels; /* SeqTimelineChannel */// more ...
} Editing;

这个Editing结构体保存着属于sequence core的数据。其中ListBase是一个链表结构体,seqbase是指向保存strips的一个链表

// The sequence structure is the basic struct used by any strip. each of the strips uses a different sequence structure.
typedef struct Sequence {struct Sequence *next, *prev;/** SEQ_NAME_MAXSTR - name, set by default and needs to be unique, for RNA paths. */char name[64];/** The length of the contents of this strip - before handles are applied. */int len;Strip *strip;/** The linked "bSound" object. */struct bSound *sound;void *scene_sound;float volume;// more...
} Sequence;

Sequence结构体储存着序列(Strip)属性。其中,Sequence.Strip定义了一些在序列中使用的数据和属性,包括序列中的图像和视频的地址等
这里面需要注意的是,Sequence并不是代表Sequencer的数据结构而是Strip的数据结构。文档上也给出了相应的说明

This is quite confusing part of sequencer code - What is usually called a strip is represented by data structure called Sequence. There is also data structure with name Strip which defines data and some properties used by Sequence.

比较需要关注的是bSound这个结构体,它与我的任务相关

typedef struct bSound {ID id;/*** The path to the sound file.*//** 1024 = FILE_MAX. */char filepath[1024];/*** The packed file.*/struct PackedFile *packedfile;/*** The handle for audaspace.*/void *handle;/*** Deprecated; used for loading pre 2.5 files.*/struct PackedFile *newpackedfile;struct Ipo *ipo;float volume;float attenuation;float pitch;float min_gain;float max_gain;float distance;short flags;/** Runtime only, always reset in readfile. */short tags;char _pad[4];double offset_time;/* Unused currently. */// int type;// struct bSound *child_sound;/*** The audaspace handle for cache.*/void *cache;/*** Waveform display data.*/void *waveform;/*** The audaspace handle that should actually be played back.* Should be cache if cache != NULL; otherwise its handle*/void *playback_handle;/** Spin-lock for asynchronous loading of sounds. */void *spinlock;/* XXX unused currently (SOUND_TYPE_LIMITER) *//* float start, end; *//* Description of Audio channels, as of eSoundChannels*/int audio_channels;int samplerate;} bSound;

里面的waveform需要我们进行关注,因为波形的绘制与waveform的处理息息相关。我们需要通过这个线索找到waveform的绘制过程
使用VS自带的查找所有引用功能,找到了不少关于waveform的引用,我们开始排查
可以看到,在最底下的waveform调用属于sequence_draw.c,这个文件的功能在上文提到的是绘制时间线和预览。因此,很有可能是我们的绘制波形的功能。我们先看它。
调用函数名称为draw_seq_waveform_overlay 从名字来看和波形绘制非常有关系,我们打断点测试。
从调用栈中可以看出,函数先绘制了一系列基础图像后,开始绘制时间线(timeline),然后绘制序列(strip),然后最后到达waveform的绘制。

 SoundWaveform *waveform = sound->waveform;/* Waveform could not be built. */if (waveform->length == 0) {return;}typedef struct SoundWaveform {int length;float *data;} SoundWaveform;

sound是一个bSound结构体指针,它的waveform数据被赋值到了SoundWaveform 这个结构体中。我们发现,它是一个具有长度和浮点数组指针的一个结构体。在调试窗口中可以发现,waveform->data确实是一个浮点数组。

这些浮点数组的意义是什么,我们暂且不考虑。我们进一步验证,将生成的浮点数据写入到txt文件中,需要的时候再读出来,观测波形的变化。

// 读取
{#  include "stdio.h"FILE *sound_file = fopen("C:\\Users\\xs\\Desktop\\datadispose\\1.txt", "r");int i = 0;int return_value = 0;if (sound_file != NULL) {fseek(sound_file, 0, SEEK_SET);while (return_value != -1) {fscanf_s(sound_file, "%f", &(waveform->data[i++]));waveform->data[i-1] /= 8;return_value = fscanf_s(sound_file, ",");}fclose(sound_file);waveform->length = i/3;state = 1;}else {// blender提供的音频读取并生成波形的函数waveform->length = AUD_readSound(sound->playback_handle, waveform->data, length, SOUND_WAVE_SAMPLES_PER_SECOND, stop);}}
// 写入
{#include "stdio.h"if (state == 0) {FILE *sound_file = fopen("C:\\Users\\xs\\Desktop\\datadispose\\1.txt", "w");int count = 0;for (int i = 0; i < waveform->length * 3; ++i) {if (i == waveform->length - 1)fprintf_s(sound_file, "%f", waveform->data[i]);elsefprintf_s(sound_file, "%f,", waveform->data[i]);count++;}fclose(sound_file);
}

上边代码的读取我做了一个手脚,我将读到的数据除以8,这样子对比前后波形可以更好的确定这个waveform的功能。

上图为除以8以后的波形,下图是使用blender默认加载函数得到的波形。两者可以看出很大的区别。因此,我们可以确定waveform确实是储存的波形数据。
值得一提的是,我的写入的for循环采用的是waveform->length * 3作为终止条件而不是waveform->length。这是因为我采用后者生成的波形只有总波形的大约1/3。

由此可以推测,应该是3个float数据对应的一个波形。下面,我们最重要的是了解这个浮点数如何对应的最终的波形。
第一个猜测是waveform本身就是代表了音频的数据。首先上网搜集一些资料,发现主流音频格式似乎都是用整形来储存音频数据的,但是也有一些资料说浮点数也可以表示音频数据
为什么通常的声音格式,每个采样点都是用整形? - 诗云的回答 - 知乎
为了更加确认这件事情,我们需要查看blender的代码。
Blender的波形读取代码是int AUD_readSound(AUD_Sound* sound, float* buffer, int length, int samples_per_second, short* interrupt),我们进入到函数内部去查看内部代码。

AUD_API int AUD_readSound(AUD_Sound* sound, float* buffer, int length, int samples_per_second, short* interrupt)
{float* buf;int len;float min, max, power, overallmax;bool eos;for(int i = 0; i < length; i++) {// more...len = floor(samplejump * (i+1)) - floor(samplejump * i);std::shared_ptr<IReader> reader = ChannelMapper(*sound, specs).createReader();aBuffer.assureSize(len * AUD_SAMPLE_SIZE(specs));buf = aBuffer.getBuffer();reader->read(len, eos, buf);max = min = *buf;power = *buf * *buf;for(int j = 1; j < len; j++){if(buf[j] < min)min = buf[j];if(buf[j] > max)max = buf[j];power += buf[j] * buf[j];}buffer[i * 3] = min;buffer[i * 3 + 1] = max;buffer[i * 3 + 2] = std::sqrt(power / len);if(overallmax < max)overallmax = max;if(overallmax < -min)overallmax = -min;if(eos){length = i;break;}if(overallmax > 1.0f){for(int i = 0; i < length * 3; i++){buffer[i] /= overallmax;}}return length;
}

以上是函数中比较关键的部分。从这里面的代码可以看出为什么之前需要使用waveform->length * 3作为终止条件以及每一组浮点数(三个浮点数)所代表的意义。
浮点数组以三个为一组,其中分别为最小值、最大值以及能量。从循环for(int j = 1; j < len; j++)中的循环体可以看出这些值是如何求出的。其中能量需要稍微说明一下。在信号与系统中,我们把能量表述为信号平方对于时间的积分。对于离散来说,这就意味着对信号平方进行求和。至于为什么能量表示为这样子,我们可以理解为对于自然界一些能量的抽象。具体可以参照奥本海姆的信号与系统这本书。

if(overallmax < max)overallmax = max;
if(overallmax < -min)overallmax = -min;if(overallmax > 1.0f)
{for(int i = 0; i < length * 3; i++){buffer[i] /= overallmax;}
}

overallmax取所有片段max和min绝对值中的最大值。如果overallmax大于1,则需要对于片段进行标准化。
除此之外,reader->read(len, eos, buf);涉及到工厂模式的一些知识,
(一周时间写到这就到了,因为笔者忙于期末考试所以内容较少,我们下周总结见[狗头])

GSoC 2022 Blender VSE: 第一周总结相关推荐

  1. GSoC 2022 Blender VSE: 第二、三周总结

    本文章旨在于个人记录参加GSoC2022的编程进展. 工厂模式解读 我们继续上一周提到的关于reader的工厂模式的调用.关于工厂模式,我看到过一篇非常好的文章.它详细讲述了简单工厂模式.工厂方法以及 ...

  2. 2022年程序员开工第一周,应该收藏这样一份书单

    新年新气象,开工第一周,作为程序员的你有哪些新年规划?如果还没准备好,不如小编来一份开工书单,开始你的学习旅程吧! 开工后,有哪些新书最受欢迎? 1.计算之魂 (<数学之美><浪潮之 ...

  3. 【猿哥学二建】第一周 考试准备

    [猿哥学二建]第一周 考试准备 自从计划考证以来,花费了两周时间了解二级建造师的相关知识.互联网上有很多针对考生的经验和建议,自行百度即可.本人根据这两周从互联网了解资料大概估算,类似于本人这种上班族 ...

  4. vulhub打靶第一周

    title: vulhub打靶第一周 description: vulhub打靶第一周 难度:middle date: 2023-06-01 categories: [渗透,靶机] 图片链接有问题访问 ...

  5. 吴恩达深度学习第四课第一周 卷积神经网络

    文章目录 前言 一.计算机视觉(引言) 二.边缘检测示例(过滤器) 三.更多边缘检测内容(由亮到暗还是由暗到亮?) 四.Padding(Valid.Same.p) 五.卷积步长(s) 六.三维卷积(通 ...

  6. 靓仔2022年的第一篇程序人生及年度总结

    文章目录 靓仔2022年的第一篇程序人生 回顾 程序员是什么职业? 对大数据的看法 程序员的职业规划 给大家的建议 未来文章风格 心情 靓仔2022年的第一篇程序人生 回顾   从2021年12月至2 ...

  7. 第一周Access课总结

    第一周Access课总结 1:这节课学到了什么? 这节课重点学了数据库是用来干什么 做什么的  老师怕我们理解不了 用了很长时间向我们举了很多的例子 让我们终于知道了数据库是用来干嘛的了 顾名思义 数 ...

  8. 20162313苑洪铭 第一周作业

    20162313苑洪铭 20016-2017-2 <程序设计与数据结构>第1周学习总结 教材学习内容总结 本周观看教材绪论 主要在教我建立一个简单的java程序 内容是林肯的名言 虽然看起 ...

  9. 大三下学期第一周总结

    本周以是开学第一周了,在生活方面,生活琐事确实变多了起来.每天上课,看着老师熟悉的面庞,如履春风.感觉学习没有那么多的陌生恐惧.学习是一方面,身体锻炼不能落下.一周至少保证三小时及其以上的运动.身体是 ...

最新文章

  1. Linux那些事儿 之 戏说USB(22)设备的生命线(五)
  2. python for语句_Python循环语句
  3. 算法-Valid Anagram
  4. 循序渐进linux——基础知识、服务器搭建、系统管理、性能调优、集群应用_第四讲,Proxmox部署与应用...
  5. 查询学生选修课程管理系统java_JAVA数据库课程设计学生选课管理系统的
  6. Object to XML
  7. 透彻!博士生成长需要经历的7道门
  8. mysql 日期对比,varchar类型装换为datetime类型
  9. 微信支付,银联支付,支付宝支付——三大支付总结
  10. COM的八个经验和教训
  11. 数据--第26课 - 排序的概念及分类
  12. visio画图导入word公式符号发生变形
  13. PC端淘宝小程序开发记录
  14. 「秘」那些管UI小姐姐要来的网站
  15. CentOS 6.3安装 flash控件成功案例(其它方法未成功)
  16. 二、MySQL 介绍及 MySQL 安装与配置
  17. 周浩正:写给编辑人的信 从“紫牛”说起
  18. 新开通了我的CSDN博客,写个处女篇
  19. 计算机word论文,怎么用电脑Word写论文?
  20. 了解【泰科】协作机器人产品资料大全

热门文章

  1. 考研政治|分析大题复习攻略
  2. 成功解决:ping不通腾讯云服务器问题
  3. matlab实现注册账号,创建账户
  4. 弥散张量成像(diffusion tensor imaging,DTI)常用指标
  5. 大数据时代对存储发展需要哪些要求
  6. linux开机运行级别和关机命令总结
  7. MySQL数据库教程之七:MySQL编程基础
  8. nuget 的生成、发布、使用和更新
  9. 电口模块和光模块有什么区别?
  10. OSChina 周日乱弹 ——拆散她们,帮她们过节!