标签:
嗯,本该昨晚写的,结果因为系统出了点问题,所以便拖到了今天早上。好了,不废话了。
经常听歌的,必然都见过歌词吧。当然,我们这里要说的歌词格式是以LRC为后缀的歌词。此外,还有QRC,KRC格式的歌词。但相对来说,LRC格式歌词更为常见。我们先来看看LRC格式的歌词究竟长什么样:
好了,我们该怎么解读这种格式的歌词呢?推荐看百度百科对它的介绍,在此就不废话了。
相信写过音乐播放的或者想要写音乐播放器的都会涉及到歌词文件的解析吧。自然,使用高级语言可能更为方便一些。比如,我们可以用正则匹配的方式获得我们需要的时间和歌词行等等。但今天我们只用C语言,依靠C语言的库函数和自己编写的一些函数来实现对上面的歌词进行解析。额,下面的内容可能有点烦琐,此外,算法也不是最佳的。但,个人觉得有一点最大的好处,就是能够熟悉对C语言库函数的使用。比如,我们会用到malloc()/free()函数来动态管理内存,还会用到<string.h>中的字符串处理的函数等等。
Ok, 既然要用最土的办法来解析的话,我们就来理理思路吧。
首先,想法大致是这样的,我们可以设置几个全局变量或者全局数组来存放诸如歌曲的名称,歌曲所属唱片,歌曲的演唱人,以及偏移OFFSET等。所以,我们可以在头文件lyric.h中声明这么几个全局变量:
extern char* lrc_title; extern char* lrc_artist; extern char* lrc_album; extern char* lrc_singer; extern char* lrc_fileName; extern double lrc_offset; //milliseconds.
可以看到,我并没有直接声明成extern char lrc_title[100]这种形式,因为,将要在真正解析的时候使用动态申请内存的方式来实现对这些信息的存储。然后在我们不需要时候释放掉这些存储空间。
Well,再来看看怎么处理歌词正文吧。我们发现每一行歌词的前面都有对应的时间。当然,播放器也正是根据这些时间来显示对应行的歌词的。那么我们该怎么才能将时间对应到相应的歌词行呢?学过Java, C#, C++之类的高级语言的必定都用过类似的一个“容器”Map<T1 Key, T2 Value>,当然,可能名字不是这样,但功能都是类似的。Map<T1 Key, T2 Value>就允许我们将键值对存储进去。就像字典一样,每个单词都有对应的释义。但我们怎么在C语言中实现类似的功能呢?显示,我们不可能做到像Map<T1 Key, T2 Value>这样先进好用的容器。不过,C语言的结构体却可以帮助我们实现一个类似的功能较弱的“容器”。先看下我在头文件lyric.h中的结构体定义:
struct TextLine { double time; ////milliseconds. char text[MAXLRCLINELEN]; };
可见,我是想办法把歌词前面的时间全部转换成毫秒再存放到time中,然后将时间对应的一行歌词存放到text[MAXLRCLINELEN]数组中。这里,之所以没有用char* text形式,主要是为了方便后面的处理。因为上面的lrc_title之类的将会演示这种动态分配内存的用法,所以就不再重复了。此外,我还想提到的是,C99还添加了一个叫做“柔性”数组的特性。也就是说,在上面的结构体中可以换成下面的方式:
struct TextLine { double time; ////milliseconds. char text[]; };
然后,我们可以在解析的时候适当地分配一定大小的空间来存放歌词文本。不过在下面的代码中不再演示这种用法了。不知道“柔性”数组用法的可以自行Google一下。
好了,进入下一个环节。既然我们想好了怎么进行存储了,自然就应该设计一些重要的跟歌词解析有关的函数了。首先看下我在lyric.h中声明的全局函数如下:
//declaration of some global functions, which can be called in other files. FILE* GetLrcFileStream(const char* fileName); STATUS AnalyzeLrcFile(FILE* stream); void DestroyLrc(void);
这三个全局函数分别负责不同的任务。
1、函数:FILE* GetLrcFileStream(1)
用来将根据用户输入的文件名尝试以只读方式打开文件并返回"文件流"。
2、函数:STATUS AnalyzeLrcFile(1)
解析歌词的函数,它用来接收一个“文件流”,并将解析后的歌词信息存储到我们在上面声明的全局变量中,或者是结构体数组中。最后,会返回一个标志解析成功或者失败的状态。
3、函数:
void DestroyLrc()
这个是销毁函数。之所以设计这么一个函数,主要是因为我们可以在结束的时候将之前申请的堆内存空间释放出来,这样才能体现动态内存管理的实质 。其实,关于这个DestroyLrc()函数的实现还是得要小心的,因为我们必须要记住哪些申请的存储空间是要在最后释放的,并且能够正确的释放掉。同时,我们还要避免“迷途指针”的出现。这些具体的细节,将会在下一篇中给出具体的实现。
最后,在看如何实现上面声明的函数之前,我们先来看看头文件lyric.h吧:
1 /* 2 *Filename lyric.h 3 *Author leomon. 4 *Date 2014.4.14 to 2014.4.15. 5 *Version 0.1.0 6 *changelog nothing. 7 */ 8 #ifndef LYRIC_H 9 #define LYRIC_H 10 11 #include <stdio.h> 12 #include <stdlib.h> 13 #include <string.h> 14 #include <assert.h> 15 #include <ctype.h> 16 17 #define MAXTITLELEN 50 18 #define MAXTAGLEN 50 19 #define MAXLRCLINELEN 128 20 #define MAXTEXTLINECNT 128 21 22 struct TextLine 23 { 24 double time; ////milliseconds. 25 char text[MAXLRCLINELEN]; 26 }; 27 28 enum STATUS 29 { 30 SUCCESSFUL, 31 FAILED 32 }; 33 34 typedef enum STATUS STATUS; 35 typedef struct TextLine TextLine; 36 37 //declaration of some global variables. 38 extern char* lrc_title; 39 extern char* lrc_artist; 40 extern char* lrc_album; 41 extern char* lrc_singer; 42 extern char* lrc_fileName; 43 extern double lrc_offset; //milliseconds. 44 extern TextLine *(lrc_textLine[MAXTEXTLINECNT]); 45 46 //declaration of some global functions, which can be called in other files. 47 FILE* GetLrcFileStream(const char* fileName); 48 STATUS AnalyzeLrcFile(FILE* stream); 49 void DestroyLrc(void); 50 #endif // LYRIC_H
下一篇将会给出所有函数的实现细节。本打算放在一篇中搞定的,但发现实在太长了,看起来也不舒服。好了,又要上课去了。^_^
版权声明:本文为博主原创文章,未经博主允许不得转载。
标签:
原文地址:http://www.cnblogs.com/chriscabin/p/4659341.html