qemu模拟alsa声卡

    以前工作中做过关于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'

    配置内核选项,由于看到网上大部分用qemu模拟的时候都是选择ES1370 pci声卡,所以就在内核里面配置这个声卡的驱动。 另外由于alsa完全兼容oss,在内核里面还有一个oss的模拟模块,当在调试alsa声卡驱动的时候如果使用alsa(包括 pcm/midi/sequencer)和alsa-lib(很复杂的一个库)那么调试会很麻烦且不容易排错,  可以使用这个oss模拟模块,用这个调试通了再用alsa那套东西就很容易了。

qemu模拟alsa声卡_第1张图片

上图是 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

注意上面的那一句export QEMU_AUDIO_DRV=alsa 是必不可少的,如果不加上那么在启动虚拟机的时候会提示:

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





你可能感兴趣的:(qemu-kvm,sndkit,alsa模拟oss,ES1370)