以前工作中做过关于alsa驱动的,最近整理资料的时候感觉快忘了 抽空再复习以下。这里就不用开发板了qemu-kvm模拟下就行了,主要是分析alsa驱动的架构。底层的东西无须刻意关注。
先说下我模拟的环境:
主机 ubuntu14.04 desktop 64bit qmeu-kvm 版本:qemu-kvm-1.2.0 linux内核版本:linux-3.14.12 busybox版本:busybox-1.22.1
由于主机比较新,所以编译qemu-kvm的时候遇到了一些问题. 具体过程可以看我以前的一系列博文来解决:
blog.csdn.net/xsckernel/article/details/8159548
linux 内核 和busybox编译成32位的--习惯性的喜欢32bit虚拟机。
另外由于需要模拟声卡所以qemu-kvm在配置的时候需加入有关参数我用的是:
./configure --target-list='i386-softmmu x86_64-softmmu i386-linux-user x86_64-linux-user' --enable-sdl --audio-drv-list='oss alsa sdl' --audio-card-list='ac97 es1370 sb16 cs4231a adlib gus hda'
上图是 linux设备驱动开发详解(第2版宋宝华)里面的一张图,可以看出,在整个体系结构中与硬件有关的就是 ALSA驱动 这个模块,验证 这个模块是否正确 可以用
oss模拟模块 和 alsa内核api 这里选择前者。
关于alsa模拟oss的配置方式可以参考内核文档:
Documentation/sound/alsa/OSS-Emulation.txt
Device Drivers Sound card support Advanced Linux Sound Architecture <*> Sequencer support <*> OSS Mixer API <*> OSS PCM (digital audio) API [*] OSS PCM (digital audio) API - Include plugin system PCI sound devices (Creative) Ensoniq AudioPCI 1370
当 内核,bosybox,qemu-kvm 都准备好之后还差一个oss的用户态工具,因为仅是验证硬件能否正常工作所以用户态处理过程最好尽可能简单这里选择 sndkit 直接附上源码:
/* Name: SndKit.c Copyright: GPLv2 Author: rockins([email protected]) Date: 15-10-06 18:22 Description: implent raw sound record/play run: ./SndKit [-h] [-d device] [-c channel] [-b bits] [-f hz] [-l len] <-r|-p file> e.g.: ./SndKit -h show help information ./SndKit -r record.wav record audio from microphone(/dev/dsp) ./SndKit -d /dev/dsp -p record.wav playback record.wav via /dev/dsp ./SndKit -b 8 -f 22 -r reacord.wav record audio in 8 bit & 22k hz ./SndKit -d /dev/dsp -c 2 -r record.wav record audio in /dev/dsp and stereo ./SndKit -r -l 40 record.wav record 40k audio data */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <sys/ioctl.h> #include <linux/soundcard.h> /*for OSS style sound programing */ #include <signal.h> #define TRUE 1 #define FALSE 0 #define FMT8BITS AFMT_U8 /*unsigned 8 bits(for almost PC) */ #define FMT16BITS AFMT_S16_LE /*signed 16 bits,little endian */ #define FMT8K 8000 /*default sampling rate */ #define FMT11K 11025 /*11,22,44,48 are three pop rate */ #define FMT22K 22050 #define FMT44K 44100 #define FMT48K 48000 #define MONO 1 #define STEREO 2 #define ACT_RECORD 0 #define ACT_PLAY 1 #define DFT_SND_DEV "/dev/dsp" #define DFT_SND_FMT FMT8BITS #define DFT_SND_SPD FMT8K #define DFT_SND_CHN MONO #define DFT_LEN 1024 /*default record length:40k */ #define BUFF_SIZE 512 /*buffer size:512 Bytes */ #define SNDRV_PCM_IOCTL_PAUSE 0x40045046 /************** function prototype ********************/ void Usage(void); int OpenDevice(const char *, unsigned int); int OpenFile(const char *, unsigned int); int CloseDevice(unsigned int); int CloseFile(unsigned int); void snd_pause(int sig); void snd_resume(int sig); /************** function implementation **************/ int snd_fd; /*sound device descriptor */ int flag; int main(int argc, char *argv[]) { unsigned int snd_fmt = DFT_SND_FMT; /*sound format,bits */ unsigned int snd_chn = DFT_SND_CHN; /*sound channel number */ unsigned int snd_spd = DFT_SND_SPD; /*sound speed,frequency */ unsigned char *s_file = NULL; /*file hold sound data */ unsigned char *snd_device = DFT_SND_DEV; /*sound device */ unsigned int recd_or_play = ACT_PLAY; /*flag(default play) */ unsigned char buff[BUFF_SIZE]; /*sound buffer */ unsigned long len = DFT_LEN * 1024; /*record or play length(k) */ int s_fd; /*sound data file descrit */ ssize_t n; /*bytes already in or out */ ssize_t nRD; /*bytes readin */ ssize_t nWR; /*bytes write out */ unsigned int opt_chr; /*hold cli option */ unsigned int opt_err = FALSE; /*indicate whether parse option error */ signal(SIGTSTP,snd_pause); signal(SIGCONT,snd_resume); /*parse cli option via getopt routine */ optind = 0; /*set optindex to 0 */ while ((opt_chr = getopt(argc, argv, "hd:c:b:f:l:r:p:")) != -1) { switch (opt_chr) { case 'h': Usage(); return (0); /*return, 0 denote success */ break; case 'd': snd_device = optarg; /*sound device name */ break; case 'c': if (atoi(optarg) == 1) /*select channel number */ snd_chn = MONO; else if (atoi(optarg) == 2) snd_chn = STEREO; else { opt_err = 1; /*error occur */ fprintf(stderr, "wrong channel setting," "should be 1 or 2(default 1,MONO)\n"); } break; case 'b': if (atoi(optarg) == 8) /*select data bit */ snd_fmt = FMT8BITS; else if (atoi(optarg) == 16) snd_fmt = FMT16BITS; else { opt_err = 1; /*error occur */ fprintf(stderr, "wrong bits setting," "should be 8 or 16(default 8)\n"); } break; case 'f': if (atoi(optarg) == 8) snd_spd = FMT8K; /*sampling rate:8kbps */ else if (atoi(optarg) == 11) snd_spd = FMT11K; /*smapling rate:11.025k */ else if (atoi(optarg) == 22) snd_spd = FMT22K; /*sampling rate:22.050k */ else if (atoi(optarg) == 44) snd_spd = FMT44K; /*sampling rate:44.100k */ else if (atoi(optarg) == 48) snd_spd = FMT48K; /*sampling rate:48k */ else { opt_err = 1; /*error occur */ fprintf(stderr, "wrong sampling rate," "should be 8,11,22,or 44(default 8kbps)\n"); } break; case 'l': len = atoi(optarg) * 1024; /*record length(k) */ break; case 'r': recd_or_play = ACT_RECORD; s_file = optarg; /*file to record sound */ break; case 'p': recd_or_play = ACT_PLAY; s_file = optarg; /*file to play sound */ break; case '?': opt_err = 1; fprintf(stderr, "unknown option:%c\n", optopt); break; default: opt_err = 1; fprintf(stderr, "getopt error:%d\n", opterr); break; } } /*check if cli option parsed right*/ if (opt_err || argc < 2) { fprintf(stderr, "parse command option failed!!!\n" "run ./SndKit -h for help\n"); return (-1); } /*open device */ if ((snd_fd = OpenDevice(snd_device, recd_or_play)) < 0) { fprintf(stderr, "cannot open device %s:%s\n", snd_device, (char *)strerror(errno)); return (-1); } /*open sound data file */ if ((s_fd = OpenFile(s_file, recd_or_play)) < 0) { fprintf(stderr, "cannot open sound file %s:%s\n", s_file, (char *)strerror(errno)); return (-1); } /*set sound format */ if (SetFormat(snd_fd, snd_fmt, snd_chn, snd_spd) < 0) { fprintf(stderr, "cannot set %s in bit %d, channel %d, speed %d\n", snd_device, snd_fmt, snd_chn, snd_spd); return (-1); } /*do real action:record or playback */ if (recd_or_play == ACT_RECORD) { /*record sound into data file*/ n = 0; while (n < len) { nRD = 0; /*amount read from sound device */ if ((nRD = read(snd_fd, buff, BUFF_SIZE)) < 0) { perror("read sound device failed"); return (-1); } if (n + nRD <= len) /*the len is not full */ nWR = nRD; /*write amount to sound data file */ else nWR = len - n; /*len will be overflow */ unsigned long old_nWR = nWR; /*s hold nWR's old value */ unsigned long t = 0L; /*temp counter */ while (nWR > 0) { if ((t = write(s_fd, buff, nWR)) < 0) { perror("write sound data file failed"); return (-1); } nWR -= t; } n += old_nWR; } } else if (recd_or_play == ACT_PLAY) { /*write sound data to device*/ while (TRUE) { nRD = 0L; /*read amount from sound device */ if ((nRD = read(s_fd, buff, BUFF_SIZE)) < 0) { perror("read sound data file failed"); return (-1); } else if (nRD == 0) { /*nRD == 0 means all sound stream output */ printf("sound data play complete!\n"); exit(0); } nWR = nRD; while (nWR > 0) { /*write into device*/ if ((n = write(snd_fd, buff, nWR)) < 0) { printf("n is %d\n",n); perror("write sound device file failed"); return (-1); } nWR -= n; } } } /*close sound device and sound data file*/ CloseDevice(snd_fd); CloseFile(s_fd); return (0); } void snd_pause(int sig) { int cond; cond = 1; //ioctl(snd_fd, SNDRV_PCM_IOCTL_PAUSE, &cond); raise(SIGSTOP); } void snd_resume(int sig) { int cond; cond = 0; //ioctl(snd_fd, SNDRV_PCM_IOCTL_PAUSE, &cond); } /* * OpenDevice():open sound device * params: * dev_name -- device name,such as /dev/dsp * flag -- flag(ACT_RECORD or ACT_PLAY) * returns: * file descriptor of sound device if sucess * -1 if failed */ int OpenDevice(const char *dev_name, unsigned int flag) { int dev_fd; /*open sound device */ if (flag == ACT_RECORD) { if ((dev_fd = open(dev_name, O_RDONLY)) < 0) { return (-1); } } else if (flag == ACT_PLAY) { if ((dev_fd = open(dev_name, O_WRONLY)) < 0) { return (-1); } } return (dev_fd); } /* * CloseDevice():close the sound device * params: * dev_fd -- the sound device's file descriptor * returns: * 0 if success * -1 if error occured */ int CloseDevice(unsigned int dev_fd) { return (close(dev_fd)); } /* * OpenFile():open sound data file * params: * file_name -- file name,e.g,record.wav * flag -- flag(ACT_RECORD or ACT_PLAY) * returns: * file descriptor of sound data file if sucess * -1 if failed */ int OpenFile(const char *file_name, unsigned int flag) { int file_fd; /*open sound data file */ if (flag == ACT_RECORD) { if ((file_fd = open(file_name, O_WRONLY)) < 0) { return (-1); } } else if (flag == ACT_PLAY) { if ((file_fd = open(file_name, O_RDONLY)) < 0) { return (-1); } } return (file_fd); } /* * CloseFile():close the sound data file * params: * file_fd -- the sound data file's descriptor * returns: * 0 if success * -1 if error occured */ int CloseFile(unsigned int file_fd) { return (close(file_fd)); } /* * SetFormat():Set Record and Playback format * params; * fd -- device file descriptor * chn -- channel(MONO or STEREO) * bits -- FMT8BITS(8bits), FMT16BITS(16bits) * hz -- FMT8K(8000HZ), FMT16K(16000HZ), FMT22K(22000HZ), * FMT44K(44000HZ),FMT48K(48000HZ) * returns: * return 0 if success, else return -1 * notes: * parameter setting order should be like as: * 1.sample format(number of bits) * 2.number of channels(mono or stereo) * 3.sampling rate(speed) */ int SetFormat(unsigned int fd, unsigned int bits, unsigned int chn, unsigned int hz) { int ioctl_val; /* set bit format */ ioctl_val = bits; if (ioctl(fd, SNDCTL_DSP_SETFMT, &ioctl_val) == -1) { fprintf(stderr, "Set fmt to bit %d failed:%s\n", bits, (char *)strerror(errno)); return (-1); } if (ioctl_val != bits) { fprintf(stderr, "do not support bit %d, supported %d\n", bits, ioctl_val); return (-1); } /*set channel */ ioctl_val = chn; if ((ioctl(fd, SNDCTL_DSP_CHANNELS, &ioctl_val)) == -1) { fprintf(stderr, "Set Audio Channels %d failed:%s\n", chn, (char *)strerror(errno)); return (-1); } if (ioctl_val != chn) { fprintf(stderr, "do not support channel %d,supported %d\n", chn, ioctl_val); return (-1); } /*set speed */ ioctl_val = hz; if (ioctl(fd, SNDCTL_DSP_SPEED, &ioctl_val) == -1) { fprintf(stderr, "Set speed to %d failed:%s\n", hz, (char *)strerror(errno)); return (-1); } if (ioctl_val != hz) { fprintf(stderr, "do not support speed %d,supported is %d\n", hz, ioctl_val); return (-1); } return (0); } /* * Usage():print help information * params:(none) * returns:(none) */ void Usage(void) { printf ("run: ./SndKit [-h] [-d device] [-c channel] [-b bits] [-f hz] [-l len] <-r|-p file>\n" "Description: implent raw sound record/play\n" "option:\n" "\t -h: print help informations\n" "\t -d device: assign sound device to record or playback(default /dev/dsp)\n" "\t -c channel: indicate in MONO or STEREO channel(default MONO)\n" "\t -b bits: assign sampling bits depth(default 8bits unsigned)\n" "\t -f hz: indicate sampling rate,i.e,frequence(default 8kbps)\n" "\t -l len: indicate recording sound's length(default 1024k)\n" "\t -r|-p file: indicate record in or playback(no default,must give out explicitly)\n" "e.g.:\n" "\t ./SndKit -h show help information\n" "\t ./SndKit -r record.wav record audio from microphone(/dev/dsp)\n" "\t ./SndKit -d /dev/dsp -p record.wav playback record.wav via /dev/dsp\n" "\t ./SndKit -b 8 -f 22 -r reacord.wav record audio in 8 bit & 22k hz\n" "\t ./SndKit -d /dev/dsp -c 2 -r record.wav record audio in /dev/dsp and stereo\n" "\t ./SndKit -r -l 40 record.wav record 40k audio data in record.wav\n"); }关于这个代码网上有很多
把这个文件静态编译成sndkit 放到busybox里面。
然后就用如下脚本启动qemu-kvm虚拟机:
#!/bin/sh export QEMU_AUDIO_DRV=alsa qemu32 -m 64 -kernel linux-3.14.12/arch/x86/boot/bzImage -append "root=/dev/sda rw" -boot c -hda busybox.img --soundhw es1370 -k en-us -net nic -net tap,ifname=tap0,script=no
oss: Could not initialize DAC oss: Failed to open `/dev/dsp' oss: Reason: No such file or directory oss: Could not initialize DAC oss: Failed to open `/dev/dsp' oss: Reason: No such file or directory audio: Failed to create voice `es1370.dac2' oss: Could not initialize ADC oss: Failed to open `/dev/dsp' oss: Reason: No such file or directory oss: Could not initialize ADC oss: Failed to open `/dev/dsp' oss: Reason: No such file or directory audio: Failed to create voice `es1370.adc
当虚拟机启动之后/dev/dsp文件也是存在的,但是不会发声。那一句是用来设置qemu-kvm虚拟机跟主机声卡通信的方式的,如没有那句虚拟机的声音数据就无法传到主机上播放出来。
与播放程序 sndkit 对应的就需要选择wav音频文件。
现在wav文件不好找了,可以自己用linux下的工具把mp3文件制作成wav文件:
首先把mp3文件分割成小文件可以用 mp3splt
$ sudo apt-get install mp3splt $ mp3splt wenqing.mp3 2.57 3.50 mp3splt 2.4.2 (13/05/12) - using libmp3splt 0.7.2 Matteo Trotta <mtrotta AT users.sourceforge.net> Alexandru Munteanu <io_fx AT yahoo.fr> THIS SOFTWARE COMES WITH ABSOLUTELY NO WARRANTY! USE AT YOUR OWN RISK! Processing file 'wenqing.mp3' ... info: file matches the plugin 'mp3 (libmad)' info: found Xing or Info header. Switching to frame mode... info: MPEG 1 Layer 3 - 44100 Hz - Joint Stereo - FRAME MODE - Total time: 4m.13s info: starting normal split File "wenqing_02m_57s__03m_50s.mp3" created Processed 8806 frames - Sync errors: 0 file split
然后把 mp3 转换为 wav 这个可以用 lame :
$ sudo apt-get install lame $ lame --decode wenqing_02m_57s__03m_50s.mp3 weqing.wav input: wenqing_02m_57s__03m_50s.mp3 (44.1 kHz, 2 channels, MPEG-1 Layer III) output: weqing.wav (16 bit, Microsoft WAVE) skipping initial 1105 samples (encoder+decoder delay) skipping final 1715 samples (encoder padding-decoder delay)
还有人说用mpg123很方便:
$ mpg123 -w out.wav wenqing_02m_57s__03m_06s.mp3 High Performance MPEG 1.0/2.0/2.5 Audio Player for Layer 1, 2, and 3. Version 0.2.13-4 (2011/09/29). Written and copyrights by Joe Drew, now maintained by Nanakos Chrysostomos and others. Uses code from various people. See 'README' for more! THIS SOFTWARE COMES WITH ABSOLUTELY NO WARRANTY! USE AT YOUR OWN RISK! Title : ���� Artist : ��� Album : ��Iii��������� Year : Comment : Genre : Playing MPEG stream from wenqing_02m_57s__03m_06s.mp3 ... MPEG 1.0 layer III, 128 kbit/s, 44100 Hz joint-stereo [0:09] Decoding of wenqing_02m_57s__03m_06s.mp3 finished.
$ file out.wav out.wav: RIFF (little-endian) data, WAVE audio, stereo 44100 Hz但是经过分析发现用mpg123得到的 wav文件不是线性PCM码,有压缩的(通过格式分析可以知道) 不适合作为数组使用!
关于wav文件格式见如下链接:
http://www.cnblogs.com/tiandsp/archive/2012/10/17/2728585.html
把这个文件拷贝到busybox里面系统启动后用如下脚本播放:
#!/bin/sh ./sndkit -c 2 -b 16 -f 44 -p wenqing.wav
下面附上我制作好的一个wav文件:
https://www.dropbox.com/s/es804xnesvrejec/wenqing.wav