您尚未登录。

楼主 # 2021-07-14 09:06:48

xboot
会员
注册时间: 2019-10-15
已发帖子: 692
积分: 434

乡下老鼠与城里老鼠交了个朋友,我来试试PWM直驱喇叭

先来听听:
1,接普通功放播放音乐

2,接普通功放播放语音

3,接普通功放音乐与语音一起混音播放

4,不接功放,pwm直驱喇叭,没有低通,没有电容隔直

离线

楼主 #1 2021-07-14 09:12:53

xboot
会员
注册时间: 2019-10-15
已发帖子: 692
积分: 434

Re: 乡下老鼠与城里老鼠交了个朋友,我来试试PWM直驱喇叭

此实验,没有采用任何低通滤波器,也没有用电容隔直,pwm输出的方波直接驱动,驱动实现上,考虑到CPU的负荷,任何码流都重采样8KHZ, 16BITS, 单通道。CPU每125微秒中断一次,PWM载波频率62.5KHZ。

这是个通用驱动,所有平台都可以复用,只要填个JSON设备树即可。整个驱动仅300行左右,考虑通用性,仅使用了定时器和PWM,未采用dma。

驱动代码

/*
 * driver/audio-pwm.c
 *
 * Copyright(c) 2007-2021 Jianjun Jiang <8192542@qq.com>
 * Official site: http://xboot.org
 * Mobile phone: +86-18665388956
 * QQ: 8192542
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#include <xboot.h>
#include <pwm/pwm.h>
#include <audio/audio.h>

struct audio_pwm_pdata_t {
	struct timer_t timer;
	struct fifo_t * fifo;
	struct pwm_t * pwm;
	int polarity;

	enum audio_rate_t rate;
	enum audio_format_t fmt;
	int ch;
	audio_callback_t cb;
	void * data;
	int running;
};

static void audio_pwm_resample(int16_t * out, int osr, int osample, int16_t * in, int isr, int isample)
{
	if(out && in)
	{
		float fixed = (1.0 / (1LL << 32));
		uint64_t frac = (1LL << 32);
		uint64_t step = ((uint64_t)((float)isr / (float)osr * frac + 0.5));
		uint64_t offset = 0;
		for(int i = 0; i < osample; i += 1)
		{
			*out++ = (int16_t)(in[0] + (in[1] - in[0]) * ((float)(offset >> 32) + ((offset & (frac - 1)) * fixed)));
			offset += step;
			in += (offset >> 32);
			offset &= (frac - 1);
		}
	}
}

static int audio_pwm_timer_function(struct timer_t * timer, void * data)
{
	struct audio_t * audio = (struct audio_t *)(data);
	struct audio_pwm_pdata_t * pdat = (struct audio_pwm_pdata_t *)audio->priv;
	uint32_t inbuf[256];
	int16_t outbuf[512];
	int16_t v;
	int isample, osample;
	int i;

	if(pdat->running)
	{
		if(__fifo_len(pdat->fifo) <= 0)
		{
			switch(pdat->fmt)
			{
			case AUDIO_FORMAT_S8:
				if(pdat->ch == 1)
				{
					isample = pdat->cb(pdat->data, inbuf, sizeof(inbuf));
					if(isample > 0)
					{
						osample = isample * 8000.0 / pdat->rate;
						osample -= osample % 2;
						int8_t * p = &((int8_t *)inbuf)[(isample - 1)];
						int16_t * q = &((int16_t *)inbuf)[osample - 1];
						for(i = 0; i < isample; i++)
							*q-- = (int16_t)((int)(*p--) * 128);
						audio_pwm_resample(outbuf, 8000, osample, (int16_t *)inbuf, pdat->rate, isample);
						__fifo_put(pdat->fifo, (unsigned char *)outbuf, osample << 1);
					}
				}
				else if(pdat->ch == 2)
				{
					isample = pdat->cb(pdat->data, inbuf, sizeof(inbuf)) >> 1;
					if(isample > 0)
					{
						osample = isample * 8000.0 / pdat->rate;
						osample -= osample % 2;
						int8_t * p = &((int8_t *)inbuf)[(isample - 1) << 1];
						int16_t * q = &((int16_t *)inbuf)[osample - 1];
						for(i = 0; i < isample; i++, p-=2)
							*q-- = (int16_t)(((int)p[0] + (int)p[1]) * 128);
						audio_pwm_resample(outbuf, 8000, osample, (int16_t *)inbuf, pdat->rate, isample);
						__fifo_put(pdat->fifo, (unsigned char *)outbuf, osample << 1);
					}
				}
				break;
			case AUDIO_FORMAT_S16:
				if(pdat->ch == 1)
				{
					isample = pdat->cb(pdat->data, inbuf, sizeof(inbuf)) >> 1;
					if(isample > 0)
					{
						osample = isample * 8000.0 / pdat->rate;
						osample -= osample % 2;
						audio_pwm_resample(outbuf, 8000, osample, (int16_t *)inbuf, pdat->rate, isample);
						__fifo_put(pdat->fifo, (unsigned char *)outbuf, osample << 1);
					}
				}
				else if(pdat->ch == 2)
				{
					isample = pdat->cb(pdat->data, inbuf, sizeof(inbuf)) >> 2;
					if(isample > 0)
					{
						osample = isample * 8000.0 / pdat->rate;
						osample -= osample % 2;
						int16_t * p = (int16_t *)inbuf;
						int16_t * q = (int16_t *)inbuf;
						for(i = 0; i < isample; i++, p+=2)
							*q++ = (int16_t)(((int)p[0] + (int)p[1]) >> 1);
						audio_pwm_resample(outbuf, 8000, osample, (int16_t *)inbuf, pdat->rate, isample);
						__fifo_put(pdat->fifo, (unsigned char *)outbuf, osample << 1);
					}
				}
				break;
			case AUDIO_FORMAT_S24:
				if(pdat->ch == 1)
				{
					isample = pdat->cb(pdat->data, inbuf, sizeof(inbuf)) / 3;
					if(isample > 0)
					{
						osample = isample * 8000.0 / pdat->rate;
						osample -= osample % 2;
						int8_t * p = (int8_t *)inbuf;
						int16_t * q = (int16_t *)inbuf;
						for(i = 0; i < isample; i++, p+=3)
							*q++ = (int16_t)((int32_t)((p[2] << 24) | (p[1] << 16) | (p[0] << 8)) >> 16);
						audio_pwm_resample(outbuf, 8000, osample, (int16_t *)inbuf, pdat->rate, isample);
						__fifo_put(pdat->fifo, (unsigned char *)outbuf, osample << 1);
					}
				}
				else if(pdat->ch == 2)
				{
					isample = pdat->cb(pdat->data, inbuf, sizeof(inbuf)) / 6;
					if(isample > 0)
					{
						osample = isample * 8000.0 / pdat->rate;
						osample -= osample % 2;
						int8_t * p = (int8_t *)inbuf;
						int16_t * q = (int16_t *)inbuf;
						for(i = 0; i < isample; i++, p+=6)
						{
							int32_t l = (int32_t)((p[2] << 24) | (p[1] << 16) | (p[0] << 8));
							int32_t r = (int32_t)((p[5] << 24) | (p[4] << 16) | (p[3] << 8));
							*q++ = (int16_t)((l + r) >> 17);
						}
						audio_pwm_resample(outbuf, 8000, osample, (int16_t *)inbuf, pdat->rate, isample);
						__fifo_put(pdat->fifo, (unsigned char *)outbuf, osample << 1);
					}
				}
				break;
			case AUDIO_FORMAT_S32:
				if(pdat->ch == 1)
				{
					isample = pdat->cb(pdat->data, inbuf, sizeof(inbuf)) >> 2;
					if(isample > 0)
					{
						osample = isample * 8000.0 / pdat->rate;
						osample -= osample % 2;
						int32_t * p = (int32_t *)inbuf;
						int16_t * q = (int16_t *)inbuf;
						for(i = 0; i < isample; i++)
							*q++ = (int16_t)(*p++ >> 16);
						audio_pwm_resample(outbuf, 8000, osample, (int16_t *)inbuf, pdat->rate, isample);
						__fifo_put(pdat->fifo, (unsigned char *)outbuf, osample << 1);
					}
				}
				else if(pdat->ch == 2)
				{
					isample = pdat->cb(pdat->data, inbuf, sizeof(inbuf)) >> 3;
					if(isample > 0)
					{
						osample = isample * 8000.0 / pdat->rate;
						osample -= osample % 2;
						int32_t * p = (int32_t *)inbuf;
						int16_t * q = (int16_t *)inbuf;
						for(i = 0; i < isample; i++, p+=2)
							*q++ = (int16_t)((p[0] + p[1]) >> 17);
						audio_pwm_resample(outbuf, 8000, osample, (int16_t *)inbuf, pdat->rate, isample);
						__fifo_put(pdat->fifo, (unsigned char *)outbuf, osample << 1);
					}
				}
				break;
			default:
				break;
			}
		}
		if(__fifo_get(pdat->fifo, (unsigned char *)&v, 2) == 2)
		{
			pwm_config(pdat->pwm, ((((int)v + 32768) * 16000) >> 16), 16000, pdat->polarity);
			pwm_enable(pdat->pwm);
			timer_forward_now(&pdat->timer, us_to_ktime(125));
			return 1;
		}
	}
	__fifo_reset(pdat->fifo);
	pwm_disable(pdat->pwm);
	pdat->running = 0;
	return 0;
}

static void audio_pwm_playback_start(struct audio_t * audio, enum audio_rate_t rate, enum audio_format_t fmt, int ch, audio_callback_t cb, void * data)
{
	struct audio_pwm_pdata_t * pdat = (struct audio_pwm_pdata_t *)audio->priv;

	if(!pdat->running)
	{
		pdat->rate = rate;
		pdat->fmt = fmt;
		pdat->ch = ch;
		pdat->cb = cb;
		pdat->data = data;
		timer_start_now(&pdat->timer, ms_to_ktime(1));
		pdat->running = 1;
	}
}

static void audio_pwm_playback_stop(struct audio_t * audio)
{
	struct audio_pwm_pdata_t * pdat = (struct audio_pwm_pdata_t *)audio->priv;
	pdat->cb = NULL;
	pdat->data = NULL;
}

static int audio_pwm_ioctl(struct audio_t * audio, const char * cmd, void * arg)
{
	return -1;
}

static struct device_t * audio_pwm_probe(struct driver_t * drv, struct dtnode_t * n)
{
	struct audio_pwm_pdata_t * pdat;
	struct pwm_t * pwm;
	struct audio_t * audio;
	struct device_t * dev;

	if(!(pwm = search_pwm(dt_read_string(n, "pwm-name", NULL))))
		return NULL;

	pdat = malloc(sizeof(struct audio_pwm_pdata_t));
	if(!pdat)
		return NULL;

	audio = malloc(sizeof(struct audio_t));
	if(!audio)
	{
		free(pdat);
		return NULL;
	}

	timer_init(&pdat->timer, audio_pwm_timer_function, audio);
	pdat->fifo = fifo_alloc(1024);
	pdat->pwm = pwm;
	pdat->polarity = dt_read_bool(n, "pwm-polarity", 0);
	pdat->running = 0;

	audio->name = alloc_device_name(dt_read_name(n), -1);
	audio->playback_start = audio_pwm_playback_start;
	audio->playback_stop = audio_pwm_playback_stop;
	audio->capture_start = NULL;
	audio->capture_stop = NULL;
	audio->ioctl = audio_pwm_ioctl;
	audio->priv = pdat;

	if(!(dev = register_audio(audio, drv)))
	{
		timer_cancel(&pdat->timer);
		fifo_free(pdat->fifo);
		free_device_name(audio->name);
		free(audio->priv);
		free(audio);
		return NULL;
	}
	return dev;
}

static void audio_pwm_remove(struct device_t * dev)
{
	struct audio_t * audio = (struct audio_t *)dev->priv;
	struct audio_pwm_pdata_t * pdat = (struct audio_pwm_pdata_t *)audio->priv;

	if(audio)
	{
		unregister_audio(audio);
		timer_cancel(&pdat->timer);
		fifo_free(pdat->fifo);
		free_device_name(audio->name);
		free(audio->priv);
		free(audio);
	}
}

static void audio_pwm_suspend(struct device_t * dev)
{
}

static void audio_pwm_resume(struct device_t * dev)
{
}

static struct driver_t audio_pwm = {
	.name		= "audio-pwm",
	.probe		= audio_pwm_probe,
	.remove		= audio_pwm_remove,
	.suspend	= audio_pwm_suspend,
	.resume		= audio_pwm_resume,
};

static __init void audio_pwm_driver_init(void)
{
	register_driver(&audio_pwm);
}

static __exit void audio_pwm_driver_exit(void)
{
	unregister_driver(&audio_pwm);
}

driver_initcall(audio_pwm_driver_init);
driver_exitcall(audio_pwm_driver_exit);

https://gitee.com/xboot/xboot/blob/master/src/driver/audio/audio-pwm.c

最近编辑记录 xboot (2021-07-14 09:13:31)

离线

楼主 #3 2021-07-14 12:18:34

xboot
会员
注册时间: 2019-10-15
已发帖子: 692
积分: 434

Re: 乡下老鼠与城里老鼠交了个朋友,我来试试PWM直驱喇叭

是的,f1c100s的音频裸机驱动没找到参考,不知谁研究过这个问题,如果有参考的话,可以去写个真正的音频驱动

离线

楼主 #6 2021-07-14 15:26:02

xboot
会员
注册时间: 2019-10-15
已发帖子: 692
积分: 434

Re: 乡下老鼠与城里老鼠交了个朋友,我来试试PWM直驱喇叭

@微凉VeiLiang

太棒了,终于可以白嫖了

离线

楼主 #8 2021-07-15 11:12:50

xboot
会员
注册时间: 2019-10-15
已发帖子: 692
积分: 434

Re: 乡下老鼠与城里老鼠交了个朋友,我来试试PWM直驱喇叭

不同采样率,不同通道数,不同量化深度,这些音源一起混音,直接去写这种算法,估计头都会炸。
现在xboot的方法是,将音源统一采样到48KHZ,双通道,16bit量化,有符号小端格式,这些音源然后去混音就简单了。
而且可以对每一个音源进行各种操作,比如设定循环次数,调整左右声道的音量,gain,pan,pitch,之类的都可以,当然pitch变调这个就没实现了。

如果要更高级的,比如控制脚步声,慢慢消失,等,这种就需要上3D 音效框架了,做游戏的用得多,其他场景很少用。

最近编辑记录 xboot (2021-07-15 11:13:24)

离线

楼主 #10 2021-07-15 11:50:16

xboot
会员
注册时间: 2019-10-15
已发帖子: 692
积分: 434

Re: 乡下老鼠与城里老鼠交了个朋友,我来试试PWM直驱喇叭

就是ease相关的缓动函数,任何变量在变化时,都可以用上缓动函数,增强体验。3D音效也是一个很庞大的课题。比如敌人围着你转了一圈叫嚣着,那么你的应该要听到,这家伙的声音围着你也转了一圈,人的双耳是有能力进行声源定位的,所以做游戏时,这种场景也得模拟出来,一切都为了沉静式体验。

离线

页脚

工信部备案:粤ICP备20025096号 Powered by FluxBB

感谢为中文互联网持续输出优质内容的各位老铁们。 QQ: 516333132, 微信(wechat): whycan_cn (哇酷网/挖坑网/填坑网) service@whycan.cn