ANDROID解决音乐播放器没法获取专辑图片问题

最近遇到一个问题,就是音乐播放器没法拿到专辑图片,总是报以下错误:java

---SkImageDecoder::Factory returned null

如下为调试的一些思路及解决方法。android

  • MP3里存放了专辑歌名等信息,这些信息使用ID3格式存储。ID3有几个版本,专辑图片等使用ID3V2存储。关于ID3V2的SPEC请戳这里
  • 下载几首歌,使用mp3tag软件查看专辑图片等信息。
  • 查看ID3里关于专辑图片的说明,能够知道从歌曲里的某个偏移地址读N个字节获得专辑图片。
  • 上层获取专辑图片的代码以下:
 1     public static Bitmap getArtwork(Context context, long song_id, long album_id,
 2             boolean allowdefault) {
 3 
 4         if (album_id < 0) {
 5             // This is something that is not in the database, so get the album art directly
 6             // from the file.
 7             if (song_id >= 0) {
 8                 Bitmap bm = getArtworkFromFile(context, song_id, -1);
 9                 if (bm != null) {
10                     return bm;
11                 }
12             }
13             if (allowdefault) {
14                 return getDefaultArtwork(context);
15             }
16             return null;
17         }
18 
19         ContentResolver res = context.getContentResolver();
20         Uri uri = ContentUris.withAppendedId(sArtworkUri, album_id);
21         if (uri != null) {
22             InputStream in = null;
23             try {
24                 in = res.openInputStream(uri);
25                 return BitmapFactory.decodeStream(in, null, sBitmapOptions);
26             } catch (FileNotFoundException ex) {
27                 // The album art thumbnail does not actually exist. Maybe the user deleted it, or
28                 // maybe it never existed to begin with.
29                 Bitmap bm = getArtworkFromFile(context, song_id, album_id);
30                 if (bm != null) {
31                     if (bm.getConfig() == null) {
32                         bm = bm.copy(Bitmap.Config.RGB_565, false);
33                         if (bm == null && allowdefault) {
34                             return getDefaultArtwork(context);
35                         }
36                     }
37                 } else if (allowdefault) {
38                     bm = getDefaultArtwork(context);
39                 }
40                 return bm;
41             } finally {
42                 try {
43                     if (in != null) {
44                         in.close();
45                     }
46                 } catch (IOException ex) {
47                 }
48             }
49         }
50         
51         return null;
52     }
  • 经过调试可知出错的地方是:BitmapFactory.decodeFileDescriptor(fd)失败。而上一句获取fd能成功,那打开的是什么,哪一个文件?仍是哪一个数据库?
  • 通过一番查找,实现打开方法的地方是MediaProvider,路径是:
android/packages/providers/MediaProvider/src/com/android/providers/media/MediaProvider.java

  在此文件中有实现openFile函数。数据库

  • 对openFile函数进行单步调试,得知打开以下路径:
/mnt/sdcard/Android/data/com.android.providers.media/albumthumbs/1263093833711
  • adb pull这个文件下来,用UE打开,不知道是什么。用手机查看相同目录下的一个文件如1263093833832,用图片方式打开,竟然能够打开,并且就是专辑图片(暂命名此文件为mobile.jpg,此文件对应的mp3歌曲为mobile.mp3)。
  • 用BC对比1263093833711与mobile.jpg。发现1263093833711文件前面多了2个0x00,0x00。
  • 用BC对比mobile.jpg与其对应的mobile.mp3,可知mobile.jpg是从mobile.mp3里截出来的,无一点修改。与ID3说明的同样。
  • 再对比1263093833711与mobile.mp3,开头的地方确实多截了2个字节。
  • 此时,问题已找到,就是生成albumthumb时错了。
  • 1263093833711此类文件在哪生成?在MediaScanner里调用的native函数extractAlbumArt生成的。答案转到jni层。
  • 通过层层解析,最终找到在:
android/frameworks/base/media/libstagefright/id3/ID3.cpp

  文件里的getAlbumArt函数。ide

 

  • 反复看此段代码与ID3 SPEC的4.15&4.10节:
<Headerfor'Attached picture', ID: "APIC">
Text encoding $xx
MIME type <textstring> $00
Picture type $xx
Description <textstringaccordingtoencoding> $00 (00)
Picture data <binarydata>

$00 ISO-8859-1 character set is used => $00 is sync identifier.
$01 Unicode character set is used => $00 00 is sync identifier.
  • mobile.mp3的APIC段的description的encoding为0x01,所以须要2个0x00做为sync identifier。所以在getAlbumArt函数调用StringSize里须要跳过这2个字节:函数

 

 1 static size_t StringSize(const uint8_t *start, uint8_t encoding) {
 2     if (encoding == 0x00 || encoding == 0x03) {
 3         // ISO 8859-1 or UTF-8
 4         return strlen((const char *)start) + 1;
 5     }
 6 
 7     // UCS-2
 8     size_t n = 0;
 9     while (start[n] != '\0' || start[n + 1] != '\0') {
10         n += 2;
11     }
12 
13     n += 2; //mark
14 
15     return n;
16 }

 

  • 问题解决,OVER。
相关文章
相关标签/搜索