这是我们小学期的第一个大作业,因感受颇深,特此写下这篇博客留作纪念。

内容:设计一个带有播放控制和音量调节功能的控制台音乐播放器,其中播放控制的子菜单能实现播放暂停切换、停止当前曲、播放上一曲和下一曲的功能。在进入主菜单前能遍历工程文件内所有文件夹并在屏幕上显示所有mp3扩展名的音乐文件。

查阅相关资料:

1.sprintf(wsprintf函数的使用)

(1)sprintf

函数功能:把格式化的数据写入某个字符串

函数原型:intsprintf( char *buffer, const char *format [, argument] … );

返回值:字符串长度(strlen)

eg.

char* who = "I";

char* whom = "CSDN";

sprintf(s, "%s love %s.", who,whom); //产生:"I love CSDN. " 这字符串写到s中

sprintf(s, "%10.3f", 3.1415626); //产生:" 3.142"

(2)wsprintf

函数功能:将一系列的字符和数值输入到缓冲区。输出缓冲区里的的值取决于格式说明符(即"%")。如果写入的是文字,此函数给写入的文字的末尾追加一个'\0'。

返回值:写入的长度,但不包括最后的'\0'。

函数原型:intwsprintf(LPTSTR lpOut,//输出缓冲区,最大为1024字节

LPCTSTR lpFmt, //格式字符串

...//需输出参数列表)//这个函数的参数个数无法确定

2.mciSendString

mciSendString是用来播放多媒体文件的API指令,可以播放MPEG,AVI,WAV,MP3等多媒体文件。使用时应包含头文件 <mmsystem.h>。

3.VS2010等较高版本的visual studio编译器的Unicode字符编码占用了2个字节,这和VC6.0占一个字节不同,可能会导致控制台程序无法播放音乐。

4.编译器IDE适配系统方面,VC6.0中文版无法在win10系统上运行,在网上下了一个英文包覆盖原来的汉化包即可。

小结

在老师带我们编码和优化代码结构的过程中,我体会到了如何用C语言几个基本的语法点搭建一个实用的软件,这是学了大一一学年后的第一个贴近应用的实战项目。

在4天的分阶段分模块编码过程中,也认识到C语言的数组、指针、函数等内容如何与工程实践结合,优化架构,降低不同模块的耦合度,利用编译器的纠错特性,提高代码的健壮性和容错性。

1.简单软件的菜单选择常使用条件语句,若菜单选项过多switch比if-else语句效率更高(switch-case语句直达目标,if-else语句按顺序逐个检索。但是switch的入口变量只能是整型数据或者单个字符,这是它的局限性);

2.模块化编程常使用头文件分割的方法,并且一个头文件中一般写这个文件对外提供的接口(别的文件要使用的变量和函数声明);

3.在软件开发过程中,常需要限定控制变量和函数的作用域和生存期。C语言中与之相关的两个关键字为static和extern。在多个菜单需要共享一个变量或者共同使用一种状态时常使用静态局部变量,因为静态局部变量未明确赋值时编译会自动赋初值0,且在下次定义的函数内时会保留上一次运行的值,而自动变量未赋初值时是一个不确定的值,不会保留且每次进入函数都要申请释放空间,无论是逻辑的正确性,效率性还是容错性都更好,比如在PlayControlMenu()函数内的播放状态枚举变量即设为static,因其为播放、暂停、停止三个功能所共用且共同影响其变更。

extern的全局变量在工程中尽量少用,因为这样会降低代码的封装性,不用extern的方法通常是借鉴C++类的成员访问权限的思想,对不能被外界轻易更改的数据设置getXX函数接口。(例如这份工程代码最值得称道的是在后期分模块的过程中把所有的与播放列表有关的数组、变量和原本在PlayInterface.c中PlayControlMenu函数里有关列表转换的代码段提炼出基本操作编写getXX函数,并独立在PlayList.c文件封装起来,也避免了曲号要为Mp3Player.c,PlayList.c两个文件共用而不得不设置全局变量的情况。)

4.其他几个小的注意点:

(1)对于循环判断条件中的变量,在工程实践中通常在循环外初始化为-1,这是利用-1在内存中补码的二进制形式全为1的特性,最大限度避免不可预知的逻辑错误。

(2)scanf要在按下回车后才能将暂存在缓冲区中的字符发送,可以用getch函数,这样在按下按键后就能立刻发送指令,但是要注意getch()的原理是等待你按下任意键之后,把该键字符所对应的ASCII码赋值给变量,因而对于int型左值变量要注意隐式类型转换

(eg.执行编号1对应的菜单栏选项,int input; input = getch()-48;// “1”->49)

(3)可用<windows.h>的system(“cls”)语句清空屏幕,使得屏幕只显示当前级菜单,界面更简洁。

自己出现的问题:

1.在开始编写下一曲上一曲转换的功能时想当然一个菜单对应一个函数,于是在PlayInterface.c内重新编写了LastSong(),NextSong()两个函数。在编程过程中意识到它们可以拆解为两个更基本的步骤:停止当前曲播放的进程、打开下一曲扩展名为mp3的文件并向MCI库发送播放指令。但是也没有调用之前功能已经写的函数而是直接复制之前函数的代码段添加修改。这样做的结果是虽然能勉强实现下一曲和上一曲的切换,但存在两个问题:

(1)虽然自己的编程思路正确,但是在按下“3”或者“4”键后不能直接播放下一曲或上一曲,还必须按1次“1”(播放/暂停切换键)才会响起下一曲;

(2)于是将错就错,看看还会有什么其他异常状况,把播放状态调到暂停,依次按313131(播放列表只有3首歌)也就是列表循环一遍,发现第一首歌会在之前暂停的地方继续播放,再继续下去发现所有的歌都是这种情况,表明所有歌在切换到下一曲后没有结束进程,不符合实际需求。

把代码修改,重用stop(),play(), pause()函数后解决了上述问题。

这说明要合理利用代码的重用性提炼相同任务。不必担心调用多层嵌套函数而影响效率。

实际上这正是排除代码冗余,完善架构,提高程序执行效率的一种方式。

2.使用清屏命令行cls也存在一定的副作用,即一些提示性信息比如当前播放曲目在屏幕上转瞬即逝,我在C++ PRIMER中查到了编写while延时循环的方法。原理是利用系统时钟脉冲,将<time.h>中定义的常量CLOCKS_PER_SEC(每秒钟包含的系统时间单位数)转化为实际秒数。

代码如下:

//利用系统时钟,逐条显示文件夹内包含的mp3歌曲文件名

clock_tdelay=2.0*CLOCKS_PER_SEC;//转换为系统时钟脉冲数

clock_tstart=clock();

while(clock()-start<delay)//waituntil time elapses

;

printf("%s\n",FindFileData.cFileName);

//FindFileData.cFileName是文件名

这个播放器还有两点可以提高的地方,首先不能动态添加删除列表,而数组在增删改方面的效率低,因此实现这个功能要用到新的数据结构链表; 此外不能在一首歌播完后自动播放下一曲,而这个播放器一直用的是getch()函数进行菜单选择,程序始终处于等待输入的状态,进程被挤占,无法监测一首曲子是否播完,因而要用到更为复杂的多线程编程,这个留待以后解决。

总之,做这样一个项目从思维,调试,自主发现和解决问题的能力和学习的主动性全方位锻炼了我的软件编码实践能力。

下面上代码

main.c
#include"Mp3Player.h"
#include<stdio.h>
int main()
{find(".\\music");MainMenu();return 0;
}Mp3Player.h
//头文件中写这个文件对外提供的接口
extern void MainMenu();Mp3Player.c
#include<stdio.h>
#include<windows.h>
#include<conio.h>
#include<time.h>
#include"PlayInterface.h"
#include"PlayList.h"
#include"ShowDelay.h"
/*getch():
所在头文件:conio.h
函数用途:从控制台读取一个字符,但不显示在屏幕上
(用getch();会等待你按下任意键,再继续执行下面的语句;
用ch=getch();会等待你按下任意键之后,把该键字符所对应的ASCII码赋给ch,再执行下面的语句。)
函数原型:intgetch(void)
返回值:读取的字符对比:getchar()函数的返回值是用户输入的第一个字符的ASCII码,如出错返回-1。
*/
//#define STATUS_STOP 0
//#define STATUS_PLAY 1
//#define STATUS_PAUSE 2
enum STATUS{STATUS_STOP=0,STATUS_PLAY,STATUS_PAUSE
};//用枚举变量限制状态的变化范围//函数声明
static void PlayControlMenu();//static限定作用域在本文件内部
static void SoundControlMenu();
void MainMenu();void PlayControlMenu()
{char *name="Hero.mp3";//char name1[]="Hero.mp3";//char *names[10]={"Hero.mp3","Baby.mp3"};存的是10首歌曲的首地址int input2 = -1;static enum STATUS status = STATUS_STOP;//用枚举变量status记录播放状态,0,1,2分别代表停止,播放,暂停(用了静态局部变量)while (input2 != 0){printf("第%d曲 -- %s\n", getCurNo() + 1, getName());showdelay(0.5);system("cls");printf("1.播放/暂停\n");printf("2.停止\n");printf("3.下一曲\n");printf("4.上一曲\n");printf("0.退出\n");input2=_getch()-48;//“1”->49switch(input2){case 1:printf("1.播放/暂停\n");//播放或暂停音乐if(status!=STATUS_PLAY){printf("播放\n");showdelay(0.3);play(getName());status=STATUS_PLAY;}else{printf("暂停\n");showdelay(0.3);pause(getName());status=STATUS_PAUSE;}break;case 2:printf("2.停止\n");stop(getName()); //停止当前音乐status = STATUS_STOP;showdelay(0.3);break;case 3:printf("3.下一曲\n");stop(getName());curNoDown();play(getName());break;case 4:printf("4.上一曲\n");stop(getName());curNoUp();play(getName());break;case 0:printf("0.退出\n");break;default:printf("您输错了\n");break;}}
}void SoundControlMenu()
{int input3 = -1;while (input3 != 0){system("cls");printf("第%d曲 -- %s\n", getCurNo() + 1, getName());printf("1.音量增大\n");printf("2.音量减小\n");printf("0.退出\n");input3=_getch()-48;switch(input3){case 1:printf("1.音量增大\n");VolumnUp(getName());break;case 2:printf("2.音量减小\n");VolumnDown(getName());break;case 0:printf("0.退出\n");break;default:printf("您输错了\n");break;}}
}void MainMenu()
{//一般程序开发过程中初始化为-1,因为-1在内存中存的是补码,而负数的补码为原码取反加1,//eg.1在内存中二进制形式00000001,它的反码是11111110,补码11111111,即为-1在内存中的形式为11111111.//因为input是int型,4字节,所以内存中是32个1,即0XFFFFFFFF。int input = -1;//int input=0;while(input != 0){system("cls");printf("1.播放控制\n");printf("2.音量调节\n");printf("0.退出\n");//scanf("%d",&input);input=_getch();input-=48;switch(input){case 1:printf("1.播放控制\n");//二级菜单PlayControlMenu();break;case 2:printf("2.音量调节\n");//二级菜单SoundControlMenu();break;//default:case 0:printf("0.退出\n");break;default:printf("您输错了\n");break;}}
}PlayInterface.h
extern void play(const char *name);         //播放音乐
extern void pause(const char *name);        // 暂停播放
extern void stop(const char *name);         //停止播放
extern void VolumnUp(const char *name);     //调大音量
extern void VolumnDown(const char *name);   //调小音量PlayerInterface.c
#include <stdio.h>
#include <string.h>
#include <conio.h>    // 包含getch()声明的头文件
#include <stdlib.h>
#include <Windows.h>
#include <mmsystem.h> // mci库头文件
#pragma comment(lib, "winmm.lib") // 链接/指定MCI库,mciSendString函数的定义在winmm.lib中
#define MAX_SONG 3
//extern int serial_num;
//extern enum STATUS status;// 播放当前曲
void play(const char *name)          //播放音乐
{char cmd[MAX_PATH] = {0};char pathname[MAX_PATH] = {0};// 加路径//intprintf(const char*);//intsprintf(char* ,const char*,...);wsprintf(pathname, ".\\music\\%s", name);// GetShortPathName用来转换短名,要求被转换的歌名必须能在指定目录下找到文件,否则转换失败。// 第一个参数:源文件名,第二个参数:目的文件名,第三个参数:目的数组长度。//源文件?目的文件?区别?GetShortPathName(pathname, pathname, MAX_PATH);//滤掉文件路径的空格// 定义发往MCI的命令,cmd指定命令存储的数组,后面参数跟printf()相同wsprintf(cmd, "open %s", pathname);// 发送命令。// 一、存储命令的数组首地址,二、接受MCI返回的信息,三、接受数组的长度,四、没用,NULLmciSendString(cmd, "", 0, NULL);wsprintf(cmd, "play %s", pathname);mciSendString(cmd, "", 0, NULL);
}// 暂停当前曲,曲号由curno记录
void pause(const char *name)        // 暂停播放
{char cmd[MAX_PATH] = {0};char pathname[MAX_PATH] = {0};// 加路径wsprintf(pathname, ".\\music\\%s", name);// GetShortPathName用来转换短名,要求被转换的歌名必须能在指定目录下找到文件,否则转换失败。// 第一个参数:源文件名,第二个参数:目的文件名,第三个参数:目的数组长度。GetShortPathName(pathname, pathname, MAX_PATH);wsprintf(cmd, "pause %s", pathname);mciSendString(cmd,"",0,NULL);
}// 停止当前曲,曲号由curno记录
void stop(const char *name)
{char cmd[MAX_PATH] = {0};char pathname[MAX_PATH] = {0};// 加路径wsprintf(pathname, ".\\music\\%s", name);// GetShortPathName用来转换短名,要求被转换的歌名必须能在指定目录下找到文件,否则转换失败。// 第一个参数:源文件名,第二个参数:目的文件名,第三个参数:目的数组长度。GetShortPathName(pathname, pathname, MAX_PATH);wsprintf(cmd, "stop %s", pathname);mciSendString(cmd,"",0,NULL); wsprintf(cmd, "close %s", pathname);mciSendString(cmd,"",0,NULL);
}/*
void nextSong(char *name)
{char cmd[MAX_PATH] = {0};char pathname[MAX_PATH] = {0};//停止播放当前歌曲wsprintf(pathname, ".\\music\\%s", name);GetShortPathName(pathname, pathname, MAX_PATH);wsprintf(cmd, "stop %s", pathname);mciSendString(cmd,"",0,NULL);//播放下一首歌曲wsprintf(pathname, ".\\music\\%s", name+1);GetShortPathName(pathname, pathname, MAX_PATH);wsprintf(cmd, "open %s", pathname);mciSendString(cmd, "", 0, NULL);wsprintf(cmd, "play %s", pathname);mciSendString(cmd, "", 0, NULL);//歌曲序号+1,注意列表循环播放,最后一曲的下一曲为第一曲if(serial_num<MAX_SONG-1)serial_num++;elseserial_num=0;
}
*//*
void lastSong(char *name)
{char cmd[MAX_PATH] = {0};char pathname[MAX_PATH] = {0};//停止播放当前歌曲wsprintf(pathname, ".\\music\\%s", name);GetShortPathName(pathname, pathname, MAX_PATH);wsprintf(cmd, "stop %s", pathname);mciSendString(cmd,"",0,NULL);//播放上一首歌曲wsprintf(pathname, ".\\music\\%s", name-1);GetShortPathName(pathname, pathname, MAX_PATH);wsprintf(cmd, "open %s", pathname);mciSendString(cmd, "", 0, NULL);wsprintf(cmd, "play %s", pathname);mciSendString(cmd, "", 0, NULL);//歌曲序号-1,注意列表循环播放,第一曲的上一曲为最后一曲if(serial_num>0)serial_num--;elseserial_num=MAX_SONG-1;
}
*/void VolumnUp(const char *name)
{char path[MAX_PATH] = {0};char cmd[MAX_PATH] = {0};  char res[MAX_PATH] = {0};int volumn = 0;// 加路径sprintf(path, ".\\music\\%s", name);// GetShortPathName用来转换短名,要求被转换的歌名必须能在指定目录下找到文件,否则转换失败。// 第一个参数:源文件名,第二个参数:目的文件名,第三个参数:目的数组长度。GetShortPathName(path, path, MAX_PATH);sprintf(cmd, "status %s volume", path);mciSendString(cmd, res, MAX_PATH, NULL);// 音量转换为整形,并作加法操作volumn = atoi(res);volumn += 100;// 拼接设置音量命令sprintf(cmd, "setaudio %s volume to %d", path, volumn);// 发送设置音量命令mciSendString(cmd, "", 0, NULL);
}void VolumnDown(const char *name)
{char path[MAX_PATH] = {0};char cmd[MAX_PATH] = {0};  char res[MAX_PATH] = {0};int volumn = 0;// 加路径sprintf(path, ".\\music\\%s", name);// GetShortPathName用来转换短名,要求被转换的歌名必须能在指定目录下找到文件,否则转换失败。// 第一个参数:源文件名,第二个参数:目的文件名,第三个参数:目的数组长度。GetShortPathName(path, path, MAX_PATH);sprintf(cmd, "status %s volume", path);mciSendString(cmd, res, MAX_PATH, NULL);// res是个字符数组,不能实现减法操作,所以用atoi()转换为整形volumn = atoi(res);volumn -= 100;// 将变更后的音量发送给MCI进行设定sprintf(cmd, "setaudio %s volume to %d", path, volumn);mciSendString(cmd, "", 0, NULL);
}PlayList.h
extern void find(char * lpPath);            //在指定文件夹内遍历查找mp3文件
extern char *getName();                     //获取歌名
extern int getCurNo();                      //获得曲号
extern void curNoDown();                    //下一曲,曲号+1
extern void curNoUp();                      //上一曲,曲号-1PlayList.c
#include<windows.h>
#include"ShowDelay.h"
#define MAX_SONG 10
static char names[MAX_SONG][100]={"Beautiful Times.mp3","Fireflies.mp3","Hero.mp3",};//存的是10首歌曲的歌曲名
static int serial_num = 0;//歌曲序号,也即歌曲名字符串数组的下标
static int total = 0;    //总曲数char *getName()
{return names[serial_num];
}int getCurNo()
{return serial_num;
}void curNoDown() //变为下一首的曲号
{serial_num++;if(serial_num==3)serial_num=0;
}void curNoUp() //变为上一首的曲号
{serial_num--;if(serial_num==-1)serial_num=2;
}// 遍历文件夹,利用windows API
void find(char * lpPath)
{char szFind[MAX_PATH],szFile[MAX_PATH]; WIN32_FIND_DATA FindFileData; HANDLE hFind;strcpy(szFind,lpPath); strcat(szFind,"\\*.mp3");hFind = FindFirstFile(szFind,&FindFileData); if(INVALID_HANDLE_VALUE == hFind) return; while(TRUE) { if(FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { if(FindFileData.cFileName[0]!='.') { strcpy(szFile,lpPath); strcat(szFile,"\\"); strcat(szFile,FindFileData.cFileName); find(szFile); } } else {showdelay(1.5);printf("%s\n",FindFileData.cFileName);// FindFileData.cFileName是文件名//二维数组行地址 &names[1][0]/names[1]/(name+1)strcpy(names[total],FindFileData.cFileName);total++;} if(!FindNextFile(hFind,&FindFileData)) break; }
}ShowDelay.h
extern void showdelay(float sec);ShowDelay.c
#include<time.h>
void showdelay(float sec)
{clock_t delay=sec*CLOCKS_PER_SEC;//转换为系统时钟脉冲数clock_t start=clock();while(clock()-start<delay)//wait until time elapses;
}

基于C语言控制台程序的简易MP3音乐播放器相关推荐

  1. 基于STM32的简易MP3音乐播放器

    设计简介 本设计硬件主要利用STM32单片机,SD卡.设计思路:本设计通过STM32单片机读取SD卡里的音频文件,再通过DAC进行输出,DAC输出后接一个运放模块,再接一个喇叭.利用STM32cube ...

  2. Linux下基于Libmad库的MP3音乐播放器编写

    linux下基于Libmad库的MP3音乐播放器编写 libmad是一个开源mp3解码库,其对mp3解码算法做了很多优化,性能较好,很多播放器如mplayer.xmms等都是使用这个开源库进行解码的: ...

  3. 【毕业设计】基于单片机的MP3音乐播放器设计与实现 - stm32 物联网 c51

    文章目录 1 简介 2 绪论 2.1 课题背景与目的 3 系统设计 3.1 系统架构 3.2 软件部分设计 3.3 实现效果 3.4 部分相关代码 4 最后 1 简介 Hi,大家好,这里是丹成学长,今 ...

  4. [附源码]计算机毕业设计Python+uniapp基于微信小程序平台开发的音乐播放器f0rrr(程序+lw+远程部署)

    [附源码]计算机毕业设计Python+uniapp基于微信小程序平台开发的音乐播放器f0rrr(程序+lw+远程部署) 该项目含有源码.文档.程序.数据库.配套开发软件.软件安装教程 项目运行环境配置 ...

  5. 基于JavaSwing开发MP3音乐播放器 课程设计 大作业源码 毕业设计

    基于JavaSwing开发MP3音乐播放器:   (大作业) 开发环境: Windows操作系统 开发工具: MyEclipse+Jdk 运行效果图:  基于JavaSwing开发MP3音乐播放器:  ...

  6. 树莓派云音乐c语言,基于树莓派的红外遥控版网易云音乐播放器

    基于树莓派的红外遥控版网易云音乐播放器.下面是遥控键盘示意图: CH- CH CH+ << >> || - + EQ 0 100+ 200+ 1 2 3 4 5 6 7 8 9 ...

  7. 使用Java实现MP3音乐播放器

    原文链接:http://www.cnblogs.com/haoxia/archive/2009/06/03/1495419.html 使用Java实现MP3音乐播放器 JavaSound是一个小巧的低 ...

  8. 【转】使用Java实现MP3音乐播放器

    原文来源:http://blog.csdn.net/liuzhongbing/article/details/4535402 JavaSound是一个小巧的低层API,支持数字音频和MIDI数据的记录 ...

  9. python如何自制音乐软件_70行python代码制作一款简易的音乐播放器!

    今天整理了以前的python作业代码,发现了一些有趣的小东西,比如下面这个,大概70行代码制作一款简易的音乐播放器. install some packages pip install pygame ...

最新文章

  1. c语言系统主函数流程图,C语言程序设计——成语学习系统
  2. [摘]一张图 , oracle merge用法:
  3. 软考-信息系统项目管理师-项目采购管理
  4. pad_sequences序列填充(转载)
  5. 学而不思则罔 - SAP云平台ABAP编程环境的由来和适用场景
  6. html dom反选,HTML DOM系列教材 (五)- 事件
  7. php中des加密cbc模式,php中加密解密DES类的简单使用方法示例
  8. (器) 构建自由通行的IOS开发者地图
  9. 苹果推出十年来首款游戏 “股神”巴菲特担任游戏主角
  10. [Android] View动画特效(四)
  11. CentOS 5.5 使用 EPEL 和 RPMForge 软件库
  12. EditPlus3破解版下载以及配置java使用教程
  13. linux硬链接与软链接的联系与区别
  14. 直流稳压稳流电源基本功能,电源使用注意事项
  15. 【微信小程序】农历公历互相转换
  16. 电脑需要装杀毒软件吗?两个理由看完你做决定
  17. 解决java中使用getImage()导入图片失败问题
  18. python百度地图标注自己的店名_百度地图上怎么显示店名 百度地图标注店名方法...
  19. 京东到家埋点治理实践
  20. chromium保存网页功能

热门文章

  1. Sublime Text 3 配置 PHP IDE环境 并使用Xdebug进行调试
  2. 安全通信与安全通信标准EN50159
  3. 激光雷达建图(基于ROS)及定位数据获取步骤
  4. 丢弃 HttpClient 了,这款轻量级框架更强!
  5. 大數據環境搭建,數據採集,數倉環境準備(hive on spark) 01
  6. fluent———patch
  7. linux给红米note4x刷机,红米Note4X 刷机LineageOs 14.1全过程讲解
  8. NOIP2017暨2017年9月——11月学习总结
  9. 关于matlab的相关性函数
  10. 看完 2022 雷军年度演讲,我总结了我的故事