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)


2012年12月10日星期一

如何过滤 adb logcat 输出

简介

本文介绍如何在 shell 命令行中过滤 adb logcat 输出的几个小技巧。

开发当中经常看到别人的 log 如洪水般瞬间刷满了屏幕,对自己有用的信息都被淹没了,影响心情也影响效率。下面是几个我所知道的过滤方法。

1. 只显示需要的输出,白名单

最方便的当然是通过管道使用 grep 过滤了,这样可以使用 grep 强大的正则表达式匹配。简单的匹配一行当中的某个字符串,例如 MyApp:

adb logcat | grep MyApp
adb logcat | grep -i myapp #忽略大小写。
adb logcat | grep --color=auto -i  myapp #设置匹配字符串颜色。更多设置请查看 grep 帮助。

进阶一点可以使用 grep 的正则表达式匹配。例如上一个例子会匹配一行中任意位置的 MyApp,可以设置为仅匹配 tag。默认的 log 输出如下,如果修改过输出格式相应的表达式也要修改。

I/CacheService(  665): Preparing DiskCache for all thumbnails.

可以看出 tag 是一行开头的第三个字符开始,根据这点写出表达式:

adb logcat | grep "^..MyApp"