Archive for April, 2010

自己动手转 AAC

2010-4-25 23:20 | by 2ndboy

  自从败了 iPod Touch 以后,我就专找 AAC 的专辑下,不过 VeryCD 上的很多专辑都没有 AAC 格式,比如这张 Bryan Adams 的《So far so good》。iPT 不支持 APE,所以晚上研究了一把 APE 转 AAC。

  网上找到了从 foobar2000 里用自带的 convert 功能做转换的方法,原理是调用第三方的 encoder,对 foobar2000 解出来的 PCM raw data 做编码,所以只要是 foobar 支持的音频格式都可以转成你想要的格式[1]

  至于编码器,我还是青睐开源的方案,所以就选了 faac(Freeware Advanced Audio Coder)。SourceForge 上下载的都是源代码,不想自己编译的话,可以在这里直接下载编译好的 binary。

  打开 foobar 的设置,在 Tools 下找到 Converter,点 Add New,然后在 Encoder 里选 Custom。下面是三个最主要设置的填法:
Encoder:填 faac.exe 的绝对路径
Extension:按照 Apple 的惯例,填 m4a
Parameters-w -q 500 -c 22050 - -o %d
注意上面参数中,22050的后面是“- -o”,不是“–o”,两个减号中间有个空格。这个参数是网友推荐的 VBR 最优设置,我用 faac –long-help 查了一下,解读如下:
-w:把编码后的 AAC 数据放到 MP4 容器格式中,这样 iPT 才能识别。
-q 5000:VBR 方式的最高质量,VBR 就不用解释啦。
-c 22050:设置频带宽为 22050 Hz。无损的原始 CD 音频是 44100Hz 的,按照采样定律,采样率要是频带宽的 2 倍,所以这里把频带宽设置为 22050。
:这个单独的减号是让 faac 从 stdio/管道中读取原始音频数据,否则的话 foobar 要先把 PCM 数据写到一个临时文件里,再让 faac 去编码那个 WAVE 临时文件,多了个步骤。
-o %d:告诉 faac 输出文件的文件名,这个 %d 是 foobar 设定的变量,转换时会被替换成用户指定的文件名。

  设定好以后在 foobar 里右击要进行转换的 APE,Convert->…。在 Converter Setup 对话框的 Output format 里选中刚才创建的新 converter,转换进度条走完以后就可以去听一下转出来的 AAC 文件啦,绝对跟原始 APE 一模一样(反正不用专业仪器估计是听不出差别来地:))。

注 1:尽量不要用有损格式转到 AAC,比如 MP3->AAC,这样转出来的效果绝对比用无损的 APE 转到 AAC 要差很多!!!
注 2:关于 AAC 的更多介绍,可以移步到这里围观。

[Update @ 2013-1-26]:在 Mac 命令行下面可以组合使用 madplay 和 faac 来完成 MP3 to AAC 的转换(虽然不建议这么做)。
madplay --output=wave:- test.mp3 | faac -w -q 500 -c 22050 - -o ./test.mp4

用 Hudson 实现 Visual Studio (C++) 项目的 daily build

2010-4-24 13:12 | by 2ndboy

  大概是去年吧,在尝试找一款开源 CI 工具的时候看到了这张表格,当时感觉 BuildbotHudson 不错,但是 Buildbot 在 Windows 下的安装和配置略显复杂,Hudson 就比较简单了,不过在装好 Hudson 以后没有找到编译 VC 6 项目的方法,所以没有继续研究下去。今年工作上的项目基本都要转到 Visual Studio 2008 上去,所以又把 Hudson 捡起来研究了一下,这里记录一下用 Hudson 为 C++ 的 Visual Studio project 做 daily build 的方法和步骤。

一、环境准备和安装

我们的项目是 for Windows 平台的,所以下面介绍的内容很多也只适用于 Windows 平台。Hudson 是用 Java 开发的,所以首先要确保机器上已经安装了 JRE,没有的话可以在这里下载合适的版本安装。

当然,还需要下载 Hudson,目前最新版的 Hudson 是 1.355 版,下载得到的是一个 war 包。俺不是做 Java 的,以前一直以为运行 Hudson 必须要装 Tomcat 这类容器,后来才知道用命令行就可以启动运行 Hudson。所以装 Tomcat 就省啦。

给 Hudson 在剩余空间比较大的盘上安个家,因为 Hudson 一般都把自己的配置文件,从 SCM 里 check out 出来的代码什么的放在它自己的 home 文件夹里。比如把下载回来的 Hudson.war 放在 D:\Hudson 目录下,在命令行下用 java -jar Hudson.war 就可以把 Hudson 开起来。但是这样的话 Hudson 会自动把它的 home 目录设定到当前用户的 home 目录下,所以我们可以在启动 Hudson 的时候手工指定一下 Hudson 的 home 目录:java -DHUDSON_HOME=D:\Hudson -jar hudson.war。

启动 Hudson 之后打开浏览器,访问 http://localhost:8080 就可以看到 Hudson 的 UI 了。为了方便使用,我们可以把 Hudson 安装成为 Windows 的一个 service,这样就不用每次在重启机器后再去手工运行 Hudson 了。进入 Hudson 的设置界面,点 Install as Windows Service。

Install as Windows Service

之所以有很多人喜欢 Hudson 是因为它的灵活和强大,跟 WordPress 一样,Hudson 同样支持通过安装各种 plugin 来扩展自己的功能。而且安装 plugin 在 Hudson 自己的 UI 上就可以完成,不必自己手工下载,放在某个特定目录再去后台设置什么的,非常方便!我们编译 Visual Studio 项目需要用到 MSBuild 这个 plugin,在 plugin 管理界面里的可用 plugin 页面找到 MSBuild,安装,然后重启 Hudson 服务即可。在把 Hudson 安装成一个 service 以后,我们可以通过命令行来启动和停止 Hudson——net start hudson,net stop hudson。

plugins

安装好 MSBuild 以后还要设置一下,进入系统设置,找到 MSBuild 那一段,会看到两个字段:name 和 Path to msbuild.exe。注意这里 name 一定要填,以后在做编译 job 设置的时候会用到,Path to msbuild.exe 就是你机器上 MSBuild.exe 的绝对路径,比如 C:\WINDOWS\Microsoft.NET\Framework\v3.5\MSBuild.exe。

接下来要设置一下 SCM,比如 cvs.exe 在本机上的绝对路径,还有邮件通知使用的 SMTP 地址和账户等。注意邮件通知里的 Hudson URL 默认是 http://localhost:8080/,这里最好改成对外有效的主机名,否则别人收到 build 结果通知邮件以后点击链接访问 localhost 是无意义的:)

二、创建 Hudson build job

Hudson 把受它管理的每个编译项目叫做一个 job,在 Hudson 的 home 目录里你可以找到一个叫 jobs 的目录,里面存放着所有的 build job,在具体一个 job 的目录下还有一个 workspace 文件夹,里面存放的就是从 SCM 里 check out 出来的代码,当然你可以自己指定 workspace 的位置。比如有一个叫 foo 的 job,那它的 workspace 的位置就在 HUDSON_HOME\jobs\foo\workspace。

在 Hudson UI 里新建任务,然后选 Build a free-style software project 这个类型。

点击 Advanced Project Options 可以设置一些不常用的高级选项,比如 Use custom workspace 就可以自己来指定 workspace 的位置。如果你想把代码 check out 到一个特定名字的文件夹里,这个选项就派上用场了。假设你想把 code check out 到一个叫 bar 的文件夹里,可以在 Use custom workspace 里设置 D:\Hudson\jobs\foo\workspace\bar。其实 Hudson 内部定义了一些环境变量,使用这些变量可以增加 job settings 的灵活性,比如上面这个目录可以写成:$HUDSON_HOME\jobs\$JOB_NAME\workspace\bar,这样一来如果你以后变动了 Hudson 的安装位置就可以不用重现设置 job settings 了。有关 Hudson 的环境变量,可以看这个 link。需要注意的是,一但在这里设置了 custom workspace,那么再使用 $WORKSPACE 这个环境变量拿到的就是这个 custom workspace 的位置,而不是默认的位置。

选中 Build Triggers 里的 Build periodically 就可以让 Hudson 在特定的时间自动触发一个 build,这里的语法跟 *nux 里面的 cronjob 很像,比如你想让这个 build 在每天凌晨 1 点自动触发,可以写成 0 1 * * *。

在 Build 节里点 Add build step,选 Build a Visual Studio project or solution using MSBuild,然后来设置 MSBuild。有 3 个 field 需要设置:
MsBuild Version 这里选我们在系统设置设置的 MSBuild 的 name,如果你机器上有多个版本的 MSBuild,这里可以选择合适的版本。
MsBuild Build File 这里填 .sln 或者 .vcproj 文件的位置。
Command Line Arguments 这里填需要传递给 MSBuild 的命令行参数。比如你要编译 foo.sln 里的 p1,p2,p3 这三个工程,但是顺序必须是 2->3->1,那么可以传这个参数:/p:Configuration=Release /t:p2;p3;p1,如果想做一个 clean build,那么只需要在第一个 target 前加上 Clean 即可——/p:Configuration=Release /t:Clean;p2;p3;p1。

最后还可以设置在编译完成以后发通知邮件,多个收件人之间用空格分隔,据说淘宝内部还自己开发了旺旺通知 plugin,在编译结束后可以通过用旺旺发 IM 的形式来通知有关人等,这也体现了 Hudson 非常好的扩展性:)

设置完成以后点 save 保存,一个 new job 就创建好了。你可以点“立即生成”来马上启动一个 build,或者等着 Hudson 在你设定好的时间自动 build 然后把通知邮件发给你。

三、安全设置

安装之后 Hudson 默认是允许匿名访客对 Hudson 和 build job 的设置做修改,而且也可以访问 workspace 查看代码,在实际环境里我们显然需要对这种宽松的 AC 做一下限制。

进入系统管理,系统设置。选中 Enable security。在 Access Control 的 Security Realm 中选中 Hudson’s own user database,表示我们要使用 Hudson 内建的用户数据库来做访问控制。然后在 Authorization 中选中 Matrix-based security,这样我们就可以对每个用户的权限做详细的设置了。

默认已经有一个 Anonymous 匿名用户了,不过他没有任何权限,我们一般要给他 read 权限,这样一来收到 build 邮件通知的人就可以通过点击邮件里的链接看到一些结果信息而又无法改动配置了。在下面我们还可以用 User/group to add 增加一个用户,比如增加一个叫 2ndboy 的管理员用户,然后给他所有的权限:

Access Control

注意我们在创建 2ndboy 用户的时候并没有指定密码,而一旦你开启访问控制,保存设置后,你马上就变成了匿名用户,再也不能访问系统设置了。一开始我也晕了,后来才明白,你在系统设置里新建的用户其实相当于一个 placeholder,要真正创建这个用户、设置密码还需要先点 Log in,然后在 Log in 界面里点 Create an account 来到 Sign up 页面才能真正的创建这个用户。Sign up 好以后要记得到系统设置里把 Hudson’s own user database 下面的 Allow users to sign up 改成未选中状态,否则匿名用户可以随意的 sign up 自己的账户。

Hudson 跟 Buildbot 一样,还支持分布式 build,不过暂时还没什么研究,等有了心得以后再来分享:)

学车手记之——第一次上车

2010-4-24 09:11 | by 2ndboy

  昨天第一次发动真车练直线前进和倒车,教练的评价是车感不错,不过离合器控制还不好,车速太快:) 生平第一次开车,有这样的评价,已经挺满意啦:D

  昨天也听到很多新消息,比如驾照拿到一年内出事故的话,教练要承担责任;还有之前同事们学车和考试用的都是同一台教练车,但是昨天教练说平时练习用教练的车,考试居然要用朗逸手动版!果真是越来越严啦!!好在 LP 姐姐家就有部朗逸手动,考试前怎么也要拿来练几下!!!

  从报名起到现在已经过去了一个半月,3/8 报名、体检;3/29 结业鉴定,99 分;3/30 理论考试,97 分,然后驾校报名+缴模拟费+模拟预约;4/14 模拟一,无聊+无用;4/20 第一次上车,熄火练习;4/23 第一次发动直线前进倒车;4/24 模拟二。

  这里最想说的就是模拟一,简直是骗钱(其实就是骗钱!),就是一台没有显示器的车模型,只是可以挂挡、转方向盘、变转向灯等等。很多东西是坏的,比如转向灯操纵杆是断掉的、接电后仪表盘不亮、刹车踩不动等等。然后呢灯光雨刷等的使用完全不提,总是烂得要死,就是养活了一批人骗骗学员的钱而已,还浪费了俺宝贵的 4 个小时时间!还好模拟二看着还算有点意思,虽然在家偶尔也开开极品飞车,但是方向盘什么的跟真车差距还是比较大地。

Enumerating things in DirectShow

2010-4-11 17:52 | by 2ndboy

  在任何领域做段日子以后,或多或少都会积累起一些自己熟悉和习惯的工具函数/类,今天把最近做 DirectShow 程序的时候写的两个小函数晒一下:)

  在 DirectShow 里做东西肯定有需要枚举一些 object 的场合,比如枚举出某 filter 的所有 pin,或者某 pin 支持的所有 media type。由于 DS 基于 COM 来构建,所以在枚举代码里时不时要照顾到 release 的逻辑,而且这些枚举代码也比较有共性,所以就写了两个小函数来封装一把,下面这个是枚举某 filter 特定方向上所有 pin 的:

  1. bool
  2.  EnumPinsOfFilter(
  3.      IBaseFilter    *pFilter,
  4.      PIN_DIRECTION   dir,
  5.      IEnumPins     **ppEnum,
  6.      IPin          **ppPin )
  7.  {
  8.      bool bResult = false;
  9.  
  10.      while( true )
  11.      {
  12.          if( NULL == pFilter || NULL == ppEnum || NULL == ppPin )
  13.              break;
  14.  
  15.          SafeRelease( *ppPin );
  16.  
  17.          HRESULT hResult = S_OK;
  18.          if( NULL == *ppEnum )
  19.          {
  20.              hResult = pFilter->EnumPins( ppEnum );
  21.              if( FAILED( hResult ) )
  22.                  break;
  23.              (*ppEnum)->Reset();
  24.          }
  25.  
  26.          ULONG uFetched = 0;
  27.          hResult = (*ppEnum)->Next( 1, ppPin, &uFetched );
  28.          if( FAILED( hResult ) )
  29.              break;
  30.  
  31.          if( 0 == uFetched )  // no more pins
  32.              break;
  33.  
  34.          PIN_DIRECTION pindir = PINDIR_INPUT;
  35.          hResult = (*ppPin)->QueryDirection( &pindir );
  36.          if( FAILED( hResult ) || pindir != dir )
  37.          {
  38.              SafeRelease( *ppPin );
  39.              continue;
  40.          }
  41.  
  42.          bResult = true;
  43.          break;
  44.      }
  45.  
  46.      return( bResult );
  47.  }

调用者需要实现准备一个枚举器指针和一个 pin 指针,不断调用这个函数,直到得到一个 false 的返回值。每成功的调用一次需要 release 一下 pin 指针,最后一次调用结束后 release 枚举器指针:

  1. IEnumPins *pEnum = NULL;
  2.  IPin      *pPin  = NULL;
  3.  while( EnumPinsOfFilter( pFilter, PINDIR_OUTPUT, &pEnum, &pPin ) )
  4.  {
  5.      // use pPin
  6.      SafeRelease( pPin );
  7.  }
  8.  SafeRelease( pEnum );

下面这个函数是用来枚举某 pin 支持的所有 media type 的:

  1. bool
  2.  EnumMediaTypesOfPin(
  3.      IPin             *pPin,
  4.      IEnumMediaTypes **ppEnum,
  5.      AM_MEDIA_TYPE   **ppType )
  6.  {
  7.      bool bResult = false;
  8.  
  9.      do
  10.      {
  11.          if( NULL == pPin || NULL == ppEnum || NULL == ppType )
  12.              break;
  13.  
  14.          SafeDeleteMediaType( *ppType );
  15.  
  16.          HRESULT hResult = S_OK;
  17.          if( NULL == *ppEnum )
  18.          {
  19.              hResult = pPin->EnumMediaTypes( ppEnum );
  20.              if( FAILED( hResult ) )
  21.                  break;
  22.              (*ppEnum)->Reset();
  23.          }
  24.  
  25.          ULONG uFetched = 0;
  26.          hResult = (*ppEnum)->Next( 1, ppType, &uFetched );
  27.          if( FAILED( hResult ) )
  28.              break;
  29.  
  30.          if( 0 == uFetched )  // no more types
  31.              break;
  32.  
  33.          bResult = true;
  34.      }
  35.      while( false );
  36.  
  37.      return( bResult );
  38.  }

用法类似,不多说了;)

-=-= =-=-

iPhone OS 4.0 发布以后,果然支持多任务,不过不是真正的多任务就对了。当然,这也不一定完全是坏处,还是要兼顾电力和内存的限制,达到硬件限制和使用体验的最佳平衡。不管怎么说,到夏天的时候就可以升级了。

又一个鄙视 IE 的理由

2010-4-8 20:35 | by 2ndboy

  以前曾经在 IE 6 上遇到过这种问题,用 Chrome 和 FireFox 都可以正常浏览的网站,有几幅图片在 IE 里始终显示叉叉。抓包分析后看上去 web server 那边一切正常,当时感觉这肯定是 IE 的毛病,不过没有深究。今天同事在 IE 8 上遇到了类似的问题,一起分析了下,结果又发现了一个鄙视 IE 的理由。

  我们的程序里内嵌了 IE 控件,但是如果频繁多次去访问 http://www.sina.com 的话,从第二次开始铁定是访问失败的。抓包后发现,凡是出现这种情况的时候,IE 发出去的 HTTP 请求报文都是不完整的,这个不完整不是说请求报文里少了什么字段,而是说假设把整个请求包报文看作一个 200 bytes 的 buffer 的话,IE 在一个单独的 TCP 包里只发出了比方说 100 bytes。其实这在协议上是允许的,遇到这种情况时,web server 要先把这部分请求报文存起来,等将来把整个请求报文收全了再做处理。

  估计是新浪为了防止 DoS 攻击吧,如果 client 发过去一个不完整的 request message,新浪 server 那边会马上回应一个 RST(Reset)过来,从而导致 HTTP 连接被重置,IE 不能显示网页。其实这也不能怪新浪,如果有人用程序在短时内恶意的发来海量不完整 HTTP request,server 又照单全收的缓存了这些半拉子请求的话,系统肯定会很快失去响应。看了一下,新浪用的 web server 是 nginx 0.8.35,果然。

  从抓包的结果看,Chrome 遇到这种连接被 server 端 reset 的情况时会重试 3 次,3 次都失败后才会放弃,所以网页/图片不能显示的情况就极少出现。从 Google 刚发布 Chrome 的时候我就开始从 Maxthon 转向 Chrome,现在除了如网银这类非用 IE 不可的情况外,我已经很少开 IE/Maxthon 了,这次的事情再次证明转向正确:)

–== 俺就一分割线 ==–

明天凌晨,Apple 就要发布 iPhone OS 4.0 了,希望盼望已久的多任务支持变成现实!明早起来再看新闻,希望不会失望:)