C语言同步显示lrc歌词
C语言音视频同步播放算法
C语言音视频同步播放算法C语言音视频同步播放算法是一种用于实现音频与视频文件同步播放的技术。
在实际应用中,我们经常会遇到需要同时播放音频和视频的场景,例如在线视频网站、游戏等。
为了保证用户观看体验的连贯性和一致性,音频和视频之间的同步是非常重要的。
本文将介绍一种基于C语言的音视频同步播放算法。
在开始之前,我们先了解一下音频和视频的基本概念。
一、音频和视频的基本概念音频是指通过声音传输的信息,常见的音频格式有MP3、WAV等。
视频是指通过图像传输的信息,常见的视频格式有AVI、MP4等。
在音视频文件中,音频和视频通常是分开存储的,音频部分是音频流,视频部分是视频流。
二、音视频同步播放原理音视频同步播放的原理是,通过控制音频和视频的播放速度来达到同步的效果。
一般情况下,音频播放速度较快,视频播放速度较慢。
通过调整视频播放速度,使其与音频播放速度保持一致,从而实现同步播放。
三、音视频同步播放算法1. 初始化在开始播放音视频之前,需要进行一些初始化的工作。
首先,我们需要读取音频文件和视频文件,获取它们的基本信息,包括音频时长、视频时长、音频流的采样率等。
然后,创建音频播放线程和视频播放线程,并设置它们的优先级。
2. 音频播放音频播放线程通过调用音频解码器,将音频流解码为原始音频数据。
然后,将原始音频数据写入音频设备进行播放。
在播放音频数据的过程中,我们需要根据音频帧的时间戳来控制音频的播放速度,保证与视频的同步。
3. 视频播放视频播放线程通过调用视频解码器,将视频流解码为原始视频数据。
然后,将原始视频数据渲染到屏幕上进行播放。
在播放视频数据的过程中,我们需要根据视频帧的时间戳来控制视频的播放速度,保证与音频的同步。
4. 同步机制为了保证音频和视频的同步,我们需要引入一个同步机制。
一种常用的同步机制是采用音频时钟和视频时钟的同步方式。
音频时钟用来记录音频播放的时间,视频时钟用来记录视频播放的时间。
在每个播放周期内,我们会比较音频时钟和视频时钟的差值,如果差值较大,则需要调整视频播放速度,使其与音频保持同步。
Android实现歌曲播放时歌词同步显示具体思路
Android实现歌曲播放时歌词同步显⽰具体思路我们需要读取以上歌词⽂件的每⼀⾏转换成成⼀个个歌词实体:复制代码代码如下:public class LyricObject {public int begintime; // 开始时间public int endtime; // 结束时间public int timeline; // 单句歌词⽤时public String lrc; // 单句歌词}可根据当前播放器的播放进度与每句歌词的开始时间,得到当前屏幕中央⾼亮显⽰的那句歌词。
在UI线程中另起线程,通过回调函数 onDraw() 每隔100ms重新绘制屏幕,实现歌词平滑滚动的动画效果。
MainActivity代码如下:复制代码代码如下:import java.io.IOException;import android.app.Activity;import android.media.MediaPlayer;import .Uri;import android.os.Bundle;import android.os.Environment;import android.os.Handler;import android.view.View;import android.view.View.OnClickListener;import android.widget.Button;import android.widget.SeekBar;import android.widget.SeekBar.OnSeekBarChangeListener;public class MainActivity extends Activity {/** Called when the activity is first created. */private LyricView lyricView;private MediaPlayer mediaPlayer;private Button button;private SeekBar seekBar;private String mp3Path;private int INTERVAL=45;//歌词每⾏的间隔@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);// this.requestWindowFeature(Window.FEATURE_NO_TITLE);// getWindow().setFlags(youtParams.FLAG_FULLSCREEN);setContentView(yout.main);mp3Path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/LyricSync/1.mp3";lyricView = (LyricView) findViewById(R.id.mylrc);mediaPlayer = new MediaPlayer();// this.requestWindowFeature(Window.FEATURE_NO_TITLE);ResetMusic(mp3Path);SerchLrc();lyricView.SetTextSize();button = (Button) findViewById(R.id.button);button.setText("播放");seekBar = (SeekBar) findViewById(R.id.seekbarmusic);seekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {@Overridepublic void onStopTrackingTouch(SeekBar seekBar) {// TODO Auto-generated method stub}@Overridepublic void onStartTrackingTouch(SeekBar seekBar) {// TODO Auto-generated method stub}public void onProgressChanged(SeekBar seekBar, int progress,boolean fromUser) {// TODO Auto-generated method stubif (fromUser) {mediaPlayer.seekTo(progress);lyricView.setOffsetY(220 - lyricView.SelectIndex(progress)* (lyricView.getSIZEWORD() + INTERVAL-1));}}});button.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {// TODO Auto-generated method stubif (mediaPlayer.isPlaying()) {button.setText("播放");mediaPlayer.pause();} else {button.setText("暂停");mediaPlayer.start();lyricView.setOffsetY(220 - lyricView.SelectIndex(mediaPlayer.getCurrentPosition()) * (lyricView.getSIZEWORD() + INTERVAL-1));}}});mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { @Overridepublic void onCompletion(MediaPlayer mp) {ResetMusic(mp3Path);lyricView.SetTextSize();lyricView.setOffsetY(200);mediaPlayer.start();}});seekBar.setMax(mediaPlayer.getDuration());new Thread(new runable()).start();}public void SerchLrc() {String lrc = mp3Path;lrc = lrc.substring(0, lrc.length() - 4).trim() + ".lrc".trim();LyricView.read(lrc);lyricView.SetTextSize();lyricView.setOffsetY(350);}public void ResetMusic(String path) {mediaPlayer.reset();try {mediaPlayer.setDataSource(mp3Path);mediaPlayer.prepare();} catch (IllegalArgumentException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (IllegalStateException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}class runable implements Runnable {public void run() {// TODO Auto-generated method stubwhile (true) {try {Thread.sleep(100);if (mediaPlayer.isPlaying()) {lyricView.setOffsetY(lyricView.getOffsetY() - lyricView.SpeedLrc()); lyricView.SelectIndex(mediaPlayer.getCurrentPosition());seekBar.setProgress(mediaPlayer.getCurrentPosition());mHandler.post(mUpdateResults);}} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}}Handler mHandler = new Handler();Runnable mUpdateResults = new Runnable() {public void run() {lyricView.invalidate(); // 更新视图}};}歌词View的代码如下:复制代码代码如下:import java.io.BufferedReader;import java.io.File;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.IOException;import java.io.InputStreamReader;import java.util.Iterator;import java.util.TreeMap;import java.util.regex.Matcher;import java.util.regex.Pattern;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.util.AttributeSet;import android.util.Log;import android.view.MotionEvent;import android.view.View;public class LyricView extends View{private static TreeMap<Integer, LyricObject> lrc_map;private float mX; //屏幕X轴的中点,此值固定,保持歌词在X中间显⽰private float offsetY; //歌词在Y轴上的偏移量,此值会根据歌词的滚动变⼩private static boolean blLrc=false;private float touchY; //当触摸歌词View时,保存为当前触点的Y轴坐标private float touchX;private boolean blScrollView=false;private int lrcIndex=0; //保存歌词TreeMap的下标private int SIZEWORD=0;//显⽰歌词⽂字的⼤⼩值private int INTERVAL=45;//歌词每⾏的间隔Paint paint=new Paint();//画笔,⽤于画不是⾼亮的歌词Paint paintHL=new Paint(); //画笔,⽤于画⾼亮的歌词,即当前唱到这句歌词public LyricView(Context context){super(context);init();}public LyricView(Context context, AttributeSet attrs) {super(context, attrs);init();}/* (non-Javadoc)* @see android.view.View#onDraw(android.graphics.Canvas)*/@Overrideprotected void onDraw(Canvas canvas) {if(blLrc){paintHL.setTextSize(SIZEWORD);paint.setTextSize(SIZEWORD);LyricObject temp=lrc_map.get(lrcIndex);canvas.drawText(temp.lrc, mX, offsetY+(SIZEWORD+INTERVAL)*lrcIndex, paintHL); // 画当前歌词之前的歌词for(int i=lrcIndex-1;i>=0;i--){temp=lrc_map.get(i);if(offsetY+(SIZEWORD+INTERVAL)*i<0){break;}canvas.drawText(temp.lrc, mX, offsetY+(SIZEWORD+INTERVAL)*i, paint);}// 画当前歌词之后的歌词for(int i=lrcIndex+1;i<lrc_map.size();i++){temp=lrc_map.get(i);if(offsetY+(SIZEWORD+INTERVAL)*i>600){break;}canvas.drawText(temp.lrc, mX, offsetY+(SIZEWORD+INTERVAL)*i, paint);}}else{paint.setTextSize(25);canvas.drawText("找不到歌词", mX, 310, paint);}super.onDraw(canvas);}/* (non-Javadoc)* @see android.view.View#onTouchEvent(android.view.MotionEvent)*/@Overridepublic boolean onTouchEvent(MotionEvent event) {// TODO Auto-generated method stubSystem.out.println("bllll==="+blScrollView);float tt=event.getY();if(!blLrc){//return super.onTouchEvent(event);return super.onTouchEvent(event);}switch(event.getAction()){case MotionEvent.ACTION_DOWN:touchX=event.getX();break;case MotionEvent.ACTION_MOVE:touchY=tt-touchY;offsetY=offsetY+touchY;break;case MotionEvent.ACTION_UP:blScrollView=false;break;}touchY=tt;return true;}public void init(){lrc_map = new TreeMap<Integer, LyricObject>();offsetY=320;paint=new Paint();paint.setTextAlign(Paint.Align.CENTER);paint.setColor(Color.GREEN);paint.setAntiAlias(true);paint.setDither(true);paint.setAlpha(180);paintHL=new Paint();paintHL.setTextAlign(Paint.Align.CENTER);paintHL.setColor(Color.RED);paintHL.setAntiAlias(true);paintHL.setAlpha(255);}/*** 根据歌词⾥⾯最长的那句来确定歌词字体的⼤⼩*/public void SetTextSize(){if(!blLrc){return;}int max=lrc_map.get(0).lrc.length();for(int i=1;i<lrc_map.size();i++){LyricObject lrcStrLength=lrc_map.get(i);if(max<lrcStrLength.lrc.length()){max=lrcStrLength.lrc.length();}}SIZEWORD=320/max;}protected void onSizeChanged(int w, int h, int oldw, int oldh) { mX = w * 0.5f;super.onSizeChanged(w, h, oldw, oldh);}/*** 歌词滚动的速度** @return 返回歌词滚动的速度*/public Float SpeedLrc(){float speed=0;if(offsetY+(SIZEWORD+INTERVAL)*lrcIndex>220){speed=((offsetY+(SIZEWORD+INTERVAL)*lrcIndex-220)/20); } else if(offsetY+(SIZEWORD+INTERVAL)*lrcIndex < 120){ Log.i("speed", "speed is too fast");speed = 0;}// if(speed<0.2){// speed=0.2f;// }return speed;}/*** 按当前的歌曲的播放时间,从歌词⾥⾯获得那⼀句* @param time 当前歌曲的播放时间* @return 返回当前歌词的索引值public int SelectIndex(int time){if(!blLrc){return 0;}int index=0;for(int i=0;i<lrc_map.size();i++){LyricObject temp=lrc_map.get(i);if(temp.begintime<time){++index;}}lrcIndex=index-1;if(lrcIndex<0){lrcIndex=0;}return lrcIndex;}/*** 读取歌词⽂件* @param file 歌词的路径**/public static void read(String file) {TreeMap<Integer, LyricObject> lrc_read =new TreeMap<Integer, LyricObject>(); String data = "";try {File saveFile=new File(file);// System.out.println("是否有歌词⽂件"+saveFile.isFile());if(!saveFile.isFile()){blLrc=false;return;}blLrc=true;//System.out.println("bllrc==="+blLrc);FileInputStream stream = new FileInputStream(saveFile);// context.openFileInput(file); BufferedReader br = new BufferedReader(new InputStreamReader(stream,"GB2312")); int i = 0;Pattern pattern = pile("\\d{2}");while ((data = br.readLine()) != null) {// System.out.println("++++++++++++>>"+data);data = data.replace("[","");//将前⾯的替换成后⾯的data = data.replace("]","@");String splitdata[] =data.split("@");//分隔if(data.endsWith("@")){for(int k=0;k<splitdata.length;k++){String str=splitdata[k];str = str.replace(":",".");str = str.replace(".","@");String timedata[] =str.split("@");Matcher matcher = pattern.matcher(timedata[0]);if(timedata.length==3 && matcher.matches()){int m = Integer.parseInt(timedata[0]); //分int s = Integer.parseInt(timedata[1]); //秒int ms = Integer.parseInt(timedata[2]); //毫秒int currTime = (m*60+s)*1000+ms*10;LyricObject item1= new LyricObject();item1.begintime = currTime;item1.lrc = "";lrc_read.put(currTime,item1);}}String lrcContenet = splitdata[splitdata.length-1];for (int j=0;j<splitdata.length-1;j++){String tmpstr = splitdata[j];tmpstr = tmpstr.replace(":",".");tmpstr = tmpstr.replace(".","@");String timedata[] =tmpstr.split("@");Matcher matcher = pattern.matcher(timedata[0]);if(timedata.length==3 && matcher.matches()){int m = Integer.parseInt(timedata[0]); //分int s = Integer.parseInt(timedata[1]); //秒int ms = Integer.parseInt(timedata[2]); //毫秒int currTime = (m*60+s)*1000+ms*10;LyricObject item1= new LyricObject();item1.begintime = currTime;item1.lrc = lrcContenet;lrc_read.put(currTime,item1);// 将currTime当标签 item1当数据插⼊TreeMap⾥i++;}}}}stream.close();}catch (FileNotFoundException e) {}catch (IOException e) {}/** 遍历hashmap 计算每句歌词所需要的时间*/lrc_map.clear();data ="";Iterator<Integer> iterator = lrc_read.keySet().iterator();LyricObject oldval = null;int i =0;while(iterator.hasNext()) {Object ob =iterator.next();LyricObject val = (LyricObject)lrc_read.get(ob);if (oldval==null)oldval = val;else{LyricObject item1= new LyricObject();item1 = oldval;item1.timeline = val.begintime-oldval.begintime;lrc_map.put(new Integer(i), item1);i++;oldval = val;}if (!iterator.hasNext()) {lrc_map.put(new Integer(i), val);}}}/*** @return the blLrc*/public static boolean isBlLrc() {return blLrc;* @return the offsetY*/public float getOffsetY() {return offsetY;}/*** @param offsetY the offsetY to set*/public void setOffsetY(float offsetY) {this.offsetY = offsetY;}/*** @return 返回歌词⽂字的⼤⼩*/public int getSIZEWORD() {return SIZEWORD;}/*** 设置歌词⽂字的⼤⼩* @param sIZEWORD the sIZEWORD to set*/public void setSIZEWORD(int sIZEWORD) {SIZEWORD = sIZEWORD;}}xml布局⽂件如下:复制代码代码如下:<SPAN style="FONT-SIZE: 18px"><STRONG><?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="/apk/res/android" android:layout_width="fill_parent"android:layout_height="fill_parent"android:background="#FFFFFF" ><com.music.lyricsync.LyricViewandroid:id="@+id/mylrc"android:layout_width="fill_parent"android:layout_height="fill_parent"android:layout_marginBottom="50dip"android:layout_marginTop="50dip" /><LinearLayoutxmlns:android="/apk/res/android"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentBottom="true"android:orientation="horizontal" ><Buttonandroid:id="@+id/button"android:layout_width="wrap_content"android:layout_height="wrap_content" /><SeekBarandroid:id="@+id/seekbarmusic"android:layout_width="205px"android:layout_height="wrap_content"android:layout_gravity="center_vertical"android:layout_marginBottom="5px"android:progress="0" /></LinearLayout></RelativeLayout> </STRONG></SPAN>。
C语言同步显示lrc歌词
C语言同步显示lrc歌词C语言技术报告班级:学号:姓名:项目说明:1、本程序在VC环境下调试成功环境:需安装千千静听到默认路径下,同时安装vc6.0环境2、本程序完成功能:完成LRC格式歌词的文件读取、解析、结构体创建等,同时在屏幕上将解析出的歌词进行实时显示基本功能实现:1、读取文件,能正确打印到屏幕上。
2、切割字符串,正确存到一个结构体数组中。
3、能正确判断歌曲的歌名与演唱者,并打印出来。
4、实现模拟时钟的功能。
5、按顺序将歌词输出。
6、能滚动显示歌词。
7、能与时间匹配突出显示歌词的颜色。
项目流程:1、首先将歌词文件内容通过fopen全部读到一个数组song_word[][]中,然后将歌词内容保存到数组song_word1[][]。
2、通过函数message_song1()判断歌曲的歌名与演唱者,并打印出前4行的歌曲信息。
3、用函数message_song2()和strtok将同一行的歌词和时间一一对应的进行切割,并分别保存在数组lyric[][]和time[][]中,并通过Sleep(1000)将时间标签转换为以s为单位的模拟时钟。
4、在每一秒内对将时钟的时间与歌词前面的时间相比较,如果相等那么就输出当前时间后面的歌词,实现了歌词与时间的同步显示。
5、歌词的前5行依次输出,当歌词大于5行时,用函数MoveText(22,6,20,5,22,5);实现将歌词整块移动并每次显示5行。
7、GoToXY(22,m)和SetText_Color(2)实现将光标定位在当前行并且显示相应的颜色,使已显示的歌词变为白色,而刚出现的歌词变为相应的颜色。
6、在主程序运行时即可启动千千静听ShellExecute( NULL, NULL, "TTPlayer.exe",".\\LRC\\简单爱.mp3",NULL,1);代码:#include#include#include#include#include#include"console.h"//跳转到光标指定位置void GoToXY(int x,int y){HANDLE h1;COORD pos;pos.X=x;pos.Y=y;h1=GetStdHandle(STD_OUTPUT_HANDLE);SetConsoleCursorPosition(h1,pos);}//屏幕整块移动//参数:startx、starty:要移动的块的起始坐标// sizex、sizey:块的大小// destx、desty: 目标坐标,即要移动到得位置void MoveText(int startx, int starty, int sizex, int sizey, int destx, int desty) {SMALL_RECT rc = {startx, starty,startx+sizex, starty+sizey};COORD crDest = {destx, desty};CHAR_INFO chFill;HANDLE hout = GetStdHandle(STD_OUTPUT_HANDLE);CONSOLE_SCREEN_BUFFER_INFO bInfo;GetConsoleScreenBufferInfo(hout, &bInfo);chFill.Attributes = bInfo.wAttributes;chFill.Char.AsciiChar =' ';ScrollConsoleScreenBuffer(hout, &rc, NULL, crDest, &chFill);}/*第一个值为背景色,第二个值为前景色:0 = 黑8 = 灰1 = 蓝9 = 淡蓝2 = 绿 A = 淡绿3 = 湖蓝B = 淡浅绿4 = 红 C = 淡红5 = 紫 D = 淡紫6 = 黄 E = 淡黄7 = 白 F = 亮白*///设置接下来终端显示文本的背景色和文本颜色void SetText_Color(int color){HANDLE hStdout;hStdout = GetStdHandle(STD_OUTPUT_HANDLE);SetConsoleTextAttribute(hStdout,color);}void message_song1(char song[4][100])//切割前四行{int i;char delims[]="[:]";char *result=NULL;char *sign[4]={"歌名","歌手","制作","专辑"};for(i=0;i<4;i++){strtok(song[i],delims);result= strtok(NULL,delims);printf("%s %s\n",sign[i],result);}printf("\n\n");}void message_song2(char song1[100][100],char time[100][100] ,char lyric[100][100] )//切割歌词和时间{int i,j,k1=0,k2=0,min=0,tsec=0,gsec=0,b=6;int flag=0,m=5;char delims1[]="[]";char *result1=NULL;for(j=0;song1[j][0]!='\0';j++){result1=strtok(song1[j],delims1);for(i=0;result1!=NULL;i++){if(i%2==0){strcpy(time[k1],result1);result1=strtok(NULL,delims1);k1++;}if(i%2==1){strcpy(lyric[k2],result1);result1=strtok(NULL,delims1);k2++;}}}j=0;for(i=0;;i++){GoToXY(22,5);printf("The time is:0%d:%d%d ",min,tsec,gsec); Sleep(1000);gsec++;if(gsec==10){tsec++;gsec=0;}if(tsec==6){min++;tsec=0;}for(j=0;j<50;j++){if((min==time[j][1]-48)&&(tsec==time[j][3]-48)&&(gsec==time[j][4]-48)){if(m<10) m+=1;else m=10;GoToXY(22,m);SetText_Color(2);printf("%s\t\t\t\t\n",lyric[j-1]);GoToXY(22,m-1);SetText_Color(7);printf("%s\t\t\t\t\n",lyric[j-2]);flag++;if(flag>=5)MoveText(22,6,20,5,22,5);}}}}void main(){FILE*fp;int i=0;char time[100][100]={0};char lyric[100][100]={0};charinfile[1000]={0},song_word[100][100]={0},song_word1[100][100]={0};char *result=NULL;char delims1[]="\n";ShellExecute( NULL, NULL, "TTPlayer.exe","spring.mp3",NULL,1); //执行千千静听if((fp=fopen("spring.lrc","rb"))==NULL){printf("can not open file\n");exit(0);}while(!feof(fp)){infile[i]=fgetc(fp);i++;}infile[i]='\0';fclose(fp);printf("\n");result=strtok(infile,delims1);for(i=0;result!=NULL;i++){strcpy(song_word[i],result);result++;result=strtok(NULL,delims1);}for(i=0;i<44;i++)strcpy(song_word1[i],song_word[i+4]);message_song1(song_word);message_song2(song_word1,time,lyric);}bug调试:刚开始对切割函数strtok含有多个标识符的使用理解不透彻,经过多次改变标识符观察输出结果,也就正确理解了。
歌曲信息管理系统——C语言
歌曲信息管理系统[要求]制作一个歌曲信息管理系统功能:1)歌曲信息包括歌曲名,作者,演唱者,发行年月等。
2)可以对歌曲信息进行输入,删除,浏览。
3)可以查询根据歌曲名,作者,演唱者歌曲信息。
4)可以提供按作者分组显示功能。
5)用文件存储信息。
我得设计思路根据题目要求,由于歌曲信息数据以文本文件得方式存放,所以应提供文件得输入、输出等操作;在程序中需要浏览歌曲得信息,应提供显示、查询等操作,按照一般得思路,我采用得就是按照歌曲名进行查找得方式。
并且要提供可以按作者进行分组得方法。
整体设计思路就是首先以一个主函数为主,将各个功能诸如浏览,删除等分别设计成函数,通过主函数得调用从而实现其功能。
由于程序中经常要用到一些函数,诸如加载函数,显示函数等,如果重复输入,会严重增加工作量,所以可以将这些函数作为公共函数编写出来,以被随时调用。
分析一下整个系统,根据上面得需求分析,可以将系统设计分为以下六大模块。
四.详细设计#include<stdio、h>#include<stdlib、h>#include<string、h>#include<malloc、h>#define N 100上面这些就是整个程序要用到得文件包含命令与宏定义struct song{char name[20];char creater[20];char singer[20];char date[6];}so[N];将下面所要编写得函数分别声明出来void shu_ru() ;void shan_chu();void liu_lan() ;void cha_zhao();void fz(int i,int n);void fen_zu ();void fname();void printf_one(int i);void input(int i);void save(int n);void printf_back();int load();void printf_face();int shan_chue_data(int j,int n);首先就是设计主函数,设计主函数一般应该做到简洁,主函数主要就是提供函数调用等方面得功能,各功能函数以菜单得形式在主函数中列出来,通过主函数得调用从而实现各功能函数得功能。
lrc歌词读取和排序算法 (1)
LRC歌词读取和排序算法已有 14280 次阅读2008-6-5 15:41代码写了其实挺久了,现在突然想起来,鼓励自己下下,呵呵代码是为了给SPCE3200做一个微型mp3播放器而做的。
界面仿千千静听,使用软件解码器解mp3,滚动条、文件列表,基本功能差不多都有了,后来突然想到为它增加歌词显示,这样才算比较完整。
所以就开始要写lrc的解释程序了。
之前对lrc有一点了解,知道它最主要的就是用时间标签表达每一句歌词需要显示的时间。
所以,lrc解释程序的最主要任务就是分析时间标签,然后将实际的歌词拿出来~~~看代码吧:#include <string.h>#include "lrc.h"#define isnumeric(a) (((a >= '0') && (a <= '9')) ? 1 : 0)void CopyLrcInfo(LRC_INFO *DstInfo, LRC_INFO *SrcInfo){DstInfo->Time = SrcInfo->Time;DstInfo->Prev = SrcInfo->Prev;DstInfo->Next = SrcInfo->Next;strcpy(DstInfo->LrcText, SrcInfo->LrcText);}LRC_INFO *GetFirstOfList(LRC_QUEUE *List){return &List->List[List->First];}u16_t InsertList(LRC_QUEUE *List, u32_t Time, char LrcText[]) {int InsertPos = -1;u16_t i;LRC_INFO *lp = &List->List[List->Last];if(List->Length == 0){List->List[0].Time = Time;strcpy(List->List[0].LrcText, LrcText);List->List[0].Prev = (void*)0;List->List[0].Next = (void*)0;List->First = 0;List->Last = 0;List->Length = 1;InsertPos = 0;}else{for(i = List->Length; i != 0; i--){if(lp->Time <= Time){if(i == List->Length){List->Last = List->Length;lp->Next = &List->List[List->Length];List->List[List->Length].Prev = lp;List->List[List->Length].Next = (void*)0;// List->First = List->Length;// lp->Prev = &List->List[List->Length];// List->List[List->Length].Prev = (void*)0;// List->List[List->Length].Next = lp;}else{LRC_INFO *temp = lp->Next;lp->Next = &List->List[List->Length];List->List[List->Length].Prev = lp;List->List[List->Length].Next = temp;temp->Prev = &List->List[List->Length];}List->List[List->Length].Time = Time;strcpy(List->List[List->Length].LrcText, LrcText);InsertPos = (List->Length = List->Length + 1);break;}else if(i == 1){List->First = List->Length;lp->Prev = &List->List[List->Length];List->List[List->Length].Prev = (void*)0;List->List[List->Length].Next = lp;List->List[List->Length].Time = Time;strcpy(List->List[List->Length].LrcText, LrcText);InsertPos = (List->Length = List->Length + 1);// List->Last = List->Length;// lp->Next = &List->List[List->Length];// List->List[List->Length].Prev = lp;// List->List[List->Length].Next = (void*)0;// List->List[List->Length].Time = Time;// strcpy(List->List[List->Length].LrcText, LrcText); // InsertPos = (List->Length = List->Length + 1);}lp = lp->Prev;}}return InsertPos;}u32_t GetLrcInfo(char *LrcFile, u32_t FileSize, LRC_QUEUE *List) {s32_t ōffsetTime = 0;u32_t CurOffset = 0;u32_t CurTime = 0;u32_t TempOffset;int Flag = 0;memset(List, 0, sizeof(*List));// List->Length = 0;// List->First = 0;// List->Last = 0;// List->List[0].Prev = (void*)0;// List->List[0].Next = (void*)0;do {if(LrcFile[CurOffset++] == '['){if(Flag == 0)if(strncmp(&LrcFile[CurOffset], "offset:", 7) == 0){CurOffset += 7;if(LrcFile[CurOffset] == '-'){Flag = -1;}elseFlag = 1;while(LrcFile[CurOffset] != ']'){OffsetTime *= 10;OffsetTime += LrcFile[CurOffset++] - '0';}OffsetTime = OffsetTime / 10 * Flag;}if(isnumeric(LrcFile[CurOffset])){char *lrc;Flag = 1;CurTime = (((LrcFile[CurOffset] - '0') * 10 + (LrcFile[CurOffset + 1] - '0')) * 60 +((LrcFile[CurOffset + 3] - '0') * 10 + LrcFile[CurOffset + 4] - '0')) * 100;CurOffset += 5;if(LrcFile[CurOffset] == '.'){CurTime += (LrcFile[CurOffset + 1] - '0') * 10 + LrcFile[CurOffset + 2] - '0';}CurOffset++;TempOffset = CurOffset;while((LrcFile[TempOffset] == '[') && isnumeric(LrcFile[TempOffset + 1])){while((LrcFile[TempOffset] != ']') && (TempOffset < FileSize))TempOffset++;TempOffset++;}lrc = &LrcFile[TempOffset];while((LrcFile[TempOffset] != 0x0d) && (LrcFile[TempOffset] != 0x0a) && (TempOffset < FileSize))TempOffset++;LrcFile[TempOffset] = '\0';InsertList(List, CurTime + OffsetTime, lrc);}// else// {// while(LrcFile[CurOffset++] != ']');// }}} while(CurOffset < FileSize);return 0;}头文件:#ifndef __LRC_H__#define __LRC_H__#define MAX_LINE 128#define MAX_LINE_LEN 255typedef int s32_t;typedef unsigned int u32_t;typedef unsigned short u16_t;typedef unsigned char u8_t;typedef struct _LRC_INFO {u32_t Time;char LrcText[MAX_LINE_LEN];struct _LRC_INFO *Prev;struct _LRC_INFO *Next;} LRC_INFO;typedef struct {LRC_INFO List[MAX_LINE];u16_t Length;u16_t First;u16_t Last;} LRC_QUEUE;u32_t GetLrcInfo(char *LrcFile, u32_t FileSize, LRC_QUEUE *List);LRC_INFO *GetFirstOfList(LRC_QUEUE *List);#endif代码主体部分用来从lrc数据的开头开始解析时间标签,检测到一个标签就将对应的歌词提取出来,并放入一个链表中。
用于读取LRC同步歌词的类
用于读取LRC同步歌词的类用于读取LRC同步歌词的类今天心情不错,用FLASH写了一个用于读取LRC同步歌词的类,只要25行代码,完全用是用FLASH的字符串操作来分析标签的.。
下面就是这个类:package LRC{public class readlrc {public function readlrcastime(mp3playtime:Number,gc:String):String { var qsd:int;//起始点var fhz:String;for (var j:int=0; j < gc.length; j ) {if (gc.charCodeAt(j) == 58) {if (int(gc.slice(j - 2,j)) * 60 int(gc.slice(j 1,j 3)) == mp3playtime) {var i:int=0;do {i ;if (gc.charCodeAt(j i) == 93) {qsd=j i;}if (gc.charCodeAt(j i) == 13) {fhz=gc.slice(qsd 1,j i);}} while (gc.charCodeAt(j i) != 13&&j i<gc.length);}}}return fhz;}}}将此文件保存为readlrc.as。
注意:一定要把此AS文件放入一个名为LRC的文件夹中,才可以使用。
下面就用一个小示例来说明如何用这个类,新建一个AS文件,写入以下代码:package {import flash.display.MovieClip;import flash.events.Event;import .URLLoader;import .URLRequest;import flash.media.Sound;import flash.media.SoundChannel;import flash.system.System;import flash.text.T extField;import LRC.readlrc;public class mp3 extends MovieClip {private var lrcreq:URLRequest=new URLRequest("日不落.lrc");private var mp3url:URLRequest=new URLRequest("日不落.mp3");private var mymp3:Sound=new Sound();private var channel:SoundChannel=new SoundChannel();private var lrcurl:URLLoader=new URLLoader();private var tbgc:String;private var mylrc:readlrc=new readlrc();eCodePage=true;public function mp3() {mymp3.load(mp3url);channel=mymp3.play();lrcurl.load(lrcreq);lrcurl.addEventListener(PLETE,loaded);}private function enterframe(event:Event) {try {gc_txt.text=mylrc.readlrcastime(int(channel.position/1000),t bgc);} catch (error:Error) {//}}private function loaded(event:Event) {tbgc=String(event.target.data);addEventListener(Event.ENTER_FRAME,enterframe);}}}保存此文件为mp3.as。
MP3歌词lrc文件读取算法 C语言源程序
·为了向后兼容,应对未定义的新标签作忽略处理。另应对注释标签([:])后的同一行内 容作忽略处理。 应允许一行中存在多个标签,并能正确处理。应能正确处理未排序的标签。
Lrc 歌词文本中含有两类标签:
一是标识标签,其格式为“[标识名:值]”主要包含以下预定义的标签: [ar:歌手名]、[ti:歌曲名]、[al:专辑名]、[by:编辑者(指 lrc 歌词的制作人)]、[offset: 时间补偿值] (其单位是毫秒,正值表示整体提前,负值相反。这是用于总体调整显示快慢 的,但多数的 MP3 可能不会支持这种标签)。
fclose(fp);
m_bValid = TRUE;
return TRUE; }
void CLyric::UnLoad() {
m_bValid = FALSE; m_csArtist.Empty(); m_csTitle.Empty(); m_csAlbum.Empty(); m_csBy.Empty(); m_lOffset = 0;
return FALSE; }
WORD wUnicode_tag = 0; DWORD dwReadLen = 0; try {
dwReadLen = fread((void *)&wUnicode_tag, sizeof(WCHAR), 1, fp); } catch (...) {
printf("CLyric::Load: fread Exception: %s!\r\n", szFileName); fclose(fp); return FALSE; } if (dwReadLen != 1) { printf("CLyric::Load: read file %s failed!\r\n", szFileName); fclose(fp); return FALSE; } if (wUnicode_tag == 0xfeff) // unicode file { fclose(fp); fp = _tfopen(szFileName, _T("rb")); if (fp == NULL) {
c#歌词同步
C# 歌词同步可以桌面字幕透明显示,这个前一篇说过同步就是判断歌曲当前播放时间去匹配歌词文件里的时间段标志。
很简单的,当然你还可以自动下载歌曲的歌词并同步。
FormLrc 是显示歌词的窗体:using System.IO;using System.Text.RegularExpressions;using System.Collections;namespace MyPlayer{public partial class FormLrc : Form{public FormLrc(){TimerLrc2.Interval = 60;TimerLrc2.Tick += new EventHandler(TimerLrc2_Tick);InitializeComponent();this.DoubleBuffered = true;this.TopMost = true;timerMask.Tick += new EventHandler(timerMask_Tick);timerLrc.Tick += new EventHandler(timerLrc_Tick);timerLrc.Interval = 100;timerMask.Interval = 200;InitializeComponent();timerMask.Enabled = true;}/// <summary>/// 结构体/// </summary>struct Lrc{public Lrc(string _sTime, string _sText){sTime = _sTime;sText = _sText;}public string sTime;public string sText;public override string ToString(){return sTime + sText;}}System.Windows.Forms.Timer TimerLrc2 = new Timer(); ArrayList LrcList = new ArrayList();string sFile = "刘德华- 冰雨.lrc";string sOut = "";string sText = "";DateTime LrcTime = new DateTime();Graphics g;Bitmap bmp = new Bitmap(1024, 500);Bitmap bmpMask = new Bitmap(1024, 500);Graphics BmpG;Graphics MaskGr;Point p = new Point(2, 2);Point p1 = new Point(4, 4);Point p2 = new Point(3, 3);Brush b = new SolidBrush(Color.Red);Brush bMask = new SolidBrush(Color.Yellow);// mask color Brush b2 = new SolidBrush(Color.FromArgb(11, 211, 1, 1));//Font f = new Font("Times New Roman", 44, FontStyle.Bold); Font f = new Font("宋体", 44, FontStyle.Bold);Font f2 = new Font("宋体", 44, FontStyle.Bold);Color transColor = new Color();MemoryStream ms = new MemoryStream();int TextIndex = 0; //ths need mask text indexSystem.Windows.Forms.Timer timerMask = new Timer(); System.Windows.Forms.Timer timerLrc = new Timer();public static string SongCurrentTime = "00:00.00";private void FormTest_Load(object sender, EventArgs e) {this.Size = Screen.PrimaryScreen.Bounds.Size;//LoadLrc("刘德华- 冰雨.lrc");}public void LoadLrc(string _sFile){OpenFile(_sFile);TimerLrc2.Enabled = true;// StartShowLrc("刘德华- 冰雨.lrc");}private void button1_Click(object sender, EventArgs e) {timerMask.Enabled = false;richTextBox1.T ext = sText;panel1.Visible = true;// timerMask.Enabled = (!timerMask.Enabled); }protected override void OnPaint(PaintEventArgs e){// if (sText == "") { return; }g = e.Graphics;bmp = new Bitmap(1024, 500); ;//reset imagebmpMask = new Bitmap(1024, 124);// ms = new MemoryStream();//resetBmpG = Graphics.FromImage(bmp);transColor = bmp.GetPixel(1, 1);this.TransparencyKey = transColor;g.Clear(transColor);//g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;//g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;//BmpG.TextRenderingHint = System.Drawing.Text.T extRenderingHint.AntiAliasGridFit;g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;BmpG.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;BmpG.DrawString(sText, f2, b2, p1);BmpG.DrawString(sText, f, b2, p2);BmpG.DrawString(sText, f, b, p);//Masktry{BmpG.DrawString(sText.Substring(0, TextIndex), f2, bMask, p);// MaskGr.DrawString(sText.Substring(0, TextIndex), f, b2, p);}catch (Exception ex) { }bmp.MakeTransparent(Color.White);// bmp.Save(ms, ImageFormat.Bmp);g.DrawImage(bmp, p);// g.DrawImage(bmp, p);//this.BackgroundImage =(Image) bmp;//pictureBox1.Image = bmp;sOut += "Paint \r\n";// base.OnPaint(e);// BmpG.Dispose();}protected override void OnPaintBackground(PaintEventArgs e){// e.Graphics.DrawString("wgscd 的说法的", f, b, p);//this.TransparencyKey = transColor;sOut += "Back Paint\r\n";base.OnPaintBackground(e);}private void FormTest_Click(object sender, EventArgs e){}#region "拖动窗体"private bool moveFlag = false;private int x = 0;private int y = 0;protected override void OnMouseMove(System.Windows.Forms.MouseEventArgs e) {if (moveFlag && (e.Button == MouseButtons.Left))this.SetBounds(Left + e.X - x, Top + e.Y - y, this.Width, this.Height);base.OnMouseMove(e);}protected override void OnMouseDown(System.Windows.Forms.MouseEventArgs e) {if (!moveFlag && e.Clicks >= 1)moveFlag = true;x = e.X;y = e.Y;base.OnMouseDown(e);}protected override void OnMouseUp(System.Windows.Forms.MouseEventArgs e) {if (moveFlag)moveFlag = false;base.OnMouseUp(e);}#endregionvoid timerMask_Tick(object sender, EventArgs e){// if (sText.Trim() == "") { return; };this.Refresh();TextIndex++;if (TextIndex > sText.Length){TextIndex = 0;}}void timerLrc_Tick(object sender, EventArgs e){//LrcTime = new DateTime(2009, 10, 10, 0, 0, 0, 0);//if (TextIndex > sText.Length)//{// TextIndex = 0;//}}private void button2_Click(object sender, EventArgs e) {if (richTextBox1.Text.Trim() == "") { return; }sText = richTextBox1.Text;panel1.Visible = false;timerMask.Enabled = true;}/// <summary>//////读取歌词/// </summary>void OpenFile(string _strFile){if (!File.Exists(_strFile)){MessageBox.Show("File not fount");return;}string sContent = "";Stream oStream = null;StreamReader oReader = null;string AllLines = "";string oneLine = "";try{oStream = File.Open(_strFile, FileMode.OpenOrCreate, FileAccess.ReadWrite);oReader = new StreamReader(oStream, Encoding.GetEncoding("gb2312"));LrcList.Clear();while (!oReader.EndOfStream){oneLine = oReader.ReadLine() + "\r\n";AllLines += oneLine;regLrc(oneLine);}oReader.Close();oStream.Close();}catch (Exception ex){oReader.Close();oStream.Close();}richTextBox1.AppendText(AllLines);}/// <summary>/// match the lrcs all lines in to lrc list/// </summary>/// <param name="strInput"></param>/// <returns></returns>string regLrc(string strInput){//string sPattner="(?<t>\\[\\d[^[]+])(?<w>.*\r\n)";string sPattner = "(?<t>\\[\\d.*\\]+)(?<w>[^\\[]+\r\n)";Regex reg = new Regex(sPattner);foreach (Match mc in reg.Matches(strInput)){richTextBox1.AppendText(mc.Groups["t"].ToString() + "\r\n");richTextBox1.AppendText(mc.Value + "\r\n");LrcList.Add(new Lrc(mc.Groups["t"].ToString(), mc.Groups["w"].ToString()));}return "";}/// <summary>/// 通过时间找歌词/// </summary>/// <param name="_sTime"></param>/// <returns></returns>string m = "";Lrc findMatch = new Lrc();public string FindLrc(string _sTime){foreach (Lrc w in LrcList){// MessageBox.Show(w.ToString());if (w.sTime.Contains(_sTime.Substring(0, 6))){if (findMatch.Equals(w)) return "";findMatch = w;sText = w.sText;m += w.sText;return w.sText;}}return "";}private void showWord(object sender, EventArgs e){//[03:26.03][02:50.67][01:16.59]冷冷的冰雨在脸上胡乱的拍// FindLrc2("[02:50.67]");MessageBox.Show(m);}DateTime d1 = new DateTime(1900, 1, 1, 0, 0, 0, 0);DateTime d2;DateTime d3;TimeSpan tsp;string tTime;public void TimerLrc2_Tick(object sender, EventArgs e){FindLrc(ClassCommon.SongCrrentTime);return;//if (sText.Trim() == "") { return; };d2 = DateTime.Now;if (d1.Year == 1900)d1 = d2;return;}TimerLrc2.Enabled = false;tsp = (TimeSpan)(d2 - d1);d3 = new DateTime(1900, 1, 1, 0, tsp.Minutes, tsp.Seconds, liseconds);tTime = d3.ToString("[mm:ss.ff]");ClassCommon.SongCrrentTime = tTime;FindLrc(tTime);this.Text = tTime;TimerLrc2.Enabled = true;}}}FormLrc 界面类(FormLrc.Designer.cs):namespace MyPlayer{partial class FormLrc{/// <summary>/// Required designer variable./// </summary>private ponentModel.IContainer components = null;/// <summary>/// Clean up any resources being used./// </summary>/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>protected override void Dispose(bool disposing){if (disposing && (components != null)){components.Dispose();base.Dispose(disposing);}#region Windows Form Designer generated code/// <summary>/// Required method for Designer support - do not modify/// the contents of this method with the code editor./// </summary>private void InitializeComponent(){this.button1 = new System.Windows.Forms.Button();this.panel1 = new System.Windows.Forms.Panel();this.button2 = new System.Windows.Forms.Button();this.richTextBox1 = new System.Windows.Forms.RichTextBox();this.panel1.SuspendLayout();this.SuspendLayout();//// button1//this.button1.ForeColor = System.Drawing.SystemColors.HotTrack;this.button1.Location = new System.Drawing.Point(292, 169); = "button1";this.button1.Size = new System.Drawing.Size(169, 62);this.button1.TabIndex = 1;this.button1.Text = "添加字幕";eVisualStyleBackColor = true;this.button1.Click += new System.EventHandler(this.button1_Click);//// panel1//this.panel1.BackColor = System.Drawing.SystemColors.ActiveCaption;this.panel1.Controls.Add(this.button2);this.panel1.Controls.Add(this.richTextBox1);this.panel1.Location = new System.Drawing.Point(110, 31); = "panel1";this.panel1.Size = new System.Drawing.Size(511, 77);this.panel1.TabIndex = 3;this.panel1.Visible = false;//// button2//this.button2.BackColor = System.Drawing.SystemColors.MenuHighlight;this.button2.ForeColor = System.Drawing.SystemColors.InactiveBorder;this.button2.Location = new System.Drawing.Point(432, 3); = "button2";this.button2.Size = new System.Drawing.Size(75, 70);this.button2.TabIndex = 1;this.button2.Text = "确定";eVisualStyleBackColor = false;this.button2.Click += new System.EventHandler(this.button2_Click);//// richTextBox1//this.richTextBox1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(64)))));this.richTextBox1.Font = new System.Drawing.Font("SimHei", 12F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134)));this.richTextBox1.ForeColor = System.Drawing.SystemColors.MenuHighlight;this.richTextBox1.Location = new System.Drawing.Point(4, 3); = "richTextBox1";this.richTextBox1.Size = new System.Drawing.Size(422, 70);this.richTextBox1.TabIndex = 0;this.richTextBox1.Text = "";//// FormTest//this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;this.ClientSize = new System.Drawing.Size(867, 318);this.Controls.Add(this.panel1);this.Controls.Add(this.button1);this.Cursor = System.Windows.Forms.Cursors.Hand;this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None; = "FormTest";this.Text = "FormTest";this.Load += new System.EventHandler(this.FormTest_Load);this.Click += new System.EventHandler(this.FormTest_Click);this.panel1.ResumeLayout(false);this.ResumeLayout(false);}#endregionprivate System.Windows.Forms.Button button1;private System.Windows.Forms.Panel panel1;private System.Windows.Forms.RichTextBox richTextBox1;private System.Windows.Forms.Button button2;}}播放窗体类(FormPlayer.cs ):using System;using System.Collections.Generic;using ponentModel;using System.Data;using System.Drawing;using System.Text;using System.Windows.Forms;namespace MyPlayer{public partial class FormPlayer : Form{Song Music = new Song();public FormPlayer(){InitializeComponent();}private void button1_Click(object sender, EventArgs e){OpenFileDialog openFileDialog = new OpenFileDialog();openFileDialog.InitialDirectory = "c:\\";openFileDialog.Filter = "mp3文件|*.mp3|wma文件|*.wma|wav文件|*.wav";openFileDialog.RestoreDirectory = true;openFileDialog.FilterIndex = 1;openFileDialog.InitialDirectory = "E:\\music";if (openFileDialog.ShowDialog() == DialogResult.OK){timer1.Stop();Music.Stop();string fName = openFileDialog.FileName;textBox1.Text = fName;Music.FilePath = textBox1.Text;//将文件路径赋给播放类中的FilePath;Music.OpenFile();//打开播放文件,准备播放;textBox2.Text = Music.FileName;//获取文件名;textBox3.Text = Music.Duration.ToString();//获取文件长度;textBox6.Text = Music.Status().ToString();//获取文件状态;trackBar1.Value = 50;Music.SetVolume(500);//将音量设置成500;textBox5.Text = Music.TheVolume().ToString();//获取音量;timer1.Start();}}private void button3_Click(object sender, EventArgs e){Music.Pause();//暂停;textBox6.Text = Music.Status().ToString();}private void button4_Click(object sender, EventArgs e){Music.Stop();//停止;timer1.Enabled = false;textBox6.Text = Music.Status().ToString();}private void button5_Click(object sender, EventArgs e){Music.PlayHalfSpeed();//半速播放;textBox6.Text = Music.Status().ToString();}private void button6_Click(object sender, EventArgs e){Music.PlayDoubleSpeed();//倍速播放;textBox6.Text = Music.Status().ToString();}private void timer1_Tick(object sender, EventArgs e){//获取文件当前播放位置;textBox4.Text = Convert.ToString(Music.CurrentPosition);//textBox4.Text = Convert.ToString(Music.CurrentPosition.ToString("mm:ss:ff"));//textBox4.Text = (Music.CurrentPosition / 1000 / 60).ToString("00") + ":" + (Music.CurrentPosition / 1000 / 60).ToString("00");// Complete number of secondsint s = Music.CurrentPosition;TimeSpan t = new TimeSpan(1900,0,0,0,s);ClassCommon.SongCrrentTime = t.Minutes.ToString("00") + ":" + t.Seconds.ToString("00") + "." + liseconds.ToString("00");textBox4.Text = t.Minutes.ToString("00") + ":" + t.Seconds.ToString("00") + ":" + liseconds.ToString("00");return;// Seconds to displayint ss = s % 60;// Complete number of minutesint m = (s - ss) / 60;// Minutes to displayint mm = m % 60;// Complete number of hoursint h = (m - mm) / 60;// Make "hh:mm:ss"textBox4.Text = h.ToString("D2") + ":" + mm.ToString("D2") + ":" + ss.ToString("D2");}private void button7_Click(object sender, EventArgs e){//设置指定位置开始播放;Music.CurrentPosition = Convert.ToInt32(textBox8.T ext);}private void button9_Click(object sender, EventArgs e){//进行录音;Music.Record(Convert.ToInt32(textBox9.Text));}private void button10_Click(object sender, EventArgs e){Music.VolumeOn();//取消静音;}private void button11_Click(object sender, EventArgs e){Music.VolumeOff();//静音;}private void trackBar1_Scroll(object sender, EventArgs e) {//设置音量;Music.SetVolume(Convert.ToInt32(trackBar1.Value * 10));textBox5.Text = Music.TheVolume().ToString();}private void FormPlayer_Load(object sender, EventArgs e) {}FormLrc oFormLrc = new FormLrc();private void button2_Click(object sender, EventArgs e){//if(Music.FilePath){}Music.OpenFile();Music.Play();//播放;// oFormLrc.LoadLrc("刘德华- 冰雨.lrc");oFormLrc.LoadLrc("刘德华-天意.lrc");oFormLrc.Show();timer1.Enabled = true;textBox6.Text = Music.Status().ToString();}}}播放窗体界面类(FormPlayer.Designer.cs):namespace MyPlayer{partial class FormPlayer{/// <summary>/// 必需的设计器变量。
控制台C语言编写音乐播放器主要功能
控制台C语言编写音乐播放器主要功能音乐播放器是一种可以播放音乐文件的应用程序,具有多种功能来帮助用户管理和播放音乐。
下面是控制台C语言编写音乐播放器的主要功能:1.音乐:用户可以通过输入歌曲名或艺术家来他们想要播放的音乐。
功能也可以根据关键词进行过滤和排序。
2.音乐播放:一旦用户找到他们想要听的歌曲,他们可以选择播放它。
音乐播放器应该能够打开并解码音乐文件,然后通过音频设备播放音乐。
4.歌曲信息显示:在播放音乐的同时,音乐播放器应该显示歌曲的相关信息,如歌曲名、艺术家、专辑名、歌词等。
这些信息可以从音乐文件的元数据中获取。
5.音乐控制:音乐播放器应该具有基本的音乐控制功能,如播放、暂停、停止、上一首、下一首等。
用户可以通过命令来控制音乐的播放状态。
6.音量控制:用户可以通过增加或减少音量来调整音乐的音量。
音乐播放器应该能够调整音频设备的输出音量。
7.重复和随机播放:用户可以选择歌曲的播放模式,如顺序播放、循环播放、随机播放等。
音乐播放器应该能够根据用户的选择来调整播放模式。
9.用户界面:音乐播放器应该有一个简洁而直观的用户界面,允许用户轻松地浏览、和管理自己的音乐。
用户界面可以使用控制台界面或基于文本的界面。
10.错误处理:音乐播放器应该能够处理各种错误情况,如无效的输入、文件不存在、解码错误等。
它应该能够向用户显示有关错误的信息,并采取适当的措施来避免应用程序崩溃。
11.文件格式支持:音乐播放器应该支持多种常见的音乐文件格式,如MP3、WAV、FLAC等。
它应该能够根据文件格式选择合适的解码器。
12.常规设置:音乐播放器应该具有一些常规设置,如默认音量、默认播放模式、快捷键等。
用户可以根据自己的需求来调整这些设置。
以上是控制台C语言编写音乐播放器的主要功能。
根据以上功能,你可以开始设计和编写一个简单的音乐播放器应用程序。
当然,你可以根据个人需求和编程能力来扩展和优化这些功能。
C语言实现歌曲信息管理系统
C语⾔实现歌曲信息管理系统本⽂实例为⼤家分享了C语⾔实现歌曲信息管理系统的具体代码,供⼤家参考,具体内容如下系统功能:该系统以菜单⽅式⼯作,歌曲信息包括:歌名、演唱者、作词、作曲、所属专辑、出版时间、出版公司。
试设计⼀歌厅歌曲管理系统,使之能提供以下功能:歌曲信息录⼊、修改、插⼊、删除功能;歌曲排序浏览功能;按歌名查询、按演唱者查询等功能。
完整的实现代码如下:#include "stdio.h"#include "stdlib.h"#include "string.h"//歌曲信息包括:歌名、演唱者、作词、作曲、所属专辑、出版时间、出版公司typedef struct music{char name[20]; //歌名char singer[20]; //演唱者char authors[20]; //作词char compose[30]; //作曲char album[20]; //所属专辑char time[15]; //出版时间char company[30]; //出版公司struct music *next;}music;music *head=NULL;int length; //链表的长度void create(){music *p1,*p2;length=0;p1=(music *)malloc(sizeof(music));strcpy(p1->name,"-1");if(head==NULL)head=p1;printf("请输⼊⾳乐的歌名、演唱者、作词、作曲、所属专辑、出版时间、出版公司:/n");while(1) //歌名为0的时候退出{p2=(music *)malloc(sizeof(music));//输⼊歌曲信息scanf("%s %s %s %s %s %s %s",p2->name,p2->singer,p2->authors,p2->compose,p2->album,p2->time,p2->company);if(strcmp(p2->name,"0")==0){printf("链表创建完成!/n");break;}length++; //链表的长度p1->next=p2;p2->next=NULL;p1=p1->next;}return ;}void ModifymusicInfo(){music *p=head->next;char name[20];printf("请输⼊要修改的歌曲的歌名:");getchar();scanf("%s",name);while(p!=NULL){if(strcmp(p->name,name)==0){printf("修改前,歌名为%s的歌曲的信息如下:/n",name);printf("⾳乐的歌名、演唱者、作词、作曲、所属专辑、出版时间、出版公司:/n");printf("%s %s %s %s %s %s %s/n",p->name,p->singer,p->authors,p->compose,p->album,p->time,p->company);printf("请输⼊歌曲的新的所属专辑:");getchar();scanf("%s",p->album);printf("请输⼊歌曲的新出版公司:");getchar();printf("⾳乐的歌名、演唱者、作词、作曲、所属专辑、出版时间、出版公司:/n");printf("%s %s %s %s %s %s %s/n",p->name,p->singer,p->authors,p->compose,p->album,p->time,p->company); return ;}p=p->next;}if(p==NULL){printf("该歌曲不存在!/n");return ;}}void display(){music *p=head->next;printf("链表中所有的歌曲信息如下:/n");printf("⾳乐的歌名、演唱者、作词、作曲、所属专辑、出版时间、出版公司:/n");while(p!=NULL){printf("%s %s %s %s %s %s %s/n",p->name,p->singer,p->authors,p->compose,p->album,p->time,p->company); p=p->next;}return ;}void search(){int num,x,flag;char name[20];music *p=head->next;printf("请选择查询的⽅式:/n");printf("1、按歌名查询/t 2、按演唱者查询/n");scanf("%d",&x);if(x==1){printf("需要查找的歌曲歌名为:");getchar();scanf("%s",name);while(p!=NULL){if(strcmp(p->name,name)==0){printf("歌名为%s的歌曲的信息如下:/n",name);printf("⾳乐的歌名、演唱者、作词、作曲、所属专辑、出版时间、出版公司:/n");printf("%s %s %s %s %s %s %s/n",p->name,p->singer,p->authors,p->compose,p->album,p->time,p->company); return ;}p=p->next;}if(p==NULL)printf("没有这⾸歌曲的记录!/n");}else if(x==2){flag=0;printf("需要查找的演唱者为:");getchar();scanf("%s",name);p=head->next;while(p!=NULL){if(strcmp(p->singer,name)==0){if(flag==0){printf("演唱者为%s的歌曲的信息如下:/n",name);printf("⾳乐的歌名、演唱者、作词、作曲、所属专辑、出版时间、出版公司:/n");flag=1;}printf("%s %s %s %s %s %s %s/n",p->name,p->singer,p->authors,p->compose,p->album,p->time,p->company); }p=p->next;}if(p==NULL && flag==0)printf("没有该演唱者的歌曲记录!/n");return;}}return ;}void insert(){int num,i;music *p,*q;p=head;printf("请输⼊你要插⼊位置: ");scanf("%d",&num);if(num>length){printf("找不到要插⼊的位置/n");return ;}else{printf("请输⼊你要插⼊的⾳乐的歌名、演唱者、作词、作曲、所属专辑、出版时间、出版公司:/n");q=(music *)malloc(sizeof(music));//输⼊歌曲信息scanf("%s %s %s %s %s %s %s",q->name,q->singer,q->authors,q->compose,q->album,q->time,q->company); while(p!=NULL){if(strcmp(p->name,q->name)==0){printf("该歌曲已经存在,⽆法插⼊!/n");return ;}p=p->next;}p=head;for(i=0;i<num;i++)p=p->next;q->next=p->next;p->next=q;length++;printf("插⼊成功!/n");return ;}}void Delete(){char name[20];music *p,*q;q=head,p=head->next;printf("请输⼊要删除的歌曲的歌名:/n");getchar();scanf("%s",name);while(p!=NULL){if(strcmp(p->name,name)==0){q->next=p->next;free(p);length--;printf("删除成功!/n");return ;}p=p->next;q=q->next;}if(p==NULL){printf("找不到要删除的歌曲!/n");return ;}}void menu()printf("________________________________________________________________/n"); printf("| 歌厅歌曲管理系统 |/n");printf("| 0、退出系统 |/n");printf("| 1、录⼊歌曲信息 |/n");printf("| 2、显⽰歌曲信息 |/n");printf("| 3、查找链表中的某⼀⾸歌曲 |/n");printf("| 4、删除链表中指定歌曲 |/n");printf("| 5、指定的位置上插⼊⼀个新结点 |/n");printf("| 6、修改歌曲信息 |/n");printf("________________________________________________________________/n"); return ;}int main(void){int a;menu();while(1){printf("请选择相应的功能:");scanf("%d",&a);switch(a){case 0:return 0;case 1:create();menu();break;case 2:if(head){display();menu();}else{printf("链表为空,请先建⽴链表!/n");menu();}break;case 3:if(head){search();menu();}else{printf("链表为空,请先建⽴链表!/n");menu();}break;case 4:if(head){Delete();menu();}else{printf("链表为空,请先建⽴链表!/n");menu();}break;case 5:if(head){insert();menu();}else{printf("链表为空,请先建⽴链表!/n");menu();}case 6:if(head){ModifymusicInfo();menu();}else{printf("链表为空,请先建⽴链表!/n");menu();}break;default:break;}}system("pause");return 0;}以上就是本⽂的全部内容,希望对⼤家的学习有所帮助,也希望⼤家多多⽀持。
基于C语言的KTV点歌系统
基于C语⾔的KTV点歌系统更换了编辑器为markdown,来码⼀个以前的编程作业,KTV点歌系统.模拟KTV点歌系统。
⽤户可按歌名查找某⾸歌曲或按歌⼿名查找其所有歌曲,点歌后显⽰所点歌曲歌词。
管理员可添加和删除歌曲,每个歌曲的歌词⽤⼀个单独的⽂件存储。
查询歌⼿的歌曲功能、查询歌曲的歌词功能、登录管理员功能、增添歌曲及其歌词功能、删除歌曲(⽂件)功能、修改管理员密码功能等把歌⼿以其姓名⽂件命名,⽂件内每⾏都是他(她)的⼀⾸歌曲,⽂件夹名singer;歌词以其歌曲⽂件命名,⽂件内是歌词内容,⽂件夹名song。
查询/删除歌曲:可选择下图1的所有内容。
查询歌⼿:可选择下图2的所有内容。
登录管理员:登录密码是初始化的 'admin123'。
增添歌曲:歌曲名直接输⼊、歌词内容直接复制粘贴(末尾⾏要输⼊quit然后回车)函数总体上是很简单的,除了登录函数login返回⼀个int整数表⽰不同的登录状态,其他的函数都是⽆传参、⽆返回值的。
main函数的第⼀个while循环,根据输⼊数字选择功能,以实现下⾯三个功能:查询歌曲歌词(select_song函数)、查询歌⼿歌曲(select_singer函数)、登录功能(login函数);如果没输⼊1 2 3就退出程序。
值得注意的是登录函数返回值传给state变量保存,登陆成功后state == 1。
main函数第⼆个while循环便是依据state变量判断是否已登录,如果没有登录就直接结束了程序,state为1就作为管理员运⾏第⼆个while循环。
这个循环和上⼀个循环的实现异曲同⼯,完全是根据输⼊选择不同功能,输⼊⾮1 2 3就结束循环退出程序。
基于GCC编译器或VC6编译器。
⽂本⽂件是UTF-8编码,如果⽤cmd直接执⾏会显⽰乱码,我⽤的是cmder前端命令⾏⼯具,它⽀持UTF-8编码,可以正常显⽰。
⽂件夹结构:1. 点歌结果2. 查询歌⼿3. 登录管理员可以清楚地看到,登录初始密码为admin123,登陆后会进⼊管理员界⾯,可以选择添加歌曲、删除歌曲、更改密码等功能。
LRC 歌词同步 [FL基理]
LRC 歌词同步 [FL基理]一、准备工作既然要制作歌词同步程序,首先要准备一首歌,我们就以“周杰伦-青花瓷”为例。
首先要下载这首“青花瓷.mp3”,保存为“C:\My Player\Music\青花瓷.mp3”。
还要下载青花瓷的LRC 文件,大家可以到网上下载(地址见附录),将文本内容保存为“C:\My Player\LRC\青花瓷.lrc”。
我们的程序(类和FLA)则保存在“C:\My Player\”文件夹下。
青花瓷.lrc 文件:--------------------------------------------------------------------------------[ti:青花瓷][ar:周杰伦][al:我很忙][by:张琪][00:00.00]发送短信18到291199下载该歌曲到手机[00:01.11]青花瓷[03:36.49][00:21.39]素眉勾勒秋千话北风龙转丹[00:26.08]屏层鸟绘的牡丹一如你梳妆[00:30.46]黯然腾香透过窗心事我了然[00:34.93]宣纸上皱边直尺各一半[00:39.49]油色渲染侍女图因为被失藏[00:43.83]而你嫣然的一笑如含苞待放[00:48.30]你的美一缕飘散[00:50.77]去到我去不了的地方[02:23.97][00:55.77][03:01.92][02:25.63][00:56.90]天正在等烟雨[03:03.57][02:27.91][00:58.99]而我在等你[03:05.92][02:30.44][01:00.93]炊烟袅袅升起[03:07.76][02:32.25][01:03.49]隔江千万里[03:10.36][02:34.85][01:05.84]在平地书刻你房间上的飘影[03:14.67][02:38.73][01:09.87]就当我为遇见你伏笔[03:18.83][02:43.35][01:14.34]天正在等烟雨[03:21.20][02:45.60][01:16.68]而我在等你[03:23.71][02:48.01][01:18.99]月色被打捞起[03:25.74][02:50.10][01:21.18]掩盖了结局[03:28.33][02:52.54][01:23.72]如传世的青花瓷在独自美丽[03:32.30][02:56.67][01:27.65]你眼的笑意[01:50.25]色白花青的景已跃然于碗底[01:54.69]临摹宋体落款时却惦记着你[01:59.22]你隐藏在药效里一千年的秘密[02:03.75]急溪里犹如羞花沾落地[02:08.32]林外芭蕉惹咒语[02:10.57]梦幻的铜绿[02:12.84]而我路过那江南小镇的等你[02:17.19]在泼墨山水画里[02:19.75]你从墨色深处被隐去--------------------------------------------------------------------------------大家也可以把这个文本内容复制下来,然后在“C:\My Player\LRC\”下创建一个文本文档,将内容粘贴上去,再将文档保存为“青花瓷.lrc”,注意扩展名是“.lrc”。
Qt实现歌词显示
Qt实现歌词显示思路很简单:就是用两个相同的QLable,只是两个的样式不一样,设置相同的位置,覆盖显示就可以了.Widget.cpp(基于QWidget)代码:#include"widget.h"#include"ui_widget.h"#include#include#include#includeQLabel*label,*label2;QTimer*timer;int iCount=0;Widget::Widget(QWidget*parent):QWidget(parent),ui(new Ui::Widget){ui->setupUi(this);label=new QLabel(this);label->move(80,120);label2=new QLabel(this);label2->move(80,120);label2->setStyleSheet("color:red");label->setText("这是一行文本,看看会不会有歌词效果呢。
");timer=new QTimer(this);//计时器timer->start(100);//计时周期100ms,并开始计时connect(timer,SIGNAL(timeout()),this,SLOT(on_changeT ext()) );}Widget::~Widget(){delete ui;}void Widget::on_changeT ext(){int iLength=label->text().length();iCount=(iCount+1)%iLength;label2->setT ext(label->text().mid(0,iCount+1)); label2->adjustSize();}Widget.h代码:#ifndef WIDGET_H#define WIDGET_H#include#includenamespace Ui{class Widget;}class Widget:public QWidget{Q_OBJECTpublic slots:void on_changeText();public:explicit Widget(QWidget*parent=0);~Widget();private:Ui::Widget*ui;};#endif//WIDGET_H Main函数的代码不变。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
C语言技术报告班级:学号:姓名:项目说明:1、本程序在VC环境下调试成功环境:需安装千千静听到默认路径下,同时安装vc6.0环境2、本程序完成功能:完成LRC格式歌词的文件读取、解析、结构体创建等,同时在屏幕上将解析出的歌词进行实时显示基本功能实现:1、读取文件,能正确打印到屏幕上。
2、切割字符串,正确存到一个结构体数组中。
3、能正确判断歌曲的歌名与演唱者,并打印出来。
4、实现模拟时钟的功能。
5、按顺序将歌词输出。
6、能滚动显示歌词。
7、能与时间匹配突出显示歌词的颜色。
项目流程:1、首先将歌词文件内容通过fopen全部读到一个数组song_word[][]中,然后将歌词内容保存到数组song_word1[][]。
2、通过函数message_song1()判断歌曲的歌名与演唱者,并打印出前4行的歌曲信息。
3、用函数message_song2()和strtok将同一行的歌词和时间一一对应的进行切割,并分别保存在数组lyric[][]和time[][]中,并通过Sleep(1000)将时间标签转换为以s为单位的模拟时钟。
4、在每一秒内对将时钟的时间与歌词前面的时间相比较,如果相等那么就输出当前时间后面的歌词,实现了歌词与时间的同步显示。
5、歌词的前5行依次输出,当歌词大于5行时,用函数MoveText(22,6,20,5,22,5);实现将歌词整块移动并每次显示5行。
7、GoToXY(22,m)和SetText_Color(2)实现将光标定位在当前行并且显示相应的颜色,使已显示的歌词变为白色,而刚出现的歌词变为相应的颜色。
6、在主程序运行时即可启动千千静听ShellExecute( NULL, NULL, "TTPlayer.exe",".\\LRC\\简单爱.mp3",NULL,1);代码:#include<string.h>#include<stdio.h>#include<stdlib.h>#include<time.h>#include<windows.h>#include"console.h"//跳转到光标指定位置void GoToXY(int x,int y){HANDLE h1;COORD pos;pos.X=x;pos.Y=y;h1=GetStdHandle(STD_OUTPUT_HANDLE);SetConsoleCursorPosition(h1,pos);}//屏幕整块移动//参数:startx、starty:要移动的块的起始坐标// sizex、sizey:块的大小// destx、desty: 目标坐标,即要移动到得位置void MoveText(int startx, int starty, int sizex, int sizey, int destx, int desty) {SMALL_RECT rc = {startx, starty,startx+sizex, starty+sizey};COORD crDest = {destx, desty};CHAR_INFO chFill;HANDLE hout = GetStdHandle(STD_OUTPUT_HANDLE);CONSOLE_SCREEN_BUFFER_INFO bInfo;GetConsoleScreenBufferInfo(hout, &bInfo);chFill.Attributes = bInfo.wAttributes;chFill.Char.AsciiChar =' ';ScrollConsoleScreenBuffer(hout, &rc, NULL, crDest, &chFill);}/*第一个值为背景色,第二个值为前景色:0 = 黑8 = 灰1 = 蓝9 = 淡蓝2 = 绿 A = 淡绿3 = 湖蓝B = 淡浅绿4 = 红 C = 淡红5 = 紫 D = 淡紫6 = 黄 E = 淡黄7 = 白 F = 亮白*///设置接下来终端显示文本的背景色和文本颜色void SetText_Color(int color){HANDLE hStdout;hStdout = GetStdHandle(STD_OUTPUT_HANDLE);SetConsoleTextAttribute(hStdout,color);}void message_song1(char song[4][100])//切割前四行{int i;char delims[]="[:]";char *result=NULL;char *sign[4]={"歌名","歌手","制作","专辑"};for(i=0;i<4;i++){strtok(song[i],delims);result= strtok(NULL,delims);printf("%s %s\n",sign[i],result);}printf("\n\n");}void message_song2(char song1[100][100],char time[100][100] ,char lyric[100][100] )//切割歌词和时间{int i,j,k1=0,k2=0,min=0,tsec=0,gsec=0,b=6;int flag=0,m=5;char delims1[]="[]";char *result1=NULL;for(j=0;song1[j][0]!='\0';j++){result1=strtok(song1[j],delims1);for(i=0;result1!=NULL;i++){if(i%2==0){strcpy(time[k1],result1);result1=strtok(NULL,delims1);k1++;}if(i%2==1){strcpy(lyric[k2],result1);result1=strtok(NULL,delims1);k2++;}}}j=0;for(i=0;;i++){GoToXY(22,5);printf("The time is:0%d:%d%d ",min,tsec,gsec);Sleep(1000);gsec++;if(gsec==10){tsec++;gsec=0;}if(tsec==6){min++;tsec=0;}for(j=0;j<50;j++){if((min==time[j][1]-48)&&(tsec==time[j][3]-48)&&(gsec==time[j][4]-48)){if(m<10) m+=1;else m=10;GoToXY(22,m);SetText_Color(2);printf("%s\t\t\t\t\n",lyric[j-1]);GoToXY(22,m-1);SetText_Color(7);printf("%s\t\t\t\t\n",lyric[j-2]);flag++;if(flag>=5)MoveText(22,6,20,5,22,5);}}}}void main(){FILE*fp;int i=0;char time[100][100]={0};char lyric[100][100]={0};char infile[1000]={0},song_word[100][100]={0},song_word1[100][100]={0};char *result=NULL;char delims1[]="\n";ShellExecute( NULL, NULL, "TTPlayer.exe","spring.mp3",NULL,1); //执行千千静听if((fp=fopen("spring.lrc","rb"))==NULL){printf("can not open file\n");exit(0);}while(!feof(fp)){infile[i]=fgetc(fp);i++;}infile[i]='\0';fclose(fp);printf("\n");result=strtok(infile,delims1);for(i=0;result!=NULL;i++){strcpy(song_word[i],result);result++;result=strtok(NULL,delims1);}for(i=0;i<44;i++)strcpy(song_word1[i],song_word[i+4]);message_song1(song_word);message_song2(song_word1,time,lyric);}bug调试:刚开始对切割函数strtok含有多个标识符的使用理解不透彻,经过多次改变标识符观察输出结果,也就正确理解了。
将歌词和时间切割后保存在数组中和将时钟与歌词前面的时间比较判断花费了很长的时间。
对滚屏函数MoveText(int startx, int starty, int sizex, int sizey, int destx, int desty);不太了解,经过多次改变相应的参数值观察歌词的输出移动,明白了startx、starty:要移动的块的起始坐标sizex、sizey:块的大小destx、desty: 目标坐标,即要移动到得位置,然后定义了变量flag,当flag小于5时,歌词依次输出,当flag大于5时实现将歌词整屏移动。
心得体会:通过对整个程序的编写,对用fopen读文件,函数的传递,数组的使用有了更深刻理解,还有对切割函数,光标定位函数,滚屏函数,显示颜色函数有了更新的认识。
我程序的编写是在*****多名同学的请教和讨论后完成的,他们给我的帮助很大。
每个人都有自己的编程思想,有时候也会钻进死胡同,这时候同学之间的讨论会让我们的思想碰撞出火花,有一种恍然大悟的感觉,会让我们的程序更加的简单,完美。
萧伯纳说:“倘若你有一个苹果,我也有一个苹果,而我们彼此交换这些苹果,那么你和我仍然是各有一个苹果。