GEC6818音乐播放器制作

29 篇文章 1 订阅
20 篇文章 6 订阅

环境

# linux操作系统
uname -a
Linux incipe-virtual-machine 5.4.0-31-generic #35-Ubuntu SMP Thu May 7 20:20:34 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux

# 交叉编译器
arm-linux-gcc -v
Using built-in specs.
Target: arm-none-linux-gnueabi
Configured with: /opt/FriendlyARM/mini2440/build-toolschain/working/src/gcc-4.4.3/configure --build=i386-build_redhat-linux-gnu --host=i386-build_redhat-linux-gnu --target=arm-none-linux-gnueabi --prefix=/opt/FriendlyARM/toolschain/4.4.3 --with-sysroot=/opt/FriendlyARM/toolschain/4.4.3/arm-none-linux-gnueabi//sys-root --enable-languages=c,c++ --disable-multilib --with-arch=armv4t --with-cpu=arm920t --with-tune=arm920t --with-float=soft --with-pkgversion=ctng-1.6.1 --disable-sjlj-exceptions --enable-__cxa_atexit --with-gmp=/opt/FriendlyARM/toolschain/4.4.3 --with-mpfr=/opt/FriendlyARM/toolschain/4.4.3 --with-ppl=/opt/FriendlyARM/toolschain/4.4.3 --with-cloog=/opt/FriendlyARM/toolschain/4.4.3 --with-mpc=/opt/FriendlyARM/toolschain/4.4.3 --with-local-prefix=/opt/FriendlyARM/toolschain/4.4.3/arm-none-linux-gnueabi//sys-root --disable-nls --enable-threads=posix --enable-symvers=gnu --enable-c99 --enable-long-long --enable-target-optspace
Thread model: posix
gcc version 4.4.3 (ctng-1.6.1) 

开发板 GEC6818详情介绍

思路

Ⅰ. 总体框架

image-20200928220250419

Ⅱ. 目录结构

.
├── include
│   ├── get_touch.h
│   ├── lcd.h
│   ├── play_music.h
│   ├── show_bmp.h
│   └── ts_init.h
├── Makefile
├── README.md
└── sources
    ├── lcd.c
    ├── main.c
    ├── play_music.c
    ├── show_bmp.c
    └── ts_init.c

Ⅲ. 流程图

image-20200928220610937

Ⅳ. 需要用的知识

  1. Linux系统API,可以参考此文,你会linux系统API吗?
  2. Makefile文件编写,可以参考此文,简简单单学会写makefile
  3. 交叉编译开发
  4. 如果没有安装 madplay ,还要源码编译安装 madplay ,这个后面有空补上。
  5. markdown使用语法,可以参考,Markdown: 语法

代码

接下来详细讲解每个模块的具体功能与作用。

Ⅰ. LCD模块

LCD是GEC6818的显示屏,要想展示图片就必须打开LCD显示屏。

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

// lcd 文件描述符
int fd = 0;
// 共享映射区首地址
unsigned int *plcd = NULL;

/**
 *  打开lcd屏幕和共享映射区
 * */
void lcd_init() {
  fd = open("/dev/fb0", O_RDWR);
  if (fd == -1) {
    perror("lcd open error: ");
    exit(-1);
  }
  // 打开共享映射区
  plcd = (unsigned int *)mmap(NULL, 800 * 480 * 4, PROT_READ | PROT_WRITE,
                              MAP_SHARED, fd, 0);
  if (plcd == MAP_FAILED) {
    perror("mmap error: ");
    exit(-1);
  }
}

/**
 *  关闭lcd屏幕和映射区
 * */
void lcd_uninit() {
  // 关闭共享映射区
  munmap(plcd, 800 * 480 * 4);
  // 关闭文件描述符
  close(fd);
}

打开映射区的目的是为了加快显示图片的速度,直接使用 write 函数也是可以的。

Ⅱ. 显示图片模块

在这之前先简单介绍下 bmp 图片的存储格式。

BMP文件通常是不压缩的,通常比同一幅图像的压缩图像文件格式要大很多。可以参考百度百科 ,这里只介绍存储格式。

BMP文件组成
  1. BMP文件头,14字节,BMP文件的类型、文件大小和位图起始位置等信息。
  2. 位头信息,40字节。
  3. 颜色表,可用索引来表示图像。
  4. 位图数据,即图像数据。

文件格式

图片来源:粤嵌课设老师

例如,偏移量从 0x02 ~ 0x05 表示图片大小,0x12 ~ 0x15 表示图片宽, 0x16 ~ 0x19 表示图片高, 0x1c ~ 0x1d 表示图片的位深。

把这按照小端拼接起来就可以得到图片大小,宽高和位深信息。

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

extern int fd;
extern unsigned int* plcd;

/**
 *  绘制图片
 * */
void lcd_drawpoint(int w, int h, unsigned int color) {
  //(w,h)显示了color色
  *(plcd + w + h * 800) = color;
}

/**
 *  读取bmp图片数据
 *  从x0,y0处开始显示一张宽w高h的图片
 * */
void show_bmp(int x0, int y0, int w, int h, const char* bmp_file) {
  int bmp = 0;
  bmp = open(bmp_file, O_RDONLY);
  if (-1 == bmp) {
    perror("open bmp error");
    exit(-1);
  }

  //读取BMP和DIB数据
  int ret = 0;
  // BMP头和DIB数据
  unsigned char ch[64] = {0};
  ret = read(bmp, ch, 54);
  if (-1 == ret) {
    perror("read bmp error");
    exit(-1);
  } else if (0 == ret) {
    printf("no read data or file end\n");
    exit(-1);
  }

  // 3.处理数据
  int file_size = 0;
  int width = 0, hight = 0, pix_bit = 0;
  unsigned int color = 0;
  // rgba位图
  unsigned char a, r, g, b;
  //存储图像的位图数据(各个像素点颜色值分量)
  unsigned char pix[800 * 480 * 4] = {0};
  file_size = ch[2] | ch[3] << 8 | ch[4] << 16 | ch[5] << 24;
  width = ch[0x12] | ch[0x13] << 8 | ch[0x14] << 16 | ch[0x15] << 24;
  hight = ch[0x16] | ch[0x17] << 8 | ch[0x18] << 16 | ch[0x19] << 24;
  pix_bit = ch[0x1c] | ch[0x1d] << 8;

  //读取位图数据
  read(bmp, pix, w * h * pix_bit / 8);

  int i = 0;
  for (int y = 0; y < h; y++) {
    for (int x = 0; x < w; x++) {
      b = pix[i++];
      g = pix[i++];
      r = pix[i++];
      a = (pix_bit == 32) ? pix[i++] : 0;
      color = a << 24 | r << 16 | g << 8 | b;
      lcd_drawpoint(x0 + x, y0 + ((h - 1) - y), color);
    }
  }
  close(bmp);
}

因为前54个字节,我们是不需要的,所以,文件要偏移54个字节,或者把这54个字节读取出来。

注:bmp 图片是没有透明度选项的,即 rgb 颜色标准。

Ⅲ. 打开触屏文件

打开触屏的主要目的是实现上一首下一首,播放暂停功能的实现。

#include <fcntl.h>
#include <linux/input.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

int ts = 0;
extern bool flag;

/**
 *  获取触摸的x,y坐标
 * */
void get_touch(int *x, int *y) {
  int ret;
  struct input_event ev;  //输入事件结构体变量,用来保存读取的输入事件

  // 1) 打开触摸屏文件
  ts = open("/dev/input/event0", O_RDWR);
  if (-1 == ts) {
    perror("open input error");
    exit(-1);
  }

  // 2) 读取触摸屏事件
  while (1) {
    if (flag) {
      break;
    }
    ret = read(ts, &ev, sizeof(ev));  //读取输入事件保存到结构体ev中
    if (ret == sizeof(ev)) {
      if (ev.type == EV_ABS && ev.code == ABS_X) {
        *x = ev.value * 0.8;  //此时的value是触摸点X轴的坐标
      }
      if (ev.type == EV_ABS && ev.code == ABS_Y) {
        *y = ev.value * 0.8;  //此时的value是触摸点Y轴的坐标
      }
      if (ev.type == EV_KEY && ev.code == BTN_TOUCH && ev.value == 0) {
        //手指从触摸屏 离开
        printf("(x = %d, y = %d)\n", *x, *y);
        break;
      }
    }
  }
}

/**
 *  关闭触屏板
 * */
void close_ts() { close(ts); }

Ⅳ. 实现音乐播放器功能

介绍下 madplay 的使用。

管理madplay的主程序,包括播放,暂停播放,恢复播放,停止播放
system("madplay 1.mp3 &"); // 利用system函数调用madplay播放器播放*.mp3音乐

system("madplay 1.mp3 -r &"); // 循环播放:参数-r

system("killall -9 madplay"); // 利用system函数调用killall命令将madplay终止掉 

system("killall -STOP madplay &"); // 利用system函数调用killall命令将madplay暂停

system("killall -CONT madplay &"); // 利用system函数调用killall命令恢复madplay的播放

system("madplay 1.mp3 -a volume &");// 初始化播放音量,volume表示音量大小,范围是 -175 to +18 dB

// 更多可以使用man命令查看
// man madplay

再介绍下信号:

kill -
-l  -- list signal names or numbers of specified signals
-n  -- specify signal number
-s  -- specify signal name
-ABRT    -BUS     -CONT    -HUP     -INT     -PIPE    -PROF    -QUIT    -STKFLT  -SYS     -TRAP    -TTIN    -URG     -USR2    -WINCH   -XFSZ                  
-ALRM    -CHLD    -FPE     -ILL     -KILL    -POLL    -PWR     -SEGV    -STOP    -TERM    -TSTP    -TTOU    -USR1    -VTALRM  -XCPU

代码:

#include <fcntl.h>
#include <linux/input.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

char music[7][6] = {"1.mp3", "2.mp3", "3.mp3", "4.mp3",
                    "5.mp3", "6.mp3", "7.mp3"};

int order = 0;

extern bool isFirst;
extern bool isPlay;
extern int vol;

/**
 *  播放音乐
 *  如果是第一次播放就开始播放
 *  如果不是,就继续播放
 * */
void play_music() {
  if (isFirst) {
    char command[100] = {0};
    sprintf(command, "madplay %s -a %d &", music[order], vol);
    printf("%s\n", command);
    system(command);
  } else {
    system("killall -CONT madplay &");
  }
}

/**
 *  暂停音乐
 * */
void stop_music() { system("killall -STOP madplay &"); }

/**
 *  下一首
 * */
void next_music() {
  system("killall -9 madplay");

  if (order == 6) {
    order = -1;
  }
  char command[100] = {0};
  sprintf(command, "madplay %s -a %d &", music[++order], vol);
  printf("%s\n", command);
  system(command);
}

/**
 *  上一首
 * */
void pre_music() {
  system("killall -9 madplay");
  if (order == 0) {
    order = 7;
  }
  char command[100] = {0};
  sprintf(command, "madplay %s -a %d &", music[--order], vol);
  printf("%s\n", command);
  system(command);
}

这里播放音乐有个逻辑,就是如果是第一次播放的话,就要开始播放音乐,如果不是的话,就要继续播放音乐。

另外,上一首下一首功能,要防止数组越界,更简单的直接取模也是可以的。

Ⅴ. 主函数逻辑功能

#include <fcntl.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

extern void lcd_init();
extern void lcd_uninit();
extern void get_touch(int *, int *);
extern void close_ts();
extern void show_bmp(int, int, int, int, const char *);
extern void play_music();
extern void stop_music();
extern void next_music();
extern void pre_music();

bool isFirst = true;
bool isPlay = false;
bool flag = false;
int vol = 0;

/**
 *  处理信号函数
 * */
void my_handler(int sig) {
  // 最好不加
  printf("End of program, end of code: %d\n", sig);
  flag = true;
}

int main(int argc, char *argv[]) {
  // argv[0] 文件名 argv[1] 音量大小
  if (argc != 2) {
    vol = 0;
  } else {
    switch (atoi(argv[1])) {
      case 0:
        vol = -175;
        break;
      case 1:
        vol = -15;
        break;
      case 2:
        vol = 0;
        break;
      case 3:
        vol = 10;
        break;
    }
  }
  signal(SIGINT, my_handler);
  const char *background_bmp = "./bmp/background.bmp";
  const char *next_bmp = "./bmp/next.bmp";
  const char *pre_bmp = "./bmp/pre.bmp";
  const char *pause_bmp = "./bmp/pause.bmp";
  const char *play_bmp = "./bmp/play.bmp";
  lcd_init();
  show_bmp(0, 0, 800, 480, background_bmp);
  show_bmp(44, 340, 100, 100, pre_bmp);
  show_bmp(375, 340, 100, 100, pause_bmp);
  show_bmp(639, 340, 100, 100, next_bmp);
  // 触屏得到的坐标
  int x = 0, y = 0;
  while (1) {
    if (flag) {
      // 不让程序自动处理ctrl + z/c
      system("killall -9 madplay");
      show_bmp(375, 340, 100, 100, pause_bmp);
      flag = false;
      break;
    }
    get_touch(&x, &y);
    if (!flag) {
      if (375 < x && x < 475 && 340 < y && y < 440) {
        // 如果正在播放音乐,就停止播放音乐
        // 如果音乐没有播放,就开始播放音乐
        if (isPlay) {
          stop_music();
          show_bmp(375, 340, 100, 100, pause_bmp);
          isPlay = false;
        } else {
          play_music();
          show_bmp(375, 340, 100, 100, play_bmp);
          isPlay = true;
          isFirst = false;
        }
      }
      // 上一首音乐
      if (45 < x && x < 145 && 340 < y && y < 440) {
        pre_music();
        show_bmp(375, 340, 100, 100, play_bmp);
        isFirst = false;
        isPlay = true;
      }
      // 下一首音乐
      if (639 < x && x < 739 && 340 < y && y < 440) {
        next_music();
        show_bmp(375, 340, 100, 100, play_bmp);
        isFirst = false;
        isPlay = true;
      }
    }
  }
  close_ts();
  lcd_uninit();
  return 0;
}

主函数增加了一个 ctrl + c/z 信号处理,不想让程序帮我处理这个信号,我要自己处理,目的是为了解决直接使用 ctrl + c 结束程序,madplay 还在播放音乐的情况。

其次就是通过触摸屏得到的触摸点,进行相应的操作逻辑。

总结

代码经过编译,可以成功移植到 GEC1818 开发板上,具体操作,可见 README.md 文件。

源码地址,github

代码很大一部分是教课设的粤嵌老师造的轮子,自己的代码实际工作量不大。

不足之处

  1. 使用了较多的全局变量,这样会导致代码的耦合性降低,后期维护难度大;
  2. 功能还是有所欠缺;
  3. 代码还是有点冗余,不太精简。
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值