C语言实现音乐播放器 visual studio 2019

这是一款基于easyx图形库和C/C++编写的音乐播放器,参考了网上许多大佬友情分享的项目设计,结合自己的理解加以设计和制作,给有同样需求的朋友一点参考。文中放上了个人感觉很有帮助的文章,建议可以参考阅读。

项目基础

基于windows和easyx的一款音乐播放器

mciSendString函数是这款音乐播放器能够实现功能的底层函数,其余所有的代码不过是在锦上添花和增加扩展功能,这个是一定要掌握的。(如果能力足够,使用QT这类框架功能更加强大)

easyx是项目所需的图形库,在界面设计上有多很强大的功能和方便的函数。如果想做图形界面可以参考使用。

学习了解mciSendString函数:

有关链表的增删改查操作、文件读写操作、UI的设计、MFC库easyx库

设计思路

  1. 基于Windows控制台C程序的音乐播放器其实是通过调用Windows上的一些API实现的,那就只需要通过函数对mciSendString传输字符串参数为命令控制Windows程序播放MP3文件。

  2. 基于这个想法,可以对音乐播放器的各个功能模块在原生API的基础上再加工,封装成一个个小demo,然后再做一个简陋的界面去操控。

  3. 那么一切合理了起来:也就是说相当于把各种本来要手打的命令行变成按钮或者键盘输入字符,按一下就往这里面输出一些命令,也就能实现音乐播放器了。但是

    • 文件如何读取呢?
    • 有没有办法可以指定文件读取?批量导入?
    • 既然要读取,那么用什么东西来装它呢?
    • 装进去了能不能删除、增添、改变?
    • 能不能读取装进去之后里面的信息?
  4. (显然)由题可知,我们可以使用链表作为这种数据结构(数据量不大),方便完成各种操作。那链表存储可以实现了,文件又怎么读取呢?

  5. 当然是采用直接呼出文件资源管理器的方式去读取文件的路径,这个的实现我们之后再说,这样也更加友好和人性化,有图形界面的操作手感

    • 文件读取的时候总是读取失败是为什么
    • 通过断点发现文件路径参数是正确的,为什么mci无法成功播放呢?
    • 呼出管理器后操作返回数据为空有没有处理?
  6. 所以,需求里难弄的的东西来了。图形界面,用C做图形界面,完全陌生的领域。摆在面前的有几种选择:

    • Easyx.h图形库
    • MFC框架
    • QT框架
    • WPF框架

    这些都是在C环境下做UI的好点子。但是MFC太拉了,UI奇丑。QT可以说是功能强大齐全,但是学习成本高,或者说一个小播放器还不至于用QT,学习成本有点浪费时间。WPF是基于C#的。

    那么综上所述,我们组采取Easyx.h图形库方案,UI这种东西全靠审美。审美上去了拿什么做都好看,也就是说只要你会PS,界面这种东西不成问题,靠队友的时候到了。

  7. 那么这时根据需求,我们所有的技术选择都已经齐全,可以开始着手写代码了。

一些基础的知识不再赘述,以下是一些踩过的坑(本项目),具体代码在文末链接中可以自行下载

UI背后的逻辑问题

  • 音乐播放函数接收什么样的参数?
  • 命令都是什么样的?
  • 如何组合字符串成为一个命令?
  • 如何读取文件并输入到播放列表中?
  • 每一个功能需要几个API?
  • 什么是alias别名?如何通过别名简化命令?
  • 这些API尽可能多地能组成什么样的功能?
解决方案
mci函数参数
MCIERROR mciSendString(
      LPCTSTR lpszCommand,    //MCI命令字符串
      LPTSTR lpszReturnString, //存放反馈信息的缓冲区
      UINT cchReturn,          //缓冲区的长度
      HANDLE hwndCallback  //回调窗口的句柄,一般为NULL
); //若成功则返回0,否则返回错误码。
MCI命令
命令解释
open打开设备
close关闭设备
play开始设备播放
stop停止设备的播放或记录
record开始记录
save保存设备内容
pause暂停设备的播放或记录
resume恢复暂停播放或记录的设备
seek改变媒体的当前位置
status查询设备状态信息
capacility查询设备能力
info查询设备的信息
将字符串相互拼接产生可以被系统理解的MCI指令
  • strcpy和strcat
strcpy: char *strcpy(char* dest, const char *src);
//把从src地址开始且含有NULL结束符的字符串复制到以dest开始的地址空间
//src和dest所指内存区域不可以重叠且dest必须有足够的空间来容纳src的字符串
//返回指向dest的 指针。
strcat: extern char *strcat(char *dest, const char *src);
//把src所指字符串添加到dest结尾处(覆盖dest结尾处的'\0')
呼出文件资源管理器实现读取文件路径并存放到指定链表节点
  • 需要注意最下方链表插入的路径不能为空,加一个if判断防止插入空数据报错
void add()
{
    TCHAR szBuffer[MAX_PATH] = { 0 };
	OPENFILENAME file = { 0 };
	file.hwndOwner = NULL;
	file.lStructSize = sizeof(file);
	file.lpstrFilter = "*文件(*.*)\0*.*\0";//要选择的文件后缀
	file.lpstrFile = szBuffer;//存放文件的缓冲区
	file.nMaxFile = sizeof(szBuffer) / sizeof(*szBuffer);
	file.nFilterIndex = 0;
	file.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_EXPLORER;//标志如果是多选要加上OFN_ALLOWMULTISELECT
	BOOL bSel = GetOpenFileName(&file);
	Media* music = createMedia(file.lpstrFile);//创建的链表
	if (music != NULL) {
		headInsert(list, *music);
	}	
}
获取播放信息和播放位置
命令解释
“status movie position”播放位置
“status movie length”播放总长度
“status movie mode”播放状态
"seek movie to "指定位置
“seek movie to start”定位到开头位置
“seek movie to end”定位到最后位置
alias别名
  • 为长命令设置别名更有助于写代码

  • 由于对MCI的操作实际上就是命令操作,所以可以通过设置别名来进行操作mcisendstring函数

@doskey ls=dir /b $*
@doskey l=dir /od/p/q/tw $*
//命令 alias 别名
@REM notepad++工具设置别名为:npp
@doskey npp="C:\Program Files1\Notepad++\notepad++.exe" $   

一些难点

  • 控制台页面时期的双缓冲刷新已经是过去式了
  • 文件读取路径包含空格无法读取
  • 通过呼出文件资源对话框实现导入文件(上文已提到)
  • 对话框式导入文件和easyx界面刷新冲突
  • 如何通过图片实现伪造的按钮特效
  • easyx图形界面的进度条和刷新页面的矛盾
解决方案
双缓冲原理有些复杂,命令行使用一个更简单的方案
  • 这种刷新是通过“不刷新”实现的,当字符在相同位置更新的时候会出现上一次未刷新而字符重叠的现象。可以通过使用长空白字符覆盖未刷新区域来实现“伪刷新”
//消除cls频闪函数
void cls() {
	HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
	COORD coordScreen = { 0, 0 };    // home for the cursor
	SetConsoleCursorPosition(hConsole, coordScreen);
}
MCI文件路径空格问题
  • mci读取文件的路径如果包含空格需要采用双引号包住整个路径
int openMusic(const char* url)
{
	if (url != NULL) 
	{
		char cmd[500] = "";
		strcpy_s(cmd, "open \"");//引号需要被转义
		strcat_s(cmd, url);
		strcat_s(cmd, "\"");//引号需要被转义
		strcat_s(cmd, " alias MyMusic");
		if (mciSendStringUtil(cmd, NULL) == 0)
		{
			return 0;
		}
	}
}
对话框式导入文件和easyx界面刷新冲突
  • 一个奇怪的bug,最后通过绝对路径解决,有大佬知道问题的可以评论区见
  1. 执行如下呼出文件资源管理器代码后正常
void add() {
	TCHAR szBuffer[MAX_PATH] = { 0 };
	OPENFILENAME file = { 0 };
	file.hwndOwner = NULL;
	file.lStructSize = sizeof(file);
	file.lpstrFilter = "*文件(*.*)\0*.*\0";//要选择的文件后缀
	file.lpstrFile = szBuffer;//存放文件的缓冲区
	file.nMaxFile = sizeof(szBuffer) / sizeof(*szBuffer);
	file.nFilterIndex = 0;
	file.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_EXPLORER;//标志如果是多选要加上OFN_ALLOWMULTISELECT
	BOOL bSel = GetOpenFileName(&file);
	Media* music = createMedia(file.lpstrFile);
	if (music != NULL) {
		headInsert(list, *music);
	}
}
  1. 再执行刷新界面
HWND hwnd = initgraph(720, 960, 0);
//或者屏幕刷新等类似函数
loadimage(&play2, ".\\*.png", 81, 81);
loadimage(&pause, ".\\*.png", 81, 81);
  • 结果:页面变黑,有鼠标点击事件,图片无法加载
  1. 解决方案:所有加载的图片都指定绝对路径
loadimage(&play2, "D:\\visual studio project\\Project_List\\Project_List\\picture\\*.png", 81, 81);
loadimage(&pause, "D:\\visual studio project\\Project_List\\Project_List\\picture\\*.png", 81, 81);
伪造按钮特效
  • 提前准备好两张透明背景的png图片,执行鼠标点击动作时两种不同颜色的图层依次刷新,就可以实现鼠标点击的伪动态效果
void transimg(IMAGE* dstimg, int x, int y, IMAGE* srcimg)
{
	HDC dstDC = GetImageHDC(dstimg);
	HDC srcDC = GetImageHDC(srcimg);
	int w = srcimg->getwidth();
	int h = srcimg->getheight();
	BLENDFUNCTION bf = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA };
	//半透明位图
	AlphaBlend(dstDC, x, y, w, h, srcDC, 0, 0, w, h, bf);
}
transimg(NULL, 334, 652, &IMAGE);//一种颜色的按钮
Sleep(100);
transimg(NULL, 334, 652, &IMAGE);//另一种颜色的按钮
进度条的制作
  • 刷新页面与刷新进度条
//进度条
while(1)
{
    t = getMusicLength();
		t1 = getMusicPosition();
		if (t != 0)
        {
            c = t1 / t;
        }
		progress_length = c * 523 + 90;
		fillroundrect(90, 830, progress_length, 825, 10, 10);
}
Github项目地址 — Music-Player
  • 8
    点赞
  • 61
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
这是一个使用C语言编写的简易音乐播放器,主要实现了歌词与歌曲之间的同步。具体实现原理是将歌词切割并存入数组中,通过sleep函数创建虚拟时钟与歌词中的时间相对应。然后创建一个进程调用mplayer播放歌曲,同时开始计时就可以完成歌词与歌曲之间的同步。以下是主要代码: 1. my_lrc_play.h ```c #ifndef MY_LRC_PLAY_H #define MY_LRC_PLAY_H void my_lrc_play(); #endif ``` 2. my_lrc_play.c ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <pthread.h> #include "my_lrc_play.h" #define MAX_LRC_LINE 1024 #define MAX_LRC_SIZE 1024 typedef struct lrc_node { int time; char lrc[MAX_LRC_SIZE]; struct lrc_node *next; } LrcNode; LrcNode *head = NULL; void *lrc_thread(void *arg) { int time = 0; LrcNode *p = head; while (p != NULL) { if (time == p->time) { printf("%s\n", p->lrc); p = p->next; } sleep(1); time++; } return NULL; } void my_lrc_play() { FILE *fp = fopen("test.lrc", "r"); if (fp == NULL) { printf("Open file failed!\n"); return; } char buf[MAX_LRC_LINE]; while (fgets(buf, MAX_LRC_LINE, fp) != NULL) { int len = strlen(buf); if (len <= 10) { continue; } LrcNode *node = (LrcNode *) malloc(sizeof(LrcNode)); node->time = (buf[1] - '0') * 600 + (buf[2] - '0') * 60 + (buf[4] - '0') * 10 + (buf[5] - '0'); strcpy(node->lrc, buf + 10); node->next = NULL; if (head == NULL) { head = node; } else { LrcNode *p = head; while (p->next != NULL) { p = p->next; } p->next = node; } } pthread_t tid; pthread_create(&tid, NULL, lrc_thread, NULL); char *song_path = "test.mp3"; pid_t pid; pid = fork(); if (pid < 0) { perror("fork"); } else if (pid == 0) { close(1); close(2); execlp("mplayer", "mplayer", "-slave", "-quiet", song_path, NULL); exit(0); } else; wait(NULL); } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值