Skip to content

THUCampus/MusicPlayer

Repository files navigation

MusicPlayer

开发环境

操作系统:windows 10 集成开发环境:winasm 汇编器:masm32

功能

  • WAV/MP3 等常用格式的音频文件的解码
  • 播放、暂停、上一首、下一首
  • 曲目的管理(显示所有歌曲的信息,同时提供导入本地歌曲、将歌曲从列表删除的操作)
  • 音量的选择以及静音模式的切换
  • 当前进度的展示以及进度的切换
  • 循环模式的选择(单曲循环、全部循环)
  • 歌词的实时显示
  • 根据关键词在网络上搜索歌曲
  • 根据搜索返回的结果从网络上下载歌曲

使用说明

各个部分的说明如下

图片

使用步骤如下,

0. 双击文件夹下的音乐播放器.exe

1. 点击播放按钮开始播放歌曲,如下所示

图片

2. 在搜索输入栏中输入“Coldplay"后点击搜索按钮,会提示搜索开始,并将搜索结果展示在搜索结果列表中。

图片 图片

3. 在搜索结果列表中右键单击任一歌曲,会开始下载该歌曲到本地的Music文件夹中。

图片

4.点击导入按钮,可以将本地的歌曲导入到本地歌曲列表中。

图片

5.导入该歌曲后,可以使用播放、暂停、上一首、下一首、改变进度条、切换静音、改变音量、切换循环状态等功能。也可以使用删除按钮,将该首歌曲从列表中删除。

图片

6. 在最新版的播放器中假如将与歌曲名称完全相同的lrc格式文件加入该歌曲的同级目录下,即可在播放器的下部动态显示歌词(支持多种语言)。如果在载入歌曲时没有检索到合法的lrc文件,那么歌词显示框会显示“暂无歌词”。图片

实现原理

音频文件的播放与解码

我们基于windows提供的mciSendString命令实现了音频文件的播放与界面。mciSendString能够同时支持MPEG,AVI,WAV,MP3等多种音频文件的解码与播放。

状态的控制

当前的播放状态包括停止、播放、暂停3个状态,点击播放按钮会改变播放状态,同时调用相关的mciSendString命令,最后刷新GUI上相关控件或者图片。 静音模式、循环模式、歌词是否显示也都是通过维护一个状态的方式实现。

记忆功能

在每次程序关闭前保存当前列表中的歌曲(歌曲名,路径等信息,存在结构体中)写入文件,在下一次打开程序的时候自动读写对应的文件到列表中

歌曲导入及删除功能

调用系统函数弹出选择文件对话框,选中并记录下文件信息,使用得到的文件信息向歌曲播放列表中添加列表项,删除则为删除列表项中的索引

搜索功能的实现

通过建立socket 连接(Network/search.asm/getWebResult中),构造http请求头与服务器进行通讯。服务器接收http响应解析出body返回url等歌曲信息,返回格式为“歌曲名-歌手名#歌曲id$歌曲名-歌手名#歌曲id,......”。

下载功能的实现

通过建立socket 连接(Network/search.asm/getWebResult中),构造http请求头与服务器进行通讯。服务器会返回一个url的地址,Network/download.asm中是通过调用wget程序下载该文件到本地。 而远程的服务器是利用python3的urllib库模拟发送下载请求到酷我服务器,获得对应歌曲。

进度条和音量条的实现

在窗口的事件循环中,不断检测发来的消息,当接收到WM_HSCROLL类型的消息时,判断是音量条还是进度条。 如果是音量条消息,消息分两种:SB_THUMBTRACK和SB_ENDSCROLL。分别对应滑块滑动中和滑块滑动结束的消息。考虑到音量条本身的特性,这两种消息都需要及时处理并反馈给用户。因此,只要收到这两种消息中的一种,就调用相关函数读取当前滑块的位置,并将这个位置按照一定比例转化后调用mcisendstring命令调整当前播放歌曲的音量,并刷新文字显示的音量大小。最终的效果是用户拖动音量条时音量文字和歌曲的实际音量都是随着滑块位置实时变化的。同时,音量的控制会继承到下一首音乐,这也比较符合用户的使用习惯。 静音功能通过维护一个全局变量,使得在用户选择静音状态之后,当前歌曲的音量大小将被设置为0,然后进度条和实际音量的实时关联关系也被切断来实现。 如果是进度条消息,消息也是上面提到的两种。首先,对于SB_THUMBTRACK消息(收到此消息表示进度条正在被拖动),我们参考了现有的一些音乐播放器的模式,即不会随着进度条的拖动而实时改变真正的播放进度。否则在拖动过程中的音频输出可能会比较刺耳,而且对于用户来说也没有什么意义。因此,起初我们只针对SB_ENDSCROLL(滚动结束消息)进行了处理。当收到此消息后,才真正地向音频设备发送mcisendstring命令改变实际进度。 随着开发的进一步进行,我们也需要让进度条实时根据播放进度来进行调整。因此我们在代码中又加入了一个计时器,它每隔500ms会向主窗口发送一个WM_TIMER消息,通过对这个消息进行响应,我们实现了每隔500ms就根据当前歌曲播放进度来刷新进度条进度的功能。 这时候发现了之前实现方法的一些问题:即当用户在拖动进度条调整进度时,每500ms一次的消息仍在被正常接收,这反映到ui界面上的效果便是用户在拖动进度条未松开时,滑块自己会经常跳回原来的位置。因此我们又加入了对SB_THUMBTRACK消息的处理,用一个全局变量来记录用户当前是否在拖动进度条这一状态。如果当前用户正在拖动进度条,那么则暂时不响应WM_TIMER消息引发的滑块位置变更,而文字方式显示的进度则始终保持更新。这样就解决了在拖动滑块调整进度时用户体验不好的问题,现在已经可以很流畅地调整进度了。

循环播放的实现

我们只实现了两种循环模式:单曲循环和全部循环。这也是通过维护一个全局变量来实现。在同步进度条滑块位置和实际歌曲播放进度的函数中,添加了对于歌曲是否播放完毕的判断(通过把当前播放位置和歌曲长度相比较得到,而这两者都是可以通过mcisendstring命令直接获取),当歌曲已经播放完成,那么就判断内存中维护的状态变量,如果是单曲循环,那么就通过mcisendstring命令把当前歌曲进度调整到开始位置继续播放;如果是全部循环,那么就调用跟用户主动点击“下一首”按钮时相同的函数进行相关处理。

歌词实时显示的实现

通过windows32 api CreateFile和ReadFile来打开和读写lrc文件。这里我们只到当前播放歌曲的同级目录下去查找与当前播放歌曲除扩展名外文件名完全一致的lrc文件。如果没找到或者在文件没能正确打开,则在歌词框里显示“暂无歌词”;如果成功打开文件,那么会首先将文件的所有内容读取到内存中的一个全局字符串lrcBuffer中,与此同时记录一张表,这张表的内容是每一行歌词的时间信息和与之对应的歌词内容在lrcBuffer中的地址(绝对偏移量)。 lrc文件的一般格式如下: [text] 一般出现在文件头,存储歌手、歌曲名等信息 [分钟:秒.厘秒]lyric 文件的主体内容。[]内的内容指这句歌词的时间信息,lyric是歌词实际内容 因此我们通过相关Win32接口去遍历每一个'['的位置,并以此为基础来解析文件。内存中的那张表中存的实际上也是每一个'['对应的时间(单位为毫秒,为了与mcisendstring保持一致)和每一个'['的内存偏移量。这张表的构建是在每次切换或打开新歌曲的时候立即进行。 当歌曲开始播放后,我们仍然通过接收每500ms发来一次的WM_TIMER消息来更新歌词的内容。更新的方法是每次接收到计时器消息时,都会对内存中的表进行一次顺序遍历(因为要考虑到用户可能随意拖动进度条的缘故),当查找到某一行歌词的时间信息刚好大于用mcisendstring命令获取的当前播放进度时,那么就把这行歌词的上一行歌词的信息显示到歌词框里。这里对第一行歌词进行了特殊处理,即前奏阶段在歌词框会显示“······”来表示歌曲尚未正式开始。 注:由于lrc文件有很多种变种,且网上很多lrc文件下载下来之后编码都是乱掉的,经过多种尝试也无法恢复。通过多轮筛选,我们发现酷我音乐上下载下来的歌曲自带lrc文件,且这些文件的编码正常,语法统一。因此我们均是以从酷我音乐上下载的lrc文件的具体格式作为本次开发的基准,不保证能支持任意的lrc文件。且由于时间原因我们的播放器不能识别[offset:偏移量]这样的调整歌词整体偏移量的命令。

难点和创新点

多进程

在将小组成员的功能集成的时候,出现了许多问题。比如直接在GUI中向服务器发送请求会导致GUI界面失去响应。最后解决的方式是采用多进程。我们首先将搜索功能(Network/search.asm)封装成一个可执行程序search.exe,将下载功能(Network/download.asm)封装成download.exe。然后在GUI程序中使用CreateProcess执行这些可执行程序,通过这种方式可以很好地避免GUI的卡死。但是为了能够在搜索完后自动刷新搜索结果列表,GUI是等搜索结束后才开始接受响应的。下载是真正和GUI并行的。 另外一个问题是,在GUI程序中点击导入按钮后,会使得此后无法正常打开search.exe和download.exe。经过不懈努力后,发现是导入操作更改了GUI程序的工作路径,导致无法找到search.exe和download.exe,通过制定可执行程序的工作路径可以解决上述问题。

网络功能

考虑到增加这个播放器的功能,让这个播放器有更好的体验,我们加入了网络部分的功能。实现方式为通过建立socket 连接(Network/search.asm/getWebResult中),构造http请求头与服务器进行通讯。服务器接收http响应解析出body返回url等歌曲信息。服务器接口实现:

  • 搜索接口:/search?key=关键词,返回值:最多20条记录,格式为“歌曲名-歌手名#歌曲id$歌曲名-歌手名#歌曲id,......”。
  • 下载接口:/get_url?sid=歌曲id,返回值:所要下载的歌曲url。

服务接口实现原理:利用python3的urllib库模拟发送搜索请求和下载请求到酷我服务器(在线试听接口),获得返回结果。

小组分工

方梓唯

  • upload函数中将SongMenu的内容存到本地磁盘
  • load函数将SongMenu的内容从本地磁盘读取到内存
  • 按下导入歌曲按钮后,能够增加歌曲(实现形式是弹出一个文件选择栏,可以让用户选择电脑中的歌曲文件)
  • 按下删除歌曲按钮后,能够删除选中的歌曲
  • search.exe用于输入关键词,通过http协议向服务器发送请求,返回一个歌单列表(包括歌曲名、歌手、下载链接)
  • 返回可以直接下载的url提供接口进行下载

江俊广

  • 播放器界面的设计和用户交互基础框架的编写
  • 音乐播放、暂停、上一首、下一首、选中列表中任一首歌功能的实现
  • 静音模式的切换
  • 基于方梓唯提供的网络搜索、查询歌曲下载地址两个接口,实现Network/search.asm(根据关键词搜索歌曲)和Network/download.asm(根据歌曲id下载歌曲到本地)两个命令行程序
  • 在GUI程序中实现关键词搜索,并将搜索结果展示在列表中,当点击列表项时能够自动下载歌曲到本地

郝天翔

  • 音量条和音量控制的实现
  • 进度条和进度控制的实现及优化
  • 循环播放的实现
  • 歌词文件的读取和解析
  • 歌词的实时显示

程序的限制

  • 本地歌曲列表至多可以添加100首歌曲。
  • 由于网络传输编码一般为utf-8格式,而winasm集成开发环境只支持GB2312,因此在进行搜索时,输入只能是英文。并且返回的中文文本会出现乱码。
  • 歌词(lrc)文件的内容长度严格控制在100000字节以内,单行歌词的长度严格控制在1000字节以内,歌词文件名长度严格控制在1000字节以内,歌词行数严格控制在1000行以内,歌曲长度最好控制在数个小时以内

实验感想

  • 极为原始的调试方法...
  • 极为匮乏的相关资料,各种api用法一团糟全靠着msdn文档和脑补苟活下来...
  • 各种玄学bug不知道为啥就有了...
  • 各种玄学bug突然就好了...
  • 第一次发现我的程序能在关闭之后仍然具有如此巨大的杀伤力让我的声卡Crash掉,之前卡死的界面的声音余音绕梁经久不绝,只能重启电脑来解决...

About

用汇编写的一个音乐播放器

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published