LRC歌词解析,实现Linux设备播放音乐显示歌词 LRC解析

2023-11-08

开始正文~~~

1.关于LRC

       lrc是英文lyric(歌词)的缩写,被用做歌词文件的扩展名。以lrc为扩展名的歌词文件可以在各类数码播放器中同步显示。LRC 歌词是一种包含着“*:*”形式的“标签(tag)”的、基于纯文本的歌词专用格式。最早由郭祥祥先生(Djohan)提出并在其程序中得到应用。这种歌词文件既可以用来实现卡拉OK功能(需要专门程序),又能以普通的文字处理软件查看、编辑。当然,实际操作时通常是用专门的LRC歌词编辑软件进行高效编辑的。

1.1 格式

      1、标准格式: [分钟:秒.毫秒] 歌词

      注释:(如右图所示)中括号、冒号、点号全都要求英文输入状态;

      2、其他格式①:[分钟:秒] 歌词;

      3、其他格式②:[分钟:秒:毫秒] 歌词,与标准格式相比,秒后边的点号被改成了冒号

 1.2 标签    

  lrc歌词文本中含有两类标签:

    1.2.1标识标签(ID-tags)

    其格式为"[标识名:值]"。大小写等价。以下是预定义的标签。

    [ar:艺人名]

    [ti:曲名]

    [al:专辑名]

    [by:编者(指编辑LRC歌词的人)]

    [offset:时间补偿值] 其单位是毫秒,正值表示整体提前,负值相反。这是用于总体调整显示快慢的。

     1.2.2 时间标签(Time-tag)

    形式为"[mm:ss]"(分钟数:秒数)或"[mm:ss.ff]"。数字须为非负整数, 比如"[12:34.50]"是有效的,而"[0x0C:-34.50]"无效(但也有不太规范的歌词采用[00:-0.12]的方式表示负值以显示歌曲名,部分播放器是支持的)。 它可以位于某行歌词中的任意位置。一行歌词可以包含多个时间标签(比如歌词中的迭句部分)。根据这些时间标签,用户端程序会按顺序依次高亮显示歌词,从而实现卡拉OK功能。另外,标签无须排序。

    对于时间标签有两种形式

第一种比较简单,一个时间tag带一个歌词行

[00:00.50]蔡健雅 - 依赖

[00:07.94]词、曲:蔡健雅陶晶莹

[00:11.60]关了灯把房间整理好

[00:15.48]凌晨三点还是睡不著

[00:19.64]你应该是不在 所以把电话挂掉

[00:30.39]在黑暗手表跟着心跳

第二种就是多个时间tag,带一个歌词行

[00:15.76]编曲:林迈可
[00:20.00][01:59.75]
[02:01.18][00:21.76]狼牙月 伊人憔悴
[02:04.89][00:25.80]我举杯 饮尽了风雪
[02:10.98][00:31.73]是谁打翻前世柜 惹尘埃是非
[02:17.56][00:38.20]缘字诀 几番轮回
[02:21.66][00:42.35]你锁眉 哭红颜唤不回
[02:27.54][00:48.28]纵然青史已经成灰 我爱不灭
[02:34.89][00:55.60]繁华如三千东流水
[02:39.29][00:59.98]我只取一瓢爱了解
[02:43.39][01:04.33]只恋你化身的蝶;
[02:47.60][01:08.82][03:38.00][03:22.97][01:44.00]
[03:39.06][03:24.50][02:49.36][01:44.75][01:09.96]你发如雪 凄美了离别
[03:42.69][03:26.17][02:53.40][01:46.81][01:14.10]我焚香感动了谁
[03:47.09][03:28.08][02:58.33][01:48.54][01:18.91]邀明月 让回忆皎洁

并且时间标签是无序的,对于这种形式,要是一般的解析可能就会异常。

2.解析LRC

       既然要实现解析LRC歌词,就要完美支持各种情况,实现正确解析。考虑了一下,对于linux C的开发,核心就用链表来实现。核心思想就是:在开始播放的时候,解析歌词,将歌词的时间tag按照升序的形式以链表组织起来。取歌词的时候,以当前播放时间刻度查找链表,同时还要考虑快进、快退这种跳转的查找!

       现在我以代码从内向外的形式解析LRC歌词。

2.1 歌词结构体

/*
歌词结构体
*/
typedef struct lyric{
	struct lyric *next,*prev;
	long timescale;
	char *lyrictext;
}lyric;

next和prev分别指向前一行和后一行歌词。timescale就是时间标签换算成整数形式,以此为基准进行排序,lyrictext就是指向需要显示的具体歌词内容。

简单画个图:

使用链表的好处就是在解析一行有多个时间tag的时候,获取时间值,按照顺序插入对应位置,歌词文本都指向同一个地方。

2.2 链表代码

既然用到链表,就必然用到链表建立、遍历、插入和删除。代码如下:

/*
创建新的歌词链表
*/
lyric * lyricCreateItem(void)
{
	lyric * node = (lyric *)malloc(sizeof(lyric));
	if(node != NULL)
	{
		memset(node,0,sizeof(lyric));
	}

	return node;
}

/*
插入链表
时间刻度按照升序排列
*/
lyric * lyricInsertItem(lyric *prev,lyric * newItem)
{
	lyric *node,*prevnode;

	if(prev == NULL)
	{
		return newItem;//表头
	}

	if(newItem == NULL)
	{
		return prev;
	}

	if(newItem->timescale >= prev->timescale)
	{
		prev->next    = newItem;
		newItem->prev = prev;

		return newItem;
	}
	else
	{
		prevnode = prev;
		node = prev->prev;

		while(node != NULL && node->timescale > newItem->timescale)
		{
			prevnode = node;
			node = node->prev;
		}

		if(node != NULL )
		{
			newItem->next = node->next;
			node->next->prev = newItem;

			node->next = newItem;
			newItem->prev = node;
		}
		else
		{
			newItem->next = prevnode;
			prevnode->prev = newItem;
		}

		return prev;
	}
}

/*
查找下一组节点
*/
lyric * lyricFindItem(long timeScale,lyric *item)
{
	lyric *nextnode,*prevnode,*node=item;

	if(node == NULL)
	{
		return NULL;
	}

	if(timeScale >= node->timescale)
	{
		/* 正常播放 or 快进 */
		nextnode = node->next;
		while(nextnode != NULL && timeScale >= nextnode->timescale)
		{
			node = nextnode;
			nextnode = nextnode->next;
		}

		return node;
	}
	else
	{
		/* 快退 */
		prevnode = node->prev;
		while(prevnode != NULL && timeScale < prevnode->timescale)
		{
			prevnode = prevnode->prev;
		}
		return prevnode;
	}
}

/*
获取歌词
*/
bool lyricGetItemLyric(lyric *item,char *lastLyric,char *currentLyric,char *nextLyric)
{
	lyric *node = item;
	bool value = false;

	if(node == NULL)
	{
		return value;
	}

	if(currentLyric != NULL && node->lyrictext != NULL)
	{
		strcpy(currentLyric,node->lyrictext);
		value = true;
	}

	if(lastLyric != NULL)
	{
		node = item->prev;
		if(node != NULL && node->lyrictext != NULL)
		{
			strcpy(lastLyric,node->lyrictext);
		}
		else
		{
			*lastLyric = '\0';
		}
		value = true;
	}

	if(nextLyric != NULL)
	{
		node = item->next;
		if(node != NULL && node->lyrictext != NULL)
		{
			strcpy(nextLyric,node->lyrictext);
		}
		else
		{
			*nextLyric = '\0';
		}
		value = true;
	}

	return value;
}


lyric * lyricDeleteItem(lyric * item)
{
	lyric *nextnode,*node = item;

	while(node != NULL)
	{
		nextnode = node->next;
		free(node);
		node = nextnode;
	}

	return item;
}

lyricInsertItem代码作为插入,新的歌词单元时间tag如果大于等于前一个,则直接放在前一个后面,并把当前新的歌词单元作为下一次的插入的prev;

lyricFindItem查找链表,timeScale是当前播放时间刻度,item是当前记录的歌词单元,按照时间刻度进行对比:

1.若播放进度大于等于当前进度,则查找是否大于下一个歌词单元,如果是,则将下一个歌词单元输出,如果是快进,则可能是连续跳转好几个歌词单元,直到在小于下一个歌词单元停下。

2.若播放进度小于当前进度,表明是快退,进行时间刻度对比,一直到跳转到小于前一个歌词单元停止。

lyricGetItemLyric:item是获取的当前单元,因为需求需要显示当前歌词,前一个歌词,后一个歌词。所以通过当前获取的歌词单元,分别获取。

2.3 LRC解析

下面进行LRC解析:

/*
解析LRC格式歌词,获取时间刻度
*/
long getTimeScaleForLRC(char *time)
{
	int minute,second,millisecond;

	if(time == NULL)
	{
		return -5;
	}

	minute = second = millisecond = 0;
	do{
		//----------------------------分
		while(isdigit(*time))
		{
			minute = minute*10 + ((*time++) - '0');
		}
		if((*time++) == '\0')
		{
			break;
		}

		//----------------------------秒
		while(isdigit(*time))
		{
			second = second*10 + ((*time++) - '0');
		}
		if((*time++) == '\0')
		{
			break;
		}

		//----------------------------毫秒
		while(isdigit(*time))
		{
			millisecond = millisecond*10 + ((*time++) - '0');
		}

	}while(0);

	millisecond = millisecond < 100?millisecond*10:millisecond;

	return (minute*60+second)*1000+millisecond;
}

/*
解析一行LRC格式歌词
*/
int praseLyricLineForLRC(char *lyricTextLine,int *offset,long *timeScale,int MaxtimeScale,char **outLyricText)
{
	char *p1,*p2,*p3,*text,*out;
	int argc = 0;

	if(lyricTextLine == NULL || timeScale == NULL)
	{
		return -1;
	}

	/* 解析一行歌词所有的时间刻度 */
	text = lyricTextLine;
	while(argc < MaxtimeScale)
	{
		p1 = strchr(text, '[');
		if(p1 == NULL)
		{
			break;
		}

		p3 = strchr(p1, ':');
		if(p3 == NULL)
		{
			break;
		}

		p2 = strchr(p3, ']');
		if(p2 == NULL)
		{
			break;
		}

		p1++;
        *p2 = '\0';

		if(isdigit(*p1))
		{
			*(timeScale+(argc++)) = getTimeScaleForLRC(p1);
		}
		else if(strstr(p1,"offset") != NULL)
		{
			p1 = strchr(p1,':');
			if(p1 != NULL && offset != NULL)
			{
				text = p1+1;
				*p2 = '\0';
				*offset = atoi(text);
			}
		}
		else
		{
			text = text;//解决[01:22:21][歌词]
			break;
		}

		text = p2+1;
	}

	if(outLyricText != NULL)
	{
		*outLyricText = text;
	}

	return argc;
}


/*
解析LRC格式歌词
lyricBuf 读取的整个歌词缓存
*/
int praseLyricForLRC(char *lyricBuf,lyric **node,int *offset,int maxLyricTextLen)
{
	char *lyricLineStart,*lyricLineEnd,*lyricText;
	lyric *prev,*item;
	int i,argc,line,lyriclen;
	long timeScale[20];

	if(lyricBuf == NULL)
	{
		return -1;
	}

	item = lyricCreateItem();
	if(item == NULL)
	{
		return -2;
	}

	item->timescale = 0;
	item->lyrictext = NULL;
	prev = lyricInsertItem(NULL,item);
	*node = item;

	lyricLineStart = lyricBuf;
	line = 1;

	while((lyricLineEnd = strchr(lyricLineStart,'\n')) != NULL)
	{
		*lyricLineEnd = '\0';

		/* 解析一行歌词 */
		argc = praseLyricLineForLRC(lyricLineStart,offset,timeScale,sizeof(timeScale)/sizeof(long),&lyricText);
		for(i = 0;i < argc ; i++)
		{
			if(lyricText == NULL || (lyriclen = strlen(lyricText)) == 0)
			{
				break;
			}

			if(timeScale[i] < 0)
			{
				continue;
			}

			/* 保证歌词后面copy不溢出 */
			if(lyriclen > maxLyricTextLen)
			{
				lyricText[maxLyricTextLen] = '\0';
			}

			item = lyricCreateItem();
			if(item == NULL)
			{
				continue;
			}

			item->timescale = timeScale[i];
			item->lyrictext = lyricText;

			prev = lyricInsertItem(prev,item);
			line++;
		}

		lyricLineStart = lyricLineEnd+1;
	}

	return line;
}

praseLyricForLRC:lyricBuf是读取的整个歌词文件存放的缓存,node是输出的链表第一个单元,offset是标识标签中的[offset:时间补偿值] ,这里LRC解析,不对“标识标签”其他单元做解析,直接忽略,但需要对[offset]进行解析,进行进度的调整,maxLyricTextLen是允许最大显示歌词的长度,防止内存溢出。

因为LRC歌词形式是一行一行的,先读取一行,对其解析,从中获取时间刻度,显示歌词指针,offset。然后创建歌词链表,在进行插入。

praseLyricLineForLRC就是对其中一行进行解析;

getTimeScaleForLRC是获取时间刻度,并换算成整型,单位ms。

这里整个LRC解析就完毕了。

下面进行歌词文件的处理。

2.4 歌词文件的处理

/* 读取文件大小 */
long get_file_size(const char *path)
{
	struct stat statbuff;

	if(stat(path, &statbuff) < 0)
	{
		return -1;
	}
	else
	{
		return statbuff.st_size;
	}
}

/*
读歌词文件
*/
bool readLyricFile(char** lyricFileBuf,char *lyricFile)
{
	long file_size = 0;
	char *lyricBuffer = NULL;
	FILE *fp;
	int ret;

	if(lyricFileBuf == NULL || lyricFile == NULL)
	{
		return false;
	}

	/* 对应歌词文件是否存在 */
	if(access(lyricFile,F_OK) != 0)
	{
		return false;
	}

	/* 获取文件长度 */
	file_size = get_file_size(lyricFile);
	if(file_size <= 0)
	{
		return false;
	}
	file_size += 2;

	/* 读歌词文件 */
	fp = fopen(lyricFile, "r");
	if(fp == NULL)
	{
		return false;
	}

	lyricBuffer = (char*)malloc(file_size*sizeof(char));
	if(lyricBuffer == NULL)
	{
		fclose(fp);
		return false;
	}
	memset(lyricBuffer,'\n',file_size*sizeof(char));

	ret = fread(lyricBuffer,1,file_size-2,fp);
	if(ret < 0)
	{
		fclose(fp);
		free(lyricBuffer);
		return false;
	}
	fclose(fp);
    lyricBuffer[file_size -1] = '\0';
	*lyricFileBuf = lyricBuffer;
	return true;
}

/*
写歌词文件
*/
bool writeLyricFile(char *lyricbuf,int lyriclen,char *lyricFile)
{
	if(lyricbuf == NULL || lyricFile == NULL || lyriclen <= 0)
	{
		return false;
	}

	FILE *fp = fopen(lyricFile, "w");
	if(fp == NULL)
	{
		return false;
	}

	fwrite(lyricbuf,1,lyriclen,fp);
	fclose(fp);

	return true;
}

readLyricFile是根据歌词文件路径,读取歌词,并申请动态内存。并做了相关判断,多读取失败,返回false,表明当前歌曲不存在歌词。其中,在读取歌词文件内存多存了两个'\n',主要是因为前面 praseLyricForLRC解析按照'\n',而实际歌词往往最后一行不存在换行符,导致最后一行无法解析。所以这里进行预处理。

writeLyricFile是写歌词文件。这里下载歌词时一个单独进程,这个进行获取到歌词文件内容,写入文件中。

2.5 加载、获取、释放歌词缓存

这里需要创建整个歌词文件的控制体:

/*
display lyric struct
*/
typedef struct{
	int   lyricoffset;
	lyric *node;
	char  *lyricFileBuf;
}LyricDisplayStruct;

lyricoffset是时间偏移,node是当前获取的歌词单元,lyricFileBuf是获取的整个歌词缓存,从readLyricFile从获取。

typedef struct{
	char title[200];
	char artst[100];
	char endtimestr[12];
	char currtimestr[12];
	char lyricformat[12];
	char lyricurl[256];
	char lastLyric[256];
	char currentLyric[256];
	char nextLyric[256];
	long endtime;
	long currtime;
	bool ispause;
	double progress;
}MusicInfo;

这里是显示歌词的结构体。包括了相关歌曲需要的参数。

创建两个全局变量:

/*歌词相关信息*/
LyricDisplayStruct lyricDisplayInfo;

MusicInfo g_musicinfo;

const char *lyricfile = "/tmp/lyric/lyricfile";

/*
初始化歌词参数
*/
void initLyric(void)
{
	lyricDisplayInfo.lyricoffset  = 0;
	lyricDisplayInfo.node         = NULL;
	lyricDisplayInfo.lyricFileBuf = NULL;
}

/*
加载歌词
*/
lyric* loadLyric(void)
{
	lyric *node = NULL;

	do{
		/* 初始化歌词参数 */
		lyricDisplayInfo.lyricoffset = 0;
		if(lyricDisplayInfo.node != NULL)
		{
			lyricDeleteItem(lyricDisplayInfo.node);
			lyricDisplayInfo.node = NULL;
		}
		if(lyricDisplayInfo.lyricFileBuf != NULL)
		{
			free(lyricDisplayInfo.lyricFileBuf);
			lyricDisplayInfo.lyricFileBuf = NULL;
		}

		/* 读取歌词文件 */
		pthread_mutex_lock(&onDownLoadLyricMutex);
		bool flag = readLyricFile(&lyricDisplayInfo.lyricFileBuf,lyricfile);
		pthread_mutex_unlock(&onDownLoadLyricMutex);
		if(!flag)
		{
			break;
		}

		/* 解析歌词文件 */
		if(strcmp("LRC",g_musicinfo.lyricformat) == 0)
		{
			praseLyricForLRC(lyricDisplayInfo.lyricFileBuf,&lyricDisplayInfo.node,&lyricDisplayInfo.lyricoffset,sizeof(g_musicinfo.currentLyric)-1);
		}
		else
		{
			break;
		}

		node = lyricDisplayInfo.node;
		if(node == NULL)
		{
			break;
		}

		lyricDisplayInfo.node = lyricDisplayInfo.node->next;

		/* 预加载 */
		lyricGetItemLyric(lyricDisplayInfo.node,g_musicinfo.lastLyric,g_musicinfo.currentLyric,g_musicinfo.nextLyric);

	}while(0);

	if(lyricDisplayInfo.node == NULL)
	{
		strcpy(g_musicinfo.currentLyric,"本节目暂无可显示内容");
	}

	return node;
}

/*
获取歌词
return:
	TRUE:需要刷新歌词
	FALSE:不需要刷新歌词
*/
bool getLyric(void)
{
	lyric *node;
	bool status = false;

	if(lyricDisplayInfo.node == NULL)
	{
		return status;
	}

	node = lyricFindItem(g_musicinfo.currtime+lyricDisplayInfo.lyricoffset,lyricDisplayInfo.node);
	if(node != lyricDisplayInfo.node)
	{
		status = lyricGetItemLyric(node,g_musicinfo.lastLyric,g_musicinfo.currentLyric,g_musicinfo.nextLyric);

		lyricDisplayInfo.node = node;
	}

	return status;
}


/*
释放歌词参数缓存
*/
void freeLyric(lyric* node)
{
	lyricDeleteItem(node);
	lyricDisplayInfo.node = NULL;

	if(lyricDisplayInfo.lyricFileBuf != NULL)
	{
		free(lyricDisplayInfo.lyricFileBuf);
		lyricDisplayInfo.lyricFileBuf = NULL;
	}

	lyricDisplayInfo.lyricoffset = 0;
}

loadLyric函数中 lyricDisplayInfo.node = lyricDisplayInfo.node->next;这行语句是因为在praseLyricForLRC函数中创建了一个时间刻度为0的初始链表头,主要是为了获取固定的链表头,便于在freeLyric中从第一个链表开始释放内存,防止内存泄漏。所以loadLyric输出也是第一个链表头。代码示例:

/*
显示歌词示例
*/
void displayLyric(void)
{
    lyric* node = loadLyric();

    while(1)
    {
        if(getLyric())
        {
            //显示歌词
        }

        //其他业务逻辑
    }

    /* 释放歌词缓存 */
    freeLyric(node);
}

在音乐播放开始时,加载歌词loadLyric,如果加载歌词失败,说明没有歌词,显示固定提示内容。每一段时间间隔 getLyric,我这里是每100ms一次。可以满足需求,要求高的话,可是间隔短点,播放结束freeLyric。

这里就完成整个歌词的解析。

3 显示效果

如上已经成功可以显示歌词了

手机拍的失真比较严重,上传一张UED图。

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

4 兼容非标准格式歌词

在实际使用的时候,出现一些非标准的格式歌词:

[ti:真的爱你] [ar:BEYOND] [al:275957] [by:] [offset:0] [00:00.00]真的爱你 (Live) - BEYOND [00:08.56]词:梁美薇 [00:17.13]曲:黄家驹 [00:25.69]无法可修饰的一对手 [00:29.39]带出温暖永远在背后 [00:32.55]纵使啰嗦始终关注 [00:34.98]不懂珍惜太内疚 [00:37.93] [00:38.79]沉醉于音阶她不赞赏 [00:42.14]母亲的爱却永远未退让 [00:45.54]决心冲开心中挣扎 [00:48.14]亲恩终可报答 [00:50.94] [00:51.74]春风化雨暖透我的心 [00:54.94]一生眷顾无言地送赠 [00:59.34] [00:59.99]是你多么温馨的目光 [01:03.25]教我坚毅望着前路 [01:06.45]叮嘱我跌倒不应放弃 [01:11.85] [01:12.40]没法解释怎可报尽亲恩 [01:15.95]爱意宽大是无限 [01:19.00]请准我说声真的爱你 [01:24.50] [01:37.66]无法可修饰的一对手 [01:41.46]带出温暖永远在背后 [01:44.46]纵使啰嗦始终关注 [01:46.99]不懂珍惜太内疚 [01:49.95] [01:50.50]仍记起温馨的一对手 [01:54.10]始终给我照顾未变样 [01:57.36]理想今天终于等到 [01:59.80]分享光辉盼做到 [02:02.65] [02:03.25]春风化雨暖透我的心 [02:06.55]一生眷顾无言地送赠 [02:10.75] [02:11.35]是你多么温馨的目光 [02:14.85]教我坚毅望着前路 [02:17.95]叮嘱我跌倒不应放弃 [02:23.90]没法解释怎可报尽亲恩 [02:27.40]爱意宽大是无限 [02:30.56]请准我说声真的爱你 [02:36.01] [03:01.73]春风化雨暖透我的心 [03:04.68]一生眷顾无言地送赠 [03:08.98] [03:09.58]是你多么温馨的目光 [03:12.88]教我坚毅望着前路 [03:16.04]叮嘱我跌倒不应放弃 [03:21.39] [03:22.04]没法解释怎可报尽亲恩 [03:25.44]爱意宽大是无限 [03:28.59]请准我说声真的爱你 [03:34.15] [03:34.70]是你多么温馨的目光 [03:38.05]教我坚毅望着前路 [03:41.25]叮嘱我跌倒不应放弃 [03:47.15]没法解释怎可报尽亲恩 [03:50.76]爱意宽大是无限 [03:53.76]请准我说声真的爱你#

标准歌词格式都是用分行符标识一行歌词,在解析歌词的时候,已分行符'\n'截取一行歌词,然后解析。如上歌词出现了没有分行符,一整段都是一行,导致解析错误,显示错误信息!

这个非标准歌词有个特点,就是用空格表示一行歌词(上面歌词中的空格可能不怎么明显,但的确存在)。

既然做了歌词解析,那么就要尽可能的兼容异常的情况出现。所以决定对上述歌词进行兼容。

思路:

以空格表示一行歌词,但可能存在歌词中就存在空格的情况:“真的爱你 (Live) - BEYOND”,但一行歌词结束是空格后面紧接着'[',所以以这两个字符为标识符进行解析。

之前的代码以'\n'为一行歌词:(lyricLineEnd = strchr(lyricLineStart,'\n')) != NULL,对这段代码进行改造:


/*
发现一行歌词歌曲行
已'\n'表示一行,同时为了兼容非标准格式歌词,已空格后面紧跟[,标识一个一行
*/
char* findLyricLine(char *lyric,int lyriclen)
{
	char ch;
	int i = 0;

	if(lyric == NULL)
	{
		return NULL;
	}
	lyriclen = lyriclen - 1;//在获取歌词缓存的时候多补了一个'\n'

	while(i++ < lyriclen)//防止没有如下特殊字符出现,一整行歌词带入解析
	{
		ch = *lyric;
		if(ch == '\0')
		{
			return NULL;
		}
		else if(ch == '\n')
		{
			return lyric;
		}
		else if(ch == ' ' && (*(lyric+1) == '['))
		{
			return lyric;
		}

		lyric++;
	}

	return NULL;
}

praseLyricForLRC函数中:

...

totallyriclen = strlen(lyricBuf);

while((lyricLineEnd = findLyricLine(lyricLineStart,totallyriclen)) != NULL )
 {

...

其余保持不变。

这样就可以完美兼容非标准格式歌词了。

留个疑问:

为啥findLyricLine函数代码参数要带入歌词总长,while(i++ < lyriclen) 这句语句又是干嘛呢?

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

LRC歌词解析,实现Linux设备播放音乐显示歌词 LRC解析 的相关文章

  • boa的cgi使用总结

    相关配置 配置ScriptAlias 虚拟路径 真实路径 ScriptAlias cgi bin etc boa www cgi bin 指明CGI脚本的虚拟路径对应的实际路径 一般所有的CGI脚本都要放在实际路径里 用户访问执行时输入站点
  • 2020-11-30

    嵌入式MQTT库移植 基于mosquitto库 一 交叉编译OpenSSL 下载源码到Linux虚拟机环境 https www openssl org source openssl 1 1 1f tar gz 放入非windows共享文件夹
  • c语言代码中调用系统命令行.sh shell脚本,linux shell system传参

    C语言代码中调用命令行 1 使用system 命令行 执行完命令行后 会返回原先C代码的位置 继续执行 2 如果命令行中需要传参 使用 sprintf 先处理好命令行的内容 再 system system echo 123 int a 3
  • Linux下生成patch和打patch

    通过diff工具生成补丁 patch工具打上补丁 在使用diff之前 你需要保留一份未修改过的源码 然后在其它地方修改源码的一份拷贝 diff对比这两份源码生成patch 修改过的源码必须保留原来的文件名 例如 如果你修改源码中的a c文件
  • 树莓派教程 - 1.0 树莓派GPIO库wiringPi 点亮LED

    Git例程源码仓库 https github com ZhiliangMa raspberry git 电脑需要提前安装的工具 SSH 串口 终端神器 MobaXterm 官网下载链接 https mobaxterm mobatek net
  • linux系统下修改文件夹目录权限

    文件夹权限问题 Linux Fedora Ubuntu修改文件 文件夹权限的方法差不多 很多人开始接触Linux时都很头痛Linux的文件权限问题 这里告诉大家如何修改Linux文件 文件夹权限 以主文件夹下的一个名为cc的文件夹为例 下面
  • 智能音箱借ChatGPT重获“新生”?

    曾经靠语音助手红极一时的智能音箱 近年来的市场表现却欠佳 据洛图科技发布的最新 中国智能音箱零售市场月度追踪 报告显示 2022年中国智能音箱总销量为2631万台 同比下降28 市场销售额为75 3亿元 同比下降25 而IDC发布的2023
  • LRC算法的Java实现

    项目中要用到 本来想拿来主义 结果没有找到合适的 所有自己写了一个 LRC具体算法如下 1 对需要校验的数据 2n个字符 两两组成一个16进制的数值求和 2 将模值按位取反 3 加1 Java代码实现 输入byte data 返回LRC校验
  • C语言void指针及使用注意事项详解

    void 指针是一种特殊的指针 表示为 无类型指针 在 ANSI C 中使用它来代替 char 作为通用指针的类型 由于 void 指针没有特定的类型 因此它可以指向任何类型的数据 也就是说 任何类型的指针都可以直接赋值给 void 指针
  • TQ2440移植u-boot2016.11全过程记录-【4】LCD驱动移植并显示

    TQ2440移植u boot2016 11 LCD驱动移植并显示 LCD初始化流程分析 u boot的LCD初始化代码是在common lcd c中 我们找到lcd init函数 该函数功能流程为 lcd ctrl init初始化了LCD的
  • 基于嵌入式Qt的输入法syszuxpinyin自动弹出软件盘的问题

    移植好的syszuxpinyin输入法能正常的检测到控件焦点并自动弹出软键盘 当使用默认的QLineEdit控件时就有了一些小小的问题 问题一 QLineEditt在默认情况下会自动出现焦点 从而导致一进入界面就弹出软键盘 但是我们需要点击
  • 互斥机制之自旋锁(spinlock)

    一 基础 自旋锁 如果测试结果表明锁仍被占用 程序将在一个小的循环内重复这个 测试并设置 操作 即进行所谓的 自旋 1 定义自旋锁 spinlock t spin 2 初始化自旋锁 spin lock init lock 该宏用于动态初始化
  • 在ubuntu下搭建Qt Creator的arm交叉编译环境

    Qt Creator是跨平台的 Qt IDE Qt Creator 是 Qt 被 Nokia 收购后推出的一款新的轻量级集成开发环境 IDE 此 IDE 能够跨平台运行 支持的系统包括 Linux 32 位及 64 位 Mac OS X 以
  • 嵌入式Linux——oops:根据oops信息,找到错误的产生位置以及函数的调用关系

    简介 本文主要介绍通过oops信息找到程序中出错位置的方法 并结合自己代码中的错误来讲解如何找到出错位置 同时还会介绍使用栈信息来推到函数间的调用关系 Linux内核 linux 2 6 22 6 所用开发板 JZ2440 V3 S3C24
  • 嵌入式Linux编译系统的设计——Bootloader, 内核,驱动,文件系统,升级镜像等自动化编译打包

    项目简介 嵌入式系统的开发过程较为复杂 编译 裁剪 定制等如果没有一套规范的流程将会难于管理和控制 本项目的目的是设计一个嵌入式Linux编译系统 实现代码的编译 定制和裁剪 Bootloader 内核 驱动 文件系统 升级镜像等都可以自动
  • GCC、头文件查找顺序总结

    GCC笔记 The History of GCC 1984年 Richard Stallman发起了自由软件运动 GNU Gnu s Not Unix 项目应运而生 3年后 最初版 的GCC横空出世 成为第一款可移植 可优化 支持ANSI
  • u盘安装ubuntu问题:卡在引导界面不动

    问题 一直卡在如图界面不动 分析 既然一直提示syslinux 那我们就看看他是什么东西吧 原因 syslinux分区引导记录问题 解决方案1 安装bootice软件 将制作好的启动盘插入电脑 用bootice更改syslinux引导记录
  • 一张图深度解析Linux共享内存的内核实现

    一张图深度解析Linux共享内存的内核实现 Sailor forever sailing 9806 163 com http blog csdn net sailor 8318 article details 39484747 PDF版本下
  • Linux环境变量执行顺序

    环境变量执行顺序 etc profile etc profile d sh bash profile bashrc etc bashrc
  • unistd.h中定义的setsid()与fork()

    setsid 是一个UNIX系统调用 用于创建一个新的会话 session 并将当前进程设置为该会话的领头进程 session leader 通常情况下 setsid 函数用于创建守护进程 daemon 以确保它与任何终端分离 从而可以在后

随机推荐

  • 如何将爬虫的数据添加到mysql数据库中

    以爬取糗事百科中24小时网页中第一列表页中所有文章的内容 作者 搞笑数 评论数为例 将爬取的四项内容存入到mysql数据库中 思路 要想存入到数据库中就需要用到数据库中的表 所以我们首先创建一个名叫 myblog 的数据库 然后在此数据库中
  • PHP 的Laravel 框架

    在windows下 搭建PHP的Laravel框架很简单 先把PHP的安装目录 加入到环境变量里 在命令行能访问到php v 就说明可以了 然后 这些是需求的环境 PHP gt 7 1 3 不用说了 OpenSSL PHP扩展 用compo
  • 51单片机编写60秒倒计时程序

    include
  • linux下mysql安装

    一 解压缩mysql 5 6 4 m7 tar zip 1 gt unzip mysql 5 6 4 m7 tar zip 会生成mysql 5 6 4 m7 tar gz的压缩文件 2 gt tar zxvf mysql 5 6 4 m7
  • mysqlbinlog命令使用

    参考 https www cnblogs com zouhong p 14540380 html https www iteye com blog wx1568934009 2469938 获取二进制日志列表show binary logs
  • Shell Script—线程

    本文主要介绍shell中的线程 线程中的等待和信号 1 线程 Shell中线程的实现有多种方式 目前只介绍通过 符号 通过在命令末尾加上 符号 可以在后台启动一个进程并立即返回 允许Shell进程继续执行其他命令 示例 bin bash N
  • 023.二叉树的最近公共祖先

    题目链接 236 二叉树的最近公共祖先 大概思路 题目要求 给定一个二叉树 找到该树中两个指定节点q p的最近公共祖先x q p一定存在且值不同 最近公共祖先 两个节点共同的祖先 同时深度尽可能大 其中一个可以是祖先本身 思路 q p的最近
  • 作业asd

    餐馆 class Restaurant def init self restaurant name cuisine type self restaurant name restaurant name self cuisine type cu
  • UI设计基础知识点之Android设计规范篇

    目录 基础概念 Android界面设计规范 Android切图标注 安卓开发单位换算 总结 一 基础概念 1 什么是DPI DPI Dots Per Inch 每英寸点数 表示指屏幕密度 是测量空间点密度的单位 最初应用于打印技术中 它表示
  • 杂项:art-template-loader

    ylbtech 杂项 art template loader 1 返回顶部 2 返回顶部 3 返回顶部 4 返回顶部 5 返回顶部 1 https www npmjs com package art template loader 2 ht
  • Vivado中怎么做set_input_delay约束

    参考 https forums xilinx com t5 Timing Analysis Hold violation in ISERDES td p 715121 前言 在STA中 要分析上游器件和FPGA之间的时序关系就得指定inpu
  • VSCode+arm-none-eabi+msys使用Make实现STM32交叉编译

    记录一下在Windows平台下 利用VSCode的arm none eabi扩展和msys使用Make实现STM32的交叉编译 准备 1 安装arm none eabi扩展 在VSCode的扩展窗口 搜索eabi 安装windows arm
  • 解决HttpClient工具中application/x-www-form-urlencoded表单提交时,请求参数中文乱码问题

    一 参数乱码现象 当我去请求第三方接口时 接口接收格式为Form表单的时候 使用HttpClient工具类 这时 对于封装进HttpPost对象里的请求参数 如果有中文参数 会出现乱码的现象 二 代码现象复现 controller层 Res
  • c语言中\n,\t,\r,\b的用法和区别

    1 n 最容易理解 就是 换行 跳到下一行的起始位置 2 t 也容易理解 即跳到下一制表位 举例如下图所示 3 r 回车 不换行 r后边的数字替代 这一行最开始的相等数目的数字 举例 4 b 退格 也就是电脑键盘上的backspace b后
  • python pip命令安装 pyinstaller失败提示Installing build dependencies ... error

    1 现象 2 解决方案 自己下载 手动安装 2 1 下载地址 https pypi org project PyInstaller 3 5 files 2 2 解压到E python PyInstaller 3 5 2 3 cmd进入到解压
  • 差分信号简述

    差分信号是一种信号传输技术 传统的传输方法使用一根信号线传输信号 一根地线接地 差分信号需要在两根线上都传输信号 且两个信号振幅相同 相位相反 这样的信号就是差分信号 使用差分信号传输的好处是 只要传输正负信号的两根线在物理上是紧密耦合在一
  • echart柱状图Y轴最小间隔问题,Y轴不显示小数

    今天来说说echart 柱状图 Y轴的最小值 间隔问题 最近项目中用到柱状图 数据量少的时候 Y轴会出现小数 但是我们产品小哥哥说不能出现小数 好了 那就开干 option xAxis type category data Mon Tue
  • Linux安装opencv(附带出错,解决方法参考)

    前言 lib库 运行库 dev库 开发 gdb库 调试 安装 1 1 OpenCV是一个开源的软件 在一个source文件下 包含了所有的源代码文件 使用哪种操作系统 windows还是linux 使用哪种编译器 注意 执行安装包很可能不仅
  • springmvc有哪几种请求参数的方式呢?

    转自 springmvc有哪几种请求参数的方式呢 传送参数至后台 是每个系统都必须面对的事实 因为系统就是一个实现人机交互的工具 那么Spring MVC中如何在后台获取参数呢 下文将一一道来 如下所示 方式一 在Controller的形参
  • LRC歌词解析,实现Linux设备播放音乐显示歌词 LRC解析

    开始正文 1 关于LRC lrc是英文lyric 歌词 的缩写 被用做歌词文件的扩展名 以lrc为扩展名的歌词文件可以在各类数码播放器中同步显示 LRC 歌词是一种包含着 形式的 标签 tag 的 基于纯文本的歌词专用格式 最早由郭祥祥先生