2013年11月26日星期二

Bluetooth A2DP audio quality in depth

Summary

This post explains Bluetooth audio profile A2DP, and discuss audio quality of it in technical aspect.

1. Bluetooth A2DP profile

Let's begin with Bluetooth core specification, the most popular and widely supported one is Bluetooth 2.0/2.1 + EDR, which can transmit at 2.1Mbit/s at most. High speed feature is introduced since Bluetooth 3.0, its speed can go up to 24Mbit/s when transfering data through WiFi. The devices support this feature would be marked Bluetooth 3.0 + HS. So those support Bluetooth 3.0 or even 4.0 but without the "HS" logo will remain the same as their Bluetooth 2.1 peers. As for Bluetooth 4.0, it contains 3 subsets that are called classic Bluetooth, high speed Bluetooth and low energy Bluetooth, which is actually equals to 2.1 plus 3.0 + HS and low energy Bluetooth.

A2DP(Advanced Audio Distribution Profile) is a Bluetooth profile, which is supported by almost all Bluetooth speakers and headphones. It is the key that defines audio data bandwidth and audio quality.

2013年11月19日星期二

How to detect Android Cursor leak

Summary:This post tells the theory of detecting SQLite database Cursor leak of your Android app, alongside some common mistake examples. Some leaks are hardly noticable in the code until memory errors happen. This sort of method can be applied to other kinds of resources leak as well.

Cursor leak means that you have opened a Cursor object, which is usually associated with a portion of memory, but fail to close it before you lost referece to it. If it do happens and is repeated several hundreds of times, you would finally be unable to query SQLite database, and exception like this would appear. The log says 866 Cursors are opened and memory allocation of 2MB is failed.

2013年10月27日星期日

专题合集:深入Android媒体存储服务





Android 有一套媒体存储服务,进程名是 android.process.media,主要负责把磁盘中的文件信息保存到数据库当中,供其他 APP 使用以及 MTP 模式使用。这里包含了数据库管理、磁盘文件遍历扫描、多媒体文件解析等几个部分,下面文章分别覆盖了这些内容:

2013年10月23日星期三

深入Android媒体存储服务(二): 磁盘扫描流程

简介
本文是《深入Android媒体存储服务》系列第二篇,简要介绍媒体存储服务扫描文件的流程。文中介绍的是 Android 4.2。
Android 有一套媒体存储服务,进程名是 android.process.media,主要负责把磁盘中的文件信息保存到数据库当中,供其他 APP 使用以及 MTP 模式使用。因此如何保持数据库和磁盘文件保持一致非常关键,这个就是媒体存储服务中 MediaScanner 的工作。

媒体文件扫描流程



2013年10月19日星期六

使用Visa Gift Card无信用卡注册Google Play开发者帐号

大家都知道要注册 Google Play 开发者帐号比较困难,要求通过 Google Wallet 绑定信用卡,而且原则上不支持国内信用卡,因为选项里没有中国。网上有几个被广泛转载的教程,方法为以下两个:
  1. 国内双币信用卡。网上有人注册成功,有的不成功 Wallet 帐号还被封了。我用建行 Master 也是被封了,上传资料申诉也被拒绝。
  2. 财付通 AE 虚拟信用卡。网上有人注册成功,但目前财付通已暂停新用户注册此服务
总结起来,要符合要求的话需要有一张能够验证账单地址,且账单地址和你注册时填写一致的信用卡。由于不能选择中国,所以国内信用卡有可能无法通过,建议如果想尝试使用你的国内卡是新建个 Google 帐号,被封也没什么损失。

还有没其他方法呢?美国有一种预付费不能充值的 Visa 虚拟信用卡,叫 Visa Gift Card,接受 Visa 信用卡的商家都接受这种卡,基本上等同于一张美国信用卡。而且可以在网上注册账单地址,不用担心验证问题

2013年10月2日星期三

宾得Q10 EVA限量版微单 开箱实拍图集

宾得 Q10 是一款非常可爱的微单,有 120 种颜色搭配,今年四月还上市了 EVA 限量版。不解的是现在所谓的 1500 部限量版供货充足,难道是 EVA 量产机?
现在日本亚马逊上 EVA 限量版 29000 日元,普通版 25000 日元,并不比普通版贵多少。以下是开箱图集,点击可打开 2000px 大图:

TYPE01 初号机版

2013年7月1日星期一

深入Android媒体存储服务(一): APP与媒体存储服务的交互

简介
本文介绍如何在 Android 中,开发者的 APP 如何使用媒体存储服务(包含MediaScanner、MediaProvider以及媒体信息解析等部分),包括如何把 APP 新增或修改的文件更新到媒体数据库、如何在多媒体应用中隐藏 APP 产生的文件、如何监听媒体数据库的变化等等。
Android 原生有一套媒体存储服务,进程名是 android.process.media,主要负责把磁盘中的文件信息保存到数据库当中,供其他 APP 使用以及 MTP 模式使用。因此 APP 可以随时快速查询到机器上有多少音乐,音乐的时长、标题、艺术家、专辑封面都可以获取到。下面就介绍我们开发的 APP 如何与这个媒体存储服务打交道。关于媒体存储服务详细分析,请查看本系列第二篇文章:磁盘扫描流程
Note:MTP 模式是 Android 3.0 开始引入的,其数据来源于媒体存储服务。

隐藏多媒体文件

应用场景:APP 产生了图片/音乐/视频类文件,不想让它显示在图库/音乐播放器。市面上有不少游戏,它的图片和音效文件没有做隐藏,出现在用户的图库/音乐播放器当中,引起用户反感。如果用户把它删除了,又可能会影响 APP 正常运行。

2013年6月13日星期四

SQLite 加入自定义函数

简介

SQLite 内置函数比较有限,有时通过添加自定义函数(User-Defined Fuctions)的方式可以实现一些通过普通 SQL 操作无法实现或者实现很麻烦的功能;也可以替换 SQLite 原有的内置函数,使其符合我们的要求。本文侧重说明在 Android 环境下的做法。

现在假设我们现在要在 Android 系统的视频播放器增加一个按照文件扩展名排序的功能,如果不是用自定义函数,就需要先从多媒体数据库中查询出来视频的路径,然后取出视频文件的扩展名并进行排序,因为查询得到的 Cursor 对象不可写,所以需要生成一个 MatrixCursor,把排序后的数据写入,最后返回这个 MatrixCursor。伪代码表示如下:

public Cursor getSortedByFileExtensionCursor() {
    Cursor rawCursor = queryVideoFileNameFromDb(); // 从数据库查询出 id 和路径
    HashMap idAndExtensionMap = getVideoFileExtension(rawCursor); // 获取 id 和扩展名的 HashMap
    Cursor result = sortAndCreateNewMatrixCursor(idAndExtensionMap); // 对扩展名进行排序,生成 MatrixCursor 作为结果
    return result;
}

而如果我们能够向 SQLite 注册一个自定义函数,很多类似问题就要简单不少。

2013年3月14日星期四

蓝牙音频音质探讨

简介

本文简单介绍了蓝牙无线音频技术 A2DP,并从技术角度探讨其最理想情况下的音质。

1. 蓝牙 A2DP 简介

我们先从蓝牙核心规范说起,蓝牙 1.0 的音频传输带宽只有 64kbit/s,8kHz 采样率 8 位采样精度,仅适用于电话。后来进行了升级,目前支持最广泛的蓝牙 2.0/2.1 + EDR 连接速率为 3Mbit/s,实际可用数据传输速率为 2.1Mbit/s(A2DP 与文件传输共享)。蓝牙 3.0 引入了高速数据传输新特性,最高数据传输速率为 24Mbit/s,但是数据传输是通过 WiFi 进行的,支持该特性的设备会标记为“蓝牙 3.0 + HS”。市面上支持蓝牙 3.0 的设备,如果没有标记“蓝牙 3.0 + HS”,数据传输速率并不会提升,由于功耗限制耳机实际上不可能支持 WiFi 传输。最新的是蓝牙 4.0 是一个混合体,包括传统蓝牙,高速蓝牙与低功耗蓝牙三个子集,相当于 2.1 加上 3.0+HS 与低功耗蓝牙。低功耗蓝牙多用于可穿戴设备。

A2DP(Advanced Audio Distribution Profile) 是蓝牙的子协议,蓝牙耳机、音箱等都是通过此协议传输音频数据流的,蓝牙音频所能使用的数据带宽也是由此协议定义。

2013年3月13日星期三

虚拟环绕声技术

简介

本文简单介绍了环绕声基础知识,还介绍了各种双声道虚拟环绕声(多声道)的技术。最近突然对虚拟环绕声感兴趣,整理了一下相关知识。

1 何为环绕声

普通的立体声只能分清左右两个方向的声音,而环绕声还能让人声音的前后左右各个方位,更有空间感仿佛置身于现场。常见的环绕声主要有 5.1 声道与 7.1 声道两种,要重现环绕声也相应的需要 6 个或者 8 个音箱。

电影中最常见的环绕声格式是杜比数字DTS,两者是竞争对手关系,电影 DVD 中一般包含有其中一种或者两种音轨都有。此外,这两种格式也有多个不同的版本,这里就不作详细介绍。

除了这两种格式之外,部分蓝光光盘也提供 LPCM 格式的音轨,这是一种未经压缩的音频格式,体积最大。压缩后的音频解码成 PCM 格式才能播放,所以这种格式无需解码。理论上 LPCM 格式无压缩音质好,无需解码,是最理想的格式,但由于光盘容量是有限的,音频体积大会挤占视频的空间。可以计算 96kHz 采样率,24 位采样精度,7.1 声道的 LPCM 音轨每秒数据量是

2013年3月11日星期一

Android JB icu 汉字排序结果异常






问题

在 Android 4.1,系统语言设置为中文,当使用如下 SQL 语句查询 SQLite 数据库时,排序结果与之前的 Android 版本不同。

SELECT file_path FROM table ORDER BY file_path COLLATE LOCALIZED

以上语句对查询结果进行了排序,并且排序是按照本地化的规则,就是说不仅对 ASCII 字符排序,也会考虑到汉字的排序规则。旧版的 Android 都是遵循英文在前面,中文排在后面的规则,但 4.1 中却是中文排在英文前面。


原因

LOCALIZED 字符比较使用的是 icu 的多语言支持库,代码位于 Android 源码目录下的 external/icu4c 目录。经过一番研究后发现是 Android 4.1 把排序规则更改了(使用 icu 原始数据包排序正常),新增了两条汉字排序规则使汉字排在前面,规则文件位于 external/icu4c/data/coll/zh.txt

" [reorder Hani]"
" [reorder Hani Bopo]"

修改方法

把 external/icu4c/data/coll/zh.txt 中新增那两句话去掉,重新生成 icudt48l-default.dat 数据文件,步骤说明在 external/icu4c/stubdata/readme.txt

具体步骤:首先设置 ANDROID_BUILD_TOP 环境变量为你 Android 源码目录,然后运行 icu_dat_generator.py 就会生成新的数据文件 icudt48l-default.dat。把新的文件 push 到 /system/usr/icu/icudt48l.dat,重启即可看到效果。



2013年1月13日星期日

设定Android NDK编译环境并移植ffmpeg


简介

本文介绍如何使用 Android NDK(r7) 设置 Android 本地代码编译工具链,如何根据 Makefile 编写 Android.mk,并以 ffmpeg(0.8.5) 为例子介绍如何使用此工具链移植。使用编译出来的库文件,可以通过本地 C/C++ 程序调用 ffmpeg 解码库;也可以另外编写 JNI 接口,使用 Java 程序调用 ffmepg。


我们都知道编译软件的一般步骤为:

./configure
make
make install

当然还可以增加参数做些自定义,但大概的流程是这样。要移植一个已有的库到 Android 当中却有很大的不同,首先需要搭建一个交叉编译环境去运行 configure 脚本以便生成配置文件,然后还需要编写 Android.mk 才能编译。

ffmpeg 作例子,运行 configure 会生成 config.mak、config.h 和 libavutil/avconfig.h 这几个文件,里面决定了 ffmpeg 编译哪些模块、是否开启某些特性等。当然如果足够熟悉的话也可以手动修改这几个文件,但是其中的依赖关系复杂,较容易出错。接着根据原来的 Makefile 手动编写 Android.mk 文件,就能编译了。以下是详细流程。

注意:不能直接在宿主系统上运行 configure 脚本,因为环境和目标系统(Android)是不同的,这需要建立交叉编译环境。

如何检测 Android Cursor 泄漏

简介

本文介绍如何在 Android 检测 Cursor 泄漏的原理以及使用方法,还指出几种常见的出错示例。有一些泄漏在代码中难以察觉,但程序长时间运行后必然会出现异常。同时该方法同样适合于其他需要检测资源泄露的情况。



最近发现某蔬菜手机连接程序在查询媒体存储(MediaProvider)数据库时出现严重 Cursor 泄漏现象,运行一段时间后会导致系统中所有使用到该数据库的程序无法使用。另外在工作中也常发现有些应用有 Cursor 泄漏现象,由于需要长时间运行才会出现异常,所以有的此类 bug 很长时间都没被发现。

但是一旦 Cursor 泄漏累计到一定数目(通常为数百个)必然会出现无法查询数据库的情况,只有等数据库服务所在进程死掉重启才能恢复正常。通常的出错信息如下,指出某 pid 的程序打开了 866 个 Cursor 没有关闭,导致了 exception:


3634 3644 E JavaBinder: *** Uncaught remote exception! (Exceptions are not yet supported across processes.)
3634 3644 E JavaBinder: android.database.CursorWindowAllocationException: Cursor window allocation of 2048 kb failed. # Open Cursors=866 (# cursors opened by pid 1565=866)
3634 3644 E JavaBinder: at android.database.CursorWindow.(CursorWindow.java:104)
3634 3644 E JavaBinder: at android.database.AbstractWindowedCursor.clearOrCreateWindow(AbstractWindowedCursor.java:198)
3634 3644 E JavaBinder: at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:147)
3634 3644 E JavaBinder: at android.database.sqlite.SQLiteCursor.getCount(SQLiteCursor.java:141)
3634 3644 E JavaBinder: at android.database.CursorToBulkCursorAdaptor.getBulkCursorDescriptor(CursorToBulkCursorAdaptor.java:143)
3634 3644 E JavaBinder: at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:118)
3634 3644 E JavaBinder: at android.os.Binder.execTransact(Binder.java:367)
3634 3644 E JavaBinder: at dalvik.system.NativeStart.run(Native Method)