webrtc音频处理及发送流程

上篇文章写到了音频的采集过程,这里继续,当采集到音频数据的一个buffer之后,需要对该音频数据做相应的处理,比如降频、编码、打包等等,完成之后在发送给网络模块进行传输。

下图是基本流程结构,描述了一个音频包产生到发送需要经过的主要过程,后续再对其中的每个过程进行详细分析
音频处理流程

整体过程以Channel为中心,调用了混合解析模块(Transmixer)、音频编码模块(AudioCoding)、RTP模块(RtpRtcp)
每个模块对音频做对应的信息处理,完成之后,再通过callback返回给Channel,最终通过Transport将一个rtp包通过udp进行传输。

webrtc录音流程

上一篇文章介绍了webrtc在录音的时候,java层做的一些事情,那几个java文件,是通过底层C++代码进行调用的。

这篇文章提供了一张基本的底层C++调用的流程图

recordflow

重点介绍的是AudioDeviceModule模块,它负责实现不同平台下的音频处理对象,对于Android平台而言,它通过C++的模板机制,创建了AudioRecordJniAudioTrackJni两个对象,而这两个对象,分别对java层的对应两个文件进行调用和相互交互。

webrtc录音初始化

webrtc中Android的音频采集,是在java层实现的,通过调用系统的AudioRecord进行音频录制操作,默认采集的音频是原始的PCM格式数据。

webrtc源码中提供了对应的java代码实现

WebRtcAudioRecord.java
WebRtcAudioManager.java
WebRtcAudioTrack.java
......

这几个文件,并不是提供个java代码调用的,而是给底层C模块调用的,对应的调用过程后续补充。这里描述一下这几个java文件做的事情。

底层初始化录音的时候,会先实例化AudioManager,这个文件负责录音、播放的一些基本管理,或者android设备的硬件信息等,过程如下:

1. 设置录音通道
android支持双声道立体声(CHANNEL_IN_STEREO)和单声道(CHANNEL_IN_MONO),虽然底层C已经支持了这两种通道,但是java层目前只实现了MONO单通道,于是设置了

CHANNEL = 1;

2. 设置采样频率
至于采样频率的一些基本概念,网上很多,可以参考android中AudioRecord使用这篇文章
然而,对于android设备而言,设置正确的采样频率是很关键的,不同的机型对采样频率有自己的限制和要求。
webrtc默认设置的采样频率是44.1khz

    // Use 44.1kHz as the default sampling rate.
    private static final int SAMPLE_RATE_HZ = 44100;

虽然如此,在获取设备支持的采样频率的时候,还是做了一些判断的,


  private int getNativeOutputSampleRate() {
      // Override this if we're running on an old emulator image which only
      // supports 8 kHz and doesn't support PROPERTY_OUTPUT_SAMPLE_RATE.
      if (WebRtcAudioUtils.runningOnEmulator()) {
          Logd("Running on old emulator, overriding sampling rate to 8 kHz.");
          return 8000;
      }
      if (!WebRtcAudioUtils.runningOnJellyBeanMR1OrHigher()) {
          return SAMPLE_RATE_HZ;
      }
      String sampleRateString = audioManager.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);
      return (sampleRateString == null) ?
              SAMPLE_RATE_HZ : Integer.parseInt(sampleRateString);
  }

这段代码的逻辑是:
1. 如果是在模拟器上跑,设置采样频率是8khz
2. 如果在API 17及以下的系统跑,则设置为44.1khz
3. 如果在API 17以上的系统跑,通过调用系统支持的API获取当前设备支持的采样频率

我在测试过程中发现,这样简单粗暴的判断是不够的,我在测试HUAWEI C8813机器的时候,发现狂crash,因为该设备不支持44.1KHZ的采样频率,导致AudioRecord创建失败,该设备只支持8KHZ的频率。考虑到还有很多设备会有这种情况,不同的设备可能支持不同的采样频率,所以不好针对某些机器做单独设置,最后,重写了该方法,通过遍历所有可能的采样频率,来获取支持的数


    private int getNativeOutputSampleRate() {
        List<Integer> rates = Arrays.asList(new Integer[]{SAMPLE_RATE_HZ, 8000});
        for (int i=0; i<rates.size(); i++) {
            try {
                int bufferSize = AudioRecord.getMinBufferSize(rates.get(i), AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);
                if (bufferSize != AudioRecord.ERROR_BAD_VALUE) {
                    AudioRecord record = new AudioRecord(MediaRecorder.AudioSource.VOICE_COMMUNICATION,
                            rates.get(i), AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize);
                    if (record != null && record.getState() == AudioRecord.STATE_INITIALIZED) {
                        record.release();;
                        return rates.get(i);
                    }
                    record.release();
                    record = null;
                }
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            }

        }

        return SAMPLE_RATE_HZ;
    }

该方法,通过遍历所有可能的采样频率(目前只写了两个,应该还有很多),然后试图用该频率来创建AudioRecord,保证初始化成功。
对于语音采样:
8,000 Hz – 电话所用采样率, 对于人的说话已经足够
11,025 Hz
22,050 Hz – 无线电广播所用采样率
32,000 Hz – miniDV 数码视频 camcorder、DAT (LP mode)所用采样率
44,100 Hz – 音频 CD, 也常用于 MPEG-1 音频(VCD, SVCD, MP3)所用采样率
47,250 Hz – Nippon Columbia (Denon)开发的世界上第一个商用 PCM 录音机所用采样率
48,000 Hz – miniDV、数字电视、DVD、DAT、电影和专业音频所用的数字声音所用采样率
50,000 Hz – 二十世纪七十年代后期出现的 3M 和 Soundstream 开发的第一款商用数字录音机所用采样率
50,400 Hz – 三菱 X-80 数字录音机所用所用采样率
96,000 或者 192,000 Hz – DVD-Audio、一些 LPCM DVD 音轨、Blu-ray Disc(蓝光盘)音轨、和 HD-DVD (高清晰度 DVD)音轨所用所用采样率
2.8224 MHz – SACD、 索尼 和 飞利浦 联合开发的称为 Direct Stream Digital 的 1 位 sigma-delta modulation 过程所用采样率。

3. 判断设备是否支持AEC(Acoustic Echo Canceller 声学回声消除)
webrtc提供了一个不支持AEC的设备黑名单

// List of devices where it has been verified that the built-in AEC performs
// bad and where it makes sense to avoid using it and instead rely on the
// native WebRTC AEC instead. The device name is given by Build.MODEL.
private static final String[] BLACKLISTED_AEC_MODELS = new String[] {
        "Nexus 5", // Nexus 5
        "D6503",   // Sony Xperia Z2 D6503
};

在API 16及以上的系统中,提供了对应检测设备是否支持AEC的方法

AcousticEchoCanceler.isAvailable();

4. 设备是否支持LowLatency(低延迟音频输出)
关于什么是低延迟音频输出以及OpenSLES,可以在网上搜索相关资料,这里不提

5. 获取每个音频buffer的size

在获取到这些参数之后,将这些参数设置到C层的AudioRecordJni中进行保存,待底层数据处理

———- 邪恶的分割线 ————–

上面AudioManager初始化完成之后,就会开始初始化音频录制部分,即初始AudioRecord对象,这里重点介绍对应的一些参数设置

首先是采样频率,上面提到了,默认是44.1khz,即1秒钟采集44100帧。
对应PCM的原始数据,每帧占用16bit即2个字节

    // Default audio data format is PCM 16 bit per sample.
    // Guaranteed to be supported by all devices.
    private static final int BITS_PER_SAMPLE = 16;

在音频采集的过程中,我们应该采集多大的量作为一个包来发送呢?webrtc里头设置的是10ms,即每10ms采集到的音频帧作为一个buffer

    // Requested size of each recorded buffer provided to the client.
    private static final int CALLBACK_BUFFER_SIZE_MS = 10;

于是1秒钟就采集100个buffer,每个buffer里头有44100/100 = 441帧,占用441x(16/8)字节(bytes)
接着开始初始化一个byteBuffer,用来存储音频数据,该buffer的size即是882字节长度,由于该buffer是在java层创建的,为了让C层可以访问到里头的数据,同时不要通过jni的方式来访问(这样频繁访问会消耗性能),将byteBuffer对象存储到C层中。

然后初始化AudioRecord实例,然后等待C层开始调用StartRecording()

try {
audioRecord = new android.media.AudioRecord(MediaRecorder.AudioSource.VOICE_COMMUNICATION,
sampleRate,
AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT,
bufferSizeInBytes);

} catch (IllegalArgumentException e) {
Logd(e.getMessage());
return -1;
}
assertTrue(audioRecord.getState() == android.media.AudioRecord.STATE_INITIALIZED);
[/code]

———- 邪恶的分割线(又来) ————–

录音的过程,在一个子线程中运行,当C层调用StartRecording()的时候,启动录音

    private boolean StartRecording() {
        Logd("StartRecording");
        assertTrue(audioRecord != null);
        assertTrue(audioThread == null);
        try {
            audioRecord.startRecording();
        } catch (IllegalStateException e) {
            Loge("AudioRecord.startRecording failed: " + e.getMessage());
            return false;
        }
        if (audioRecord.getRecordingState() != android.media.AudioRecord.RECORDSTATE_RECORDING) {
            Loge("AudioRecord.startRecording failed");
            return false;
        }
        audioThread = new AudioRecordThread("AudioRecordJavaThread");
        audioThread.start();
        return true;
    }

录音的过程在一个while中循环进行,当读到了882字节之后,告诉C层一个buffer的数据已经Ready,可以拿去xx滴干活了

while (keepAlive) {
    int bytesRead = audioRecord.read(byteBuffer, byteBuffer.capacity());
    if (bytesRead == byteBuffer.capacity()) {
        nativeDataIsRecorded(bytesRead, nativeAudioRecord);
    } else {
        if (bytesRead == android.media.AudioRecord.ERROR_INVALID_OPERATION) {
            keepAlive = false;
        }
    }
}

音频采集的java层,基本就是这样

webrtc初探

最近在折腾webrtc,做实时语音通信服务。

首先当然是下载和编译webrtc的代码了,最新版本的webrtc被整合到了chromium项目中了,导致整个工程有6.5G那么大,更不幸的是,官网的下载方式,被墙了……无奈买了一个高级的代理,下了2天时间,才成功的把所有代码下载下来了。

接着才发现,下载的问题就是小儿科,真正难的是如何编译……对系统的依赖太大,我在mac上跑了一周都没能编译成功,无奈最后转向了ubuntu,在安装了多个依赖之后,终于成功比编译通过了。

基本编译过程

1. 安装depot_tools

$ git clone https://chromium.goolesource.com/chromium/tools/depot_tools.git

添加PATH

$ export PATH=`pws`/depot_tools:"$PATH"

2. 安装JDK,并设置环境变量

$ export JAVA_HOME=<path to OpenJDK 7>
$ export GYP_DEFINES="OS=android"

然后就可以开始下载代码了,最新的webrtc已经整合到chromium中了,所以不再从googlecode中下载了,网上很多文章都比较旧了,都写的从http://webrtc.googlecode.com/svn/trunk中下载代码,最新地址是 https://chromium.googlesource.com/external/webrtc.git

$ fetch webrtc_android

然后你就可以把电脑扔一边,祈祷vpn不要断了,过几天后在回来吧

3. 编译
墙裂推荐使用ubuntu进行编译,否则就是自己找虐,即便如此,也需要安装许多系统依赖的库

libxtst-dev
libxss-dev
libudev
libdbus-1
libconfig2.0
gnome-keyring-1.pc
libpci.pc
libasound2-dev
mesa-common-dev
libpulse-dev
libgl1-mesa-dev

还有别忘了装ant编译环境

$ apt-get install -y ant

接着就可以顺畅的进行编译了

export GYP_DEFINES="$GYP_DEFINES java_home=/usr/lib/jvm/java-7-openjdk-amd64 OS=android"
export GYP_GENERATOR_FLAGS="$GYP_GENERATOR_FLAGS output_dir=out_jni"
gclient runhooks
ninja -C out_android/Debug AppRTCDemo

这样可以编译出默认的AppRTCDemo.apk出来

CC2015下安装插件的正确方式

前段时间Adobe发布了新一代的系列产品CC2015(尼玛更新要不要别这么快,2014还没用熟悉……)
不过PS2015确实新增了很多不错的特性,小伙伴们都打着鸡血:升级升级升级升级升级!

兴奋的安装完撸一把UI之后,发现尼玛插件全没了有木有!!!!
那时候的心情就跟这几天的股市一样一样滴,没有了Cutterman,还怎么切图???

Cut君也是为这是困扰的多日,虽然我的一系列产品都已经支持了2015,但是无奈Extension Manager总是识别不出PS……这真的和插件没关系啊啊啊啊啊,作死的阿逗比!

Cut君经过多翻了解和翻查,从官方那里了解到,Adobe即将放弃Extension Manager的支持了,也就是说,以后安装插件,不会再使用Manager这种方式了。从CC开始Adobe在主力推Creative Cloud,想在云上有所建树,于是,后续的插件,也都会通过Cloud来进行安装,所以,以后大家的使用方式是这样滴:
1.  去adobe的Addon官方站点,浏览插件,看到中意的,点击安装(没错,就在网页上点击,当然前提要登录你的adobe帐号)
2.  之后Creative Cloud就会自动将此插件安装到你的电脑上

没错,就这样简单!
不得不说,这个体验还是非常值得表扬的~~
所以大家以后可以忘记Extension Manager这个烦人的玩意了

——————   上面是美好的未来  ————————

老姿才不管以后咋地呢,妈蛋明天要把图切出来给研发了,我的Cutterman还装不上呐!!!

Cut君经过多翻摸索,找到了一种可以不需要Exntesion Manager来安装插件的方法,全程也是自动化、傻瓜化,不用担心会暴露自己小学毕业的学历……

好了,下面是安装方法,仔细看不要眨眼!

1.  从这里下载安装包
2.  解压该zip包,会看到里头有2个文件夹,一个installer.jsx文件
3.  打开PS,在菜单栏里头  文件 -> 脚本  -> 浏览
4.  在打开的对话框选中installer.jsx

接着在弹出的警告窗口,点击OK就好啦~~
重启PS,就会发现神奇的Cutterman神奇的出现在你的PS2015中了

备注: win下的同学,如果弹出下面这个错误,请无视,应该已经成功了,重启PS看看插件在不在就好

小伙伴们终于可以拜托CC2015无法安装插件的阔扰啦~~哇咔咔

One more thing…
介于CC2015即将废弃Extension Manger,直接导致很多伙伴的其它插件也可能安装不上,如果你愿意的话,Cut君提供付费技术解决方案,让你想要的插件,也都可以安装成功!

【转】设计师如何为 Android 应用标注尺寸

下面这篇文字是转载来的,版权归原作者所有

—————-

对追求高还原的产品来说,设计稿上的精确尺寸标记是必不可少的。但 Android 生态中各种尺寸和密度不同的设备让这件事情变得麻烦,设计师好不容易搞清楚了什么是 dp ,什么是 sp,但 Photoshop 里没有这些单位啊,还要换算?这就要了命了。

如果你不想搞清楚这件事的来龙去脉,就先拿这个结论去用吧。

设计 Android 应用的最佳实践
1. 画布大小定位 720 x 1280,72 dpi
2. 只使用偶数单位的尺寸,比如 96 px 的列表项高度,16 px 的边距,64 px 的图标边长
3. 只使用 24 pt,28 pt,36 pt 和 44 pt 的字体
4. 设计完成以后,所有尺寸的 px 值除以 2 作为 dp 数值交给工程师
5. 所有字体的 pt 值除以 2 作为 sp 数值交给工程师
6. 所有切图变成三份,分别是原始大小、缩小 1.5 倍,缩小 2 倍,分别作为 xhdpi,hdpi,mdpi 的资源交给工程师

如果你还有好奇心,可以继续往下看这个结论是怎么来的。
相信你已经看过这篇文档中关于 Android 中各种尺寸单位的介绍,没看过的最好看一下

http://developer.android.com/guide/topics/resources/more-resources.html#Dimension

在 Android 应用设计中涉及到的单位都是密度无关像素(Density-independent Pixels),这个说法太拗口了,通俗点讲,Android 应用设计中只用物理尺寸,类似厘米,英寸这种单位,不用像素。之所以这样,是由于像素在手机领域说不清楚问题,比方说规定列表项高度是 48 px,在 HTC C510e 上看起来就不错,但在三星 Galaxy SIII 上看起来就会非常矮,导致很难看,这是因为这两个机器的屏幕的 dpi 相差很大,前一个大约 160 dpi,后一个大约 320 dpi。这就是手机屏幕不同带来的问题,如果不考虑平板,不同主要是密度不同,而不是尺寸不同,也不是分辨率不同,给设计带来困扰的根本是屏幕密度不同。不幸的是,很少人对这个有概念,通常介绍手机,会说屏幕尺寸,3.5 寸还是 4 寸,会说分辨率,480 x 800 还是 720 x 1280,但通常不会介绍屏幕密度是多少。其实通过尺寸和分辨率可以算出密度来,dpi 的 定义是 dot per inch,即每英寸的像素点,把分辨率和尺寸除一除就能得到。一个不确切的分法是,720 x 1280 的手机很可能接近 320 dpi (Android 里的 xhdpi),480 x 800 的手机很可能接近 240 dpi (Android 里的 hdpi)。

Android 选择的单位是 dp 和 sp,dp 的定义是「在 160 dpi 的屏幕上,1 dp 大约等于 1 px」。这个说法也很拗口,简单点说,1 dp ≈ 1 / 160 inch,他就是物理界的一长度单位。用这个单位设计就统一了,比方说规定列表项高度是 48 dp,在所有手机上看起来都差不多是 48 / 160 inch 那么高,虽然在不同手机上它对应了不一样多的像素点,但这个转换是 Android 手机完成的,每个 Android 手机都得知道在我这 1 dp 对应多少像素。sp 也是同样解释,18 sp 的字在所有手机上看起来应该都差不多大(自己改了字体大小设置的除外)。看到这里,可能有人会想,那岂不是不同手机显示的内容不同。确实是这样,同样一个列表,在 A 手机上只能显示五行,但在 B 个手机上就能显示六行;还是这个列表,在 A 手机上文字左边的留白就显得没有 B 手机多。

铺陈完了,逐条解释开始的最佳实践。

设计师在设计的时候是用不了 dp 的,他不可能拖一个 48 x 48 dp 的框,不可能设置一个 8 dp 的边距,Photoshop 里全是 px。于是我们就只有挑一个特定密度的屏幕,在这个特定密度的屏幕上,dp 和 px 的关系是确定,把设计做了,再把 px 转换成 dp 给工程师。另外有一点是,长度可以乘除一下就解决,图片是不能除的,图片必须手动缩放。

我们挑哪一个密度好呢?答案是挑密度最大的,因为图片缩小比放大好,放大会失真,选 320 dpi 作为目标屏幕,为其他屏幕提供图片时,只需要缩小。而 320 dpi 屏幕的分辨率最常见的是 720 x 1280,以这个尺寸作为画布尺寸,是最带感的,这样的设计稿就和应用在最多数的 320 dpi 的机器上运行起来的样子一样。当然你可以选其他画布大小,但再大也不见得方便,这个大小也够施展了。72 dpi 是 Photoshop 的默认设置,不要改就好,这个数字和后面的换算有关系。

字体的问题,Android 4.0 以后的设计规范中建议只使用四种字号,分别是 12 sp,14 sp,18 sp 和 22 sp,这也是 Android framework 用到的全部字号。我们需要找到在这个画布上,这些字号和 pt 的对应关系,以及,px 和 dp 的对应关系。有两种算法:

  • 算法一
    根据 dp 的定义「在 160 dpi 的屏幕上,1 dp 大约等于 1 px」,那么在 320 dpi 的屏幕上,1 dp 约等于 2 px,我们就是为 320 dpi 做的设计,所有 px 值除以 2 就是 dp 值。字体略复杂一点,1 pt = 1 / 72 inch,即在 72 dpi 的画布上,1 pt = 1 px,我们的画布就是 72 dpi,又有 1 sp 约等于 2 px(同 dp 的定义),所以 1 sp = 2 pt,所有 pt 值除以 2 就是 sp 值。
  • 算法二
    可以想象是把一个 320 dpi 的手机屏幕放大到了 Photoshop 里,放大倍数是 320 / 72,即手机上的 1 dp,在画布上就是 320 / 72 dp,而 1 dp = 1 / 160 inch,所以在画布上就是 2 / 72 inch,而画布是 72 dpi,所以在画布上就是 2 px,即手机上的 1 dp 对应画布上的 2 px。字体的计算一样,只是多一个在 72 dpi 上,1 pt = 1 px 的转换。

至此,都算清楚了,在这个画布上,px 到 dp,pt 到 sp 都是除以 2 的关系。

最后,给 320 dpi 做的图片,到 240 dpi,160 dpi 上就要分别缩小 1.5 倍和缩小 2 倍。120 dpi 的机器已经很罕见,可以不考虑了。

讨论一下Cutterman的“出柜模式”

在苹果老板库克喊出“bigger than bigger”之后,后面的事情大家都知道了

iphone出了两款大屏机器之后,切图工作应该怎么办?
我到底是应该用640的图来做设计呢,还是750呢,还是1242呢?真TMD烦淫……

关于设计稿最佳尺寸相关的知识这里就不写了,网上相关文章很多

今天主要讨论的是,cutterman是如何切@3X图的

1. 假设当前设计稿是640的尺寸

那么cutterman会认为当前的图是@2X的尺寸,于是会将当前的图原封不动输出为name@2x.png,然后将当前图放大1.5倍输出name@3x.png。这个放大的过程中有可能导致图片变虚。
如下图:

2. 假设当前设计稿是750的尺寸

750作为iphone6的屏幕分辨率,其折算的结果是仍然归位@2X的比例,所以如果以该尺寸作图,切图的结果和上面是一样一样滴,不过图会稍大一些而已。

3. 假设当前设计稿尺寸是1242

那么问题来了!
1242与640的比例是1.94,大于1.5
如果我把当前的尺寸认为是@3X,那除以1.5之后的@2X尺寸就会偏大
所以cutterman的@2X图的尺寸是当前尺寸除以1.94,以保证在640的屏幕下是合理的。像这样:

cutterman最初就是这样实现的……

直到有一天,一个小伙伴找上门来(就称呼A同学吧),告诉我这样不对
他的理由是这样的:

@3x的尺寸不应该是@2X的屏幕等比例放大,而应该是严格的1.5倍关系,这样的话,对于6plus而言,图就会偏小,但这是合理的,这样能够给内容流出更多的展示空间,符合6p的设计原则,而不是简单的把5的界面等比例放大到6的界面

然后我被说服了……觉得这样是有道理的
于是,cutterman的实现变成这样:

改成这种方案之后,cut不断地被问:

怎么我切不出@3x的图了,@3x的图不对等等

因为你再也无法切出120px的图了……每到这个时候,我就需要给大家解释以上那么多内容,而且经常都是没有人能够理解。

然后我发现,大部分同学的理解,还是以@3X为当前设计稿尺寸,来进行缩小展示的,所以……cutterman现在的策略是这样:

这样的策略基本就满足广大切图小伙伴的需要了~
不过,为了满足之前A同学的那种需求,cut君加入了一个开关,即是大家看到的“出柜模式”

默认,该开关是关闭的,如果你认同A同学的理论,并且觉得它是正确的,你就打开出柜模式,进入出柜状态~~

—————————  邪恶的分割线  ——————————-

最后,我希望大家都看明白了……sigh

Cutterman2.5版本升级日志

今天对cutterman做了个简单的升级,新增了一个“选区切图”的功能。
该功能详细介绍如下:

比如我们有这样一个ICON

然后我们希望把它切出来一个100×100尺寸的图片。
用过cutterman的童鞋都知道,这个可以通过设置“固定尺寸” 来实现。但是固定尺寸有一个问题,就是它默认会把icon居中显示,如下:

但有时候,我们不希望icon居中显示,我们希望它可以偏上一点……偏左一点等等,这个时候,固定尺寸就搞不了了~~

为此,“选区切图”功能就可以派上用场了!
它的使用方法如下:
用“选区”工具,在你想要输出的元素周围拉一个矩形,即你想要输出
的尺寸,和元素对应的位置,如下图:

该icon距离选区的位置,即是icon被切出来之后的位置,如下图:


基本流程就是:拉一个选区,点导出!没错,就是这么简单~~

—————————— 邪恶的分割线 —————————— 

除了选区切图的功能升级之外,本次还修复了窗口出滚动条和底部出现白条的UI展现问题,解开了众多强迫症设计师的心头结……乌拉乌拉乌拉……

请注意!本次升级需要广大童鞋到官网下载最新的安装包进行覆盖安装~~

2014年11月11日  晚23:58

晚安

Cutterman是如何诞生的

我这会正坐在武汉去北京的高铁上,后天出发去美国出差。漫漫旅程,我觉得可以写篇日志……

08年的夏天,我以web前端工程师的角色进入了互联网这个行业。工作职责是将设计稿转化成web页面,并将数据库的数据输出显示到页面上。用html编写界面,CSS来实现布局和视觉效果,用javascript实现用户的交互操作,同时也关注网络、性能、可用性等内容。这些大体就是前端工程师的主要工作内容了。

从软件开发流程的环节来看,web前端属于设计的下游,设计师在完成设计稿之后,将作品转交给工程师,由工程师实现设计稿到web页面的转换工作。这个交付的过程,就涉及到了一个“切图”问题。

很多的大公司,会将软件工程的各个环节做很细的划分,从而衍生出了许多角色以及部门。比如做市场推广的产品,做产品设计的产品,做界面设计的视觉设计师,做交互设计的交互设计师,做纯静态页面的前端,做交互、功能的前端等等等等。

细粒度的角色划分,有利于分工明确和专业化,但是很多时候在明确分工这个问题上会有些纠缠不清,因为有些工作的耦合性非常强,强行做拆分会增加人员的沟通协作成本。我在刚加入的时候,就存在一个叫UT的部门,也就是刚才提到的做纯静态页面的前端,该部门的同学负责从设计师那里拿到设计稿,然后切图,用html和css完成纯静态页面的制作,偶尔也会用JS实现一些如轮播图这样的交互动画。完成这样的工作之后,再将写好的静态页面代码,交给web前端工程师(也就是我这样的角色),将纯静态页面转换成比如smarty模板,将文案替换程后端变量,再编写一些用户的交互逻辑,请求逻辑等等……

这样持续了一段时间之后,我们发现效率其实非常低下,我们发现静态页面的代码无法满足动态数据输出的要求,或者会吐槽写静态页的工程师搞出了一坨烂代码不能忍,于是经常会把静态页面的代码改的面目全非,造成大量的重复工作和人力浪费,于是在持续了不到一年之后,我们两个部门合并了,于是所有的前端工程师,都需要做切图,写页面的工作。

在我当时的web前端工程师而言,切图是一件及其Easy的工作,大家可能对ps的其它功能不大了解,可是切片工具还是用的相当熟练的。而且很多优秀的前端工程师,会对设计稿做分析,什么样的图应该如何切,做到心中有数,甚至还会给设计师反馈,哪里哪里换一种效果会更好之类的。

在做了4年左右的前端之后,我开始接触客户端的开发工作,最初是从IOS开始。和web端很类似的是,我们都需要将设计稿转化为用户操作的界面,但是ios由于需要兼容3GS的机型,需要提供单倍和双倍图,我当时得知这个情况之后还蛮郁闷的,无形之间,工作量增加一倍。不过由于客户端的开发工作量太大,于是切图的工作交给设计师来完成了,让我唏嘘做web的设计师童鞋都是多么幸福!

但是幸福并没有这么容易,我们发现很多时候设计师切出来的图有问题!比如一个带渐变效果的按钮,其实只需要切一个1px的竖条就可以了,但是设计师会把整个按钮切下来,诸如此类。所以作为研发,我们依然会对切图工作做研究和分享。

就在一次组内分享中

我们组的一个研发同学推荐了一个photoshop脚本,名叫Export For IOS 它可以输出单倍和双倍的图片,我们都很激动,要知道当时设计师童鞋都是手动将设计稿缩小,再切一次图片的!于是我自己也用上了这个脚本,心里惊叹原来PS还可以有外部脚本这样神奇的东东……但是这个脚本用起来稍微不大方便,每次需要点击菜单,脚本,选定等几个鼠标操作,然后一次只能切出一个图层……

直到有一天,我们组的一位美丽的设计师mm找到我,给我介绍了一款切图的PS插件,没错,如果你之前用到过的话,Cut&SliceMe  ,我当时就斯巴达了,尼玛世上还有这么牛逼的东东!原来PS还可以搞插件的哇~~于是我就和设计师mm说:真好哇,有这个牛叉玩意,你们就轻松多啦!可是没想到设计师mm是来和我抱怨的,说这个东东不大好用,要记住一堆的规则,还有输出的图片也有些不理想,总是处于想用却又不敢用的纠结状态……

作为优秀的研发,设计师mm找上门来了,当然不能不理!于是,我决定去弄一款好用的出来……

我最早接触PS可以追溯到04年,那时还是PS6的版本,可是只觉得它是一款牛逼的作图软件,没曾想到,这玩意还能支持插件和扩展开发!于是开始google对应的开发资料。不得不说,adobe在开发平台上的投入,真的是烂到不行,只提供了非常简单的一些文档和碎片资料,而且PS的各种版本特性错综复杂,我摸索的很长时间,都没能把开发环境搭建起来……

在CS6的版本及以前,插件是基于flash平台的,然后我也没有学习过ActionScript,也没有时间学,装好FlashBuilder之后,就上手写helloword,总算是跑通了开发环境……

这里有个插曲,Adobe这个抠门,开发插件的IDE:Extension Builder要收费,卖10刀,不然只提供1个月的试用期,结果我cutterman开发了一小半,到期了,不让用了!我心想,买就买吧,都准备掏钱了,发现购买需要美国的银行帐号!你妹!没有办法,打电话让美国的好朋友第二天帮我购买,最后,也没有买成,因为我在当天晚上花了2个小时把它破解了……只想说:no zuo no die

我边写边学边Google,在花了3个周末的时间,终于把cutterman的第一个版本做出来了。我不喜欢Cut&SliceMe那种规则的模式,需要使用者记住一堆规则,生活本来就很幸苦了,我连女朋友生日都老忘,还能记住你那么多东西?另外,切图的动作本身不是高频的,你就算今天要切图记住了,等下周要切图的时候,你肯定也就忘光了。所以Cutterman的宗旨是:简单+可视化!

所有的功能都一目了然,一个按钮,完成所有工作!要让设计师觉得,没有切图这样的事情存在!

当然,目前的Cutterman还没有达到我理想中的目标,后续会归一化所有功能,只提供一个按钮,在保证功能强大的同时,让所有的配置更清晰易懂,更高效!

Just wait and see.

关于本博客

其实在很早很早以前就是开始写博客了。

从最初的QQ空间,到后来的百度空间,接着是自己建站搭wordpress。然而总是写着写着,就不再更新了……

写博客确实是一件很辛苦的活,需要有大段完整的时间,需要思考,需要整理,还需要用优雅的文字表现出来。以为会有很多观众,会有很多掌声,其实终究还是孤芳自赏罢了。

于是,久而久之,时间都被生活的琐碎给冲淡了,无法静下心来思考,也没心思去整理,就这么浑浑噩噩地,看着钟表不停的旋转,边唏嘘时光飞逝,边抱怨前途暗淡。

于我而言,写博客和画素描一样。心情和环境具有重大的影响,首先是环境是很关键的,我需要有优美的写字载体,我不喜欢在浏览器Wordpress的后台编辑器里头码字,所以在以前,我都是用VIM编辑器进行博客的撰写,然后通过XML-RPC远程发到站点上面。我还为此找到了一个对应的vim plugin,搞的非常geek!可是这种方法,对于图片等资源就比较无能为力了,需要自己手动上传,非常烦人……

到最后,我发现我还是在QQ空间写的文章最多。

今天,无聊打开了Mac的AppStore,想看看有什么好玩的软件,于是就看到了一个叫Blogo的软件,它是Mac下的一款桌面写文章的App,能够支持Wordpress站点。也就是说,你可以在mac下用一款优雅的码字软件来写博客,动态发布到wp站点上了~~

抱着试用下下的态度,想着之前也打算为Cutterman建立一个博客,记录PS插件的开发历程,于是花了几十分钟,又一个wordpress站点出来了……好久没见,wp版本都升级到4.0了

安装好之后,打开Blogo,输入博客地址,和后台登录用户名密码,就可以开始码字了。Blogo界面简洁清爽,文本编辑器支持文字加粗、斜体、引用、链接的基本功能,界面如下图:

插入图片也非常方便,直接insert Image就可以啦,有点word的味道。

接下来,我会以PS插件为主体,定期发布一些文章,讲述cuttermanparker,等等优秀工具的开发升级过程和一些有价值的内容分享。

时间是无法储存的,所以要抓紧用。