android原生态音乐播放器中文歌曲乱码问题——没落的MIPS

基于android4.2,主要出现在mips架构,arm架构上没有此现象。
猜测与MediaScanner有关,一直跟代码,跟啊跟,跟啊跟。跟到frameworks/base/media/jni/android_media_MediaScanner.cpp文件的handleStringTag函数,它的实现如下:

119virtual status_t handleStringTag(const char* name, const char* value) 120{ 121ALOGE("handleStringTag: name(%s) and value(%s)", name, value); 122jstring nameStr, valueStr; 123if ((nameStr = mEnv->NewStringUTF(name)) == NULL) { 124mEnv->ExceptionClear(); 125return NO_MEMORY; 126} 127 128// Check if the value is valid UTF-8 string and replace 129// any un-printable characters with '?' when it's not. 130char *cleaned = NULL; 131if (utf8_length(value) == -1) { 132cleaned = strdup(value); 133char *chp = cleaned; 134char ch; 135while ((ch = *chp)) { 136if (ch & 0x80) { 137*chp = '?'; 138} 139ALOGE("handleStringTag: value(%x)",ch); 140chp++; 141} 142value = https://www.it610.com/article/cleaned; 143} 144valueStr = mEnv->NewStringUTF(value); 145free(cleaned); 146if (valueStr == NULL) { 147mEnv->DeleteLocalRef(nameStr); 148mEnv->ExceptionClear(); 149return NO_MEMORY; 150} 151 152mEnv->CallVoidMethod( 153mClient, mHandleStringTagMethodID, nameStr, valueStr); 154 155mEnv->DeleteLocalRef(nameStr); 156mEnv->DeleteLocalRef(valueStr); 157return checkAndClearExceptionFromCallback(mEnv, "handleStringTag"); 158}

看131行,就是utf8_length()这个函数调用返回-1,才导致歌曲的名字都设置成了“?”。其实已经测试过了,arm架构返回的值不等于-1.
那就看下函数utf8_length()的实现,找到在frameworks/native/libs/utils/Unicode.cpp中:

364 ssize_t utf8_length(const char *src) 365 { 366const char *cur = src; 367size_t ret = 0; 368while (*cur != '\0') { 369const char first_char = *cur++; 370if ((first_char & 0x80) == 0) { // ASCII 371ret += 1; 372continue; 373} 374// (UTF-8's character must not be like 10xxxxxx, 375//but 110xxxxx, 1110xxxx, ... or 1111110x) 376if ((first_char & 0x40) == 0) { 377return -1; 378} 379 380int32_t mask, to_ignore_mask; 381size_t num_to_read = 0; 382char32_t utf32 = 0; 383for (num_to_read = 1, mask = 0x40, to_ignore_mask = 0x80; 384num_to_read < 5 && (first_char & mask); 385num_to_read++, to_ignore_mask |= mask, mask >>= 1) { 386if ((*cur & 0xC0) != 0x80) { // must be 10xxxxxx 387return -1; 388} 389// 0x3F == 00111111 390utf32 = (utf32 << 6) + (*cur++ & 0x3F); 391} 392// "first_char" must be (110xxxxx - 11110xxx) 393if (num_to_read == 5) { 394return -1; 395} 396to_ignore_mask |= mask; 397utf32 |= ((~to_ignore_mask) & first_char) << (6 * (num_to_read - 1)); 398if (utf32 > kUnicodeMaxCodepoint) { 399return -1; 400} 401 402ret += num_to_read; 403} 404return ret; 405 }

看起来像是判断utf-8的格式,返回-1的地方有多个,测试了一下是在399行返回的,utf32的值大于kUnicodeMaxCodepoint,kUnicodeMaxCodepoint的定义如下:

static const char32_t kUnicodeMaxCodepoint= 0x0010FFFF;

打印了一下(~to_ingnore_mask)、first_char和num_to_read的16进制的值,发现first_char的高位全是F,这个非常不正常。first_char的定义在369行:
【android原生态音乐播放器中文歌曲乱码问题——没落的MIPS】
const char first_char = *cur++;


一个正常的char类型的高位不是F的,为何有这样的结果?慢慢分析后发现first_char与mask以及to_ingnore_mask这两个变量运算过,它们的类型是int32_t,类型不一样就涉及到类型转换。原来,在char类型扩展到32位时,高位要补0。在arm架构里,char类型的默认扩展是无符号扩展,而在mips是有符号扩展,所以高位全补了1,导致运算出来的utf32的高位全是F,所以它的值大于kUnicodeMaxCodepoint。
如何解决?其实很简单,把first_char声明为无符号类型就可以了:

const unsigned char *first_char = *cur++;

其实这个是google程序员写程序时没注意到的细微的地方,但是在goldfish上跑明显有中文歌曲乱码的bug存在,在这种情况下也把源码发布,这个就有点说不过去。结合之前TraceView Issue 这篇文章,可以看出android在支持mips架构时已经不想花太多的精力。不过市场上的android设备跑mips的确实不多。君正不知道还有没有搞mips的soc!mips注定要没落吗!?

    推荐阅读