免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
12下一页
最近访问板块 发新帖
查看: 9108 | 回复: 12

[C] 单字节字符编码识别问题,windows-1251字符集识别问题 [复制链接]

论坛徽章:
3
摩羯座
日期:2013-11-12 20:06:19午马
日期:2013-11-27 16:35:55双鱼座
日期:2014-04-04 19:02:30
发表于 2012-10-26 21:46 |显示全部楼层
好吧,不得不提个问题了,关于windows-1251字符集检测和转码utf-8的问题。

其实我对字符集,unicode,utf-8啥的一知半解。
  

    最近在做android,遇到一个mp3文件中俄文字符显示乱码的问题。其实呢,对于这种蛋疼的问题完全可以无视的,但是bug是外国客户报出来的,优先级P0。我想肯定是那个制作这个mp3的人,在编辑文件信息(歌曲名字,专辑...)的时候保存成了window-1251,结果到android上媒体文件扫描的时候,不认识这个字符集然后就乱码了。我又想起序哥当年教育我,utf-8保存文件是程序员的基本常识。
    我看了下android上媒体扫描这部分的代码,对于双字节编码的字符android上会做一个检测找到其对应的字符集,例如gbk,big5, shift-jis这些字符集。这个检测依据就是到一个CharSetRange的表中出二分查找这个字符,看看在不在对应的表中。
   Ok,我的问题是,对于windows-1251这种单字节编码的字符,该怎么做检测呢?比如两个俄文字符例如 A B,在windows-1251上被编码成0xCFF0,但是按照android原来的检测方案0xCFF0这两个字节会被当成一个字符去那几个CharSetRange里面查表,查找失败就什么都不做直接传到上层,然后显示就是乱码,那些乱码的字符和用utf-8打开这个mp3文件看到的一样。
   唉,我凌乱了,对于这种单字节编码的字符,在这个字符串传过来的时候我有办法做个检测吗?有办法知道是哪种字符集吗?我想通过查找字符范围的方法并不能确定是哪一个单字节字符集吧?因为,单字节诶我怎么知道0xCF这个字符不会出现在其他的什么XXX字符集中啊?
   看看这个链接是windows-1251 to Unicode table。其实,这种字符的转换的问题,只要按照一个native code to unicode表转成对应的unicode码字传到上层就ok了,是这样吗?
     
    字太多,大神看的烦,我可以把代码贴上来。谢谢各位了。


@starwing83 序哥,我就不浪费你的电话费了,浪费一点点流量吧!

论坛徽章:
3
摩羯座
日期:2013-11-12 20:06:19午马
日期:2013-11-27 16:35:55双鱼座
日期:2014-04-04 19:02:30
发表于 2012-10-26 21:53 |显示全部楼层
还是贴个代码吧....

  1. static uint32_t possibleEncodings(const char* s)
  2. {
  3.     uint32_t result = kEncodingAll;
  4.     // if s contains a native encoding, then it was mistakenly encoded in utf8 as if it were latin-1
  5.     // so we need to reverse the latin-1 -> utf8 conversion to get the native chars back
  6.     uint8_t ch1, ch2;
  7.     uint8_t* chp = (uint8_t *)s;

  8.     while ((ch1 = *chp++)) {
  9.         if (ch1 & 0x80) {
  10.             ch2 = *chp++;
  11.             ch1 = ((ch1 << 6) & 0xC0) | (ch2 & 0x3F);
  12.             // ch1 is now the first byte of the potential native char

  13.             ch2 = *chp++;
  14.             if (ch2 & 0x80)
  15.                 ch2 = ((ch2 << 6) & 0xC0) | (*chp++ & 0x3F);
  16.             // ch2 is now the second byte of the potential native char
  17.             int ch = (int)ch1 << 8 | (int)ch2;
  18.             result &= findPossibleEncodings(ch);
  19.         }
  20.         // else ASCII character, which could be anything
  21.     }

  22.     return result;
  23. }
复制代码
  1. extern uint32_t findPossibleEncodings(int ch)
  2. {
  3.     // ASCII matches everything
  4.     if (ch < 256) return kEncodingAll;

  5.     int result = kEncodingNone;

  6.     if (charMatchesEncoding(ch, kShiftJISRanges, ARRAY_SIZE(kShiftJISRanges)))
  7.         result |= kEncodingShiftJIS;
  8.     if (charMatchesEncoding(ch, kGBKRanges, ARRAY_SIZE(kGBKRanges)))
  9.         result |= kEncodingGBK;
  10.     if (charMatchesEncoding(ch, kBig5Ranges, ARRAY_SIZE(kBig5Ranges)))
  11.         result |= kEncodingBig5;
  12.     if (charMatchesEncoding(ch, kEUCKRRanges, ARRAY_SIZE(kEUCKRRanges)))
  13.         result |= kEncodingEUCKR;

  14.     return result;
  15. }
复制代码

论坛徽章:
5
狮子座
日期:2013-08-20 10:12:24午马
日期:2013-11-23 18:04:102015年辞旧岁徽章
日期:2015-03-03 16:54:152015亚冠之德黑兰石油
日期:2015-06-29 18:11:1115-16赛季CBA联赛之新疆
日期:2024-02-21 10:00:53
发表于 2012-10-26 21:55 |显示全部楼层
这样,你能不能获得系统的默认编码?如果发现编码不是UTF8(这个应该很简单),就认为是当前编码(肯定是俄文),然后转换成utf8,怎么样?

论坛徽章:
3
摩羯座
日期:2013-11-12 20:06:19午马
日期:2013-11-27 16:35:55双鱼座
日期:2014-04-04 19:02:30
发表于 2012-10-26 22:01 |显示全部楼层
starwing83 发表于 2012-10-26 21:55
这样,你能不能获得系统的默认编码?如果发现编码不是UTF8(这个应该很简单),就认为是当前编码(肯定是俄 ...


肯定不行啊?你忽悠我吧,我跟我那个同事的方案不是一样恶心吗?用你的话说,很脏....

论坛徽章:
5
狮子座
日期:2013-08-20 10:12:24午马
日期:2013-11-23 18:04:102015年辞旧岁徽章
日期:2015-03-03 16:54:152015亚冠之德黑兰石油
日期:2015-06-29 18:11:1115-16赛季CBA联赛之新疆
日期:2024-02-21 10:00:53
发表于 2012-10-26 22:05 |显示全部楼层
回复 4# mci2004


    C层面应该有直接转utf8的方法,即使没有我记得Android的C层面也有ICU这样的库,你找找怎么用,获取默认locale应该也是有方法的,一种是LANG环境变量,一种是LC_XXX环境变量等等,你找找这方面的资料嘛。

论坛徽章:
2
青铜圣斗士
日期:2015-11-26 06:15:59数据库技术版块每日发帖之星
日期:2016-07-24 06:20:00
发表于 2012-10-26 22:40 |显示全部楼层
character(或者code point) 是否能按某种方式编码为 bytes? 这是可以检测出的。
具体术语其实很混乱。。。 举个例子就是:

'©' <- 能看到这个字符么? copy right sign。 它的code point是0xa9。
可以确定它能按utf8编码为0xc2, 0xa9, 也能确定它不能按cp936编码。 至于怎么确定。。。 查文档。。。



而从一堆bytes推测它们是由哪些character以什么编码得到。。。 这只能。。。
举个例子。。。
"\xce\xbb", 它可能是'λ'(小写希腊字母lambda, code point=0x3bb)按utf-8编码得到, 也可能是'位'(code point=0x4f4d)按cp936编码得到。
如果把"\xce\xbb"传给你的人不告诉你他使用的编码方式。。。  是没法确定他到底是想表达'λ'还是'位'。。。

当然,bytes给得够多的话,还是有很高概率猜对(只有一种解码方式完全不产生错误)的。。。

具体。。。   让序哥去翻vim的源代码呗。。。

论坛徽章:
3
摩羯座
日期:2013-11-12 20:06:19午马
日期:2013-11-27 16:35:55双鱼座
日期:2014-04-04 19:02:30
发表于 2012-10-27 00:00 |显示全部楼层
回复 6# OwnWaterloo


    Waterloo大神都来了,十分感谢,“猜”这个我大概明白一点,说白了就是计算一个可信度的问题。这个蛋疼的问题,和序哥讨论过了,只能用比较恶心的方法来规避了。其实,我倒觉得不改最和谐。

论坛徽章:
3
摩羯座
日期:2013-11-12 20:06:19午马
日期:2013-11-27 16:35:55双鱼座
日期:2014-04-04 19:02:30
发表于 2012-10-28 02:02 |显示全部楼层
本帖最后由 mci2004 于 2012-10-28 02:02 编辑

@starwing83 序哥,今天晚上又仔细看了下代码,发现代码中似乎不可能出现我昨天说的情况----在手机看到的乱码就是latin1编码。

1,
首先,可以确定你说的是对的,在vim上打个一个mp3文件看到的乱码确实是latin1,为此我特地去查了ISO-8859-1,也就说明了一个文件在vim上如果它不认识直接就当作latin1来转了。而且latin1是单字节的。

2,
关于locale的问题,我也查看了代码,locale信息的确定实际上通过jdk里面的Local类来确定的。这个Locale的确定又和android的系统属性有关。还记得我们昨天看到的那个函数吗?
  1. // if the locale encoding matches, then assume we have a native encoding.
  2.         if (encoding & mLocaleEncoding)
复制代码
事实上这个函数是做一个double check后面一个mLocalEncoding是通过android系统在java层获得的。前面一个encoding是通过findPossibleEncoding()---查巨表的函数获得的。如果这两个相等就非常确定可以是四种字符集中的某一个了,就可以交给下面的conertValues()了。

3,
回到最开始的问题,我为什么可以确定latin1编码不可能出现呢?因为,我看到了下面的代码
还记得endFile()这个函数吧?在没有比配成功字符集后,会直接交给上层去处理,也就是下面的逻辑
  1. // if the locale encoding matches, then assume we have a native encoding.
  2.         if (encoding & mLocaleEncoding)
  3.             convertValues(mLocaleEncoding);

  4.         // finally, push all name/value pairs to the client
  5.         for (int i = 0; i < mNames->size(); i++) {
  6.             status_t status = handleStringTag(mNames->getEntry(i), mValues->getEntry(i));
  7.             if (status) {
  8.                 break;
  9.             }
复制代码
convertValues没有机会执行,那么直接handleSringTag,这个时候那个传过来的字符(乱码那个)被当作一个字符串传进了handleString里(没做任何处理)。

//序哥下面是最后一个函数handleStringTag,主要关注value参数,它是乱码
  1. virtual status_t handleStringTag(const char* name, const char* value)
  2.     {
  3.         ALOGV("handleStringTag: name(%s) and value(%s)", name, value);
  4.         jstring nameStr, valueStr;
  5.         if ((nameStr = mEnv->NewStringUTF(name)) == NULL) {
  6.             mEnv->ExceptionClear();
  7.             return NO_MEMORY;
  8.         }

  9.         // Check if the value is valid UTF-8 string and replace
  10.         // any un-printable characters with '?' when it's not.
  11.         char *cleaned = NULL;
  12.         //判断是不是utf-8原理很简单,看单个字节是否在0x80-->0xBF之间,序哥这里有什么要补充的吗?
  13.         if (utf8_length(value) == -1){
  14.             cleaned = strdup(value);
  15.             char *chp = cleaned;
  16.             char ch;
  17.             while ((ch = *chp)) {
  18.                 if (ch & 0x80) {
  19. //看这里,如果不是utf-8且字符在0x80之后,就认为是unprintable,然后设置成‘?’。
  20.                     *chp = '?';
  21.                 }
  22.                 chp++;
  23.             }
  24.             value = cleaned;
  25.         }
  26.         valueStr = mEnv->NewStringUTF(value);
  27.         free(cleaned);
  28.         if (valueStr == NULL) {
  29.             mEnv->DeleteLocalRef(nameStr);
  30.             mEnv->ExceptionClear();
  31.             return NO_MEMORY;
  32.         }

  33.         mEnv->CallVoidMethod(
  34.             mClient, mHandleStringTagMethodID, nameStr, valueStr);

  35.         mEnv->DeleteLocalRef(nameStr);
  36.         mEnv->DeleteLocalRef(valueStr);
  37.         return checkAndClearExceptionFromCallback(mEnv, "handleStringTag");
  38.     }
复制代码
所以我觉得应该会被显示成'?'才对啊。难道序哥,这个字符传到java层会被转换城latin1吗?不可能吧,java的文档我查了,没有这个说法啊?
但是我肯定我在手机上看到的那个乱码是latin1.

论坛徽章:
5
狮子座
日期:2013-08-20 10:12:24午马
日期:2013-11-23 18:04:102015年辞旧岁徽章
日期:2015-03-03 16:54:152015亚冠之德黑兰石油
日期:2015-06-29 18:11:1115-16赛季CBA联赛之新疆
日期:2024-02-21 10:00:53
发表于 2012-10-28 03:57 |显示全部楼层
回复 8# mci2004


    那么,你要干一件事情:

在C++的handleStringTag之前,把数据打印出来:直接按16进制打印裸的二进制到Log,然后把数据拿出来看。我怀疑在这一步以前,数据就已经被当作latin-1给转成utf-8了。这意味着,读出来的时候就被转换了。那么,你就顺着数据来的方向,隔一段距离打印一次,看看到底是在哪儿被改掉(变成合法的utf-8)的,最终应该能被追踪到StageFright里面去。然后就容易了。

论坛徽章:
0
发表于 2012-10-28 07:59 |显示全部楼层
叔叔一年多前做android系统层, 也处理过andorid media库扫描的问题. LZ贴的代码我依稀看过.

不过对LZ问题的解决, 我帮不上忙. 纯灌水...

顺带吐槽下, Android媒体库扫描真TMD烂. 认为扫描路径只有/system/xxx/xxx和一张SD卡的Google程序员更是脑子被驴踢了...

您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP