您尚未登录。

楼主 # 2023-02-26 20:39:45

jinggx
会员
注册时间: 2022-05-14
已发帖子: 5
积分: 200

alsa-lib编译方法以及V3S音频功能测试

自己做了个V3S的开发板,现在慢慢研究一下软件,这两天调试好了音频功能,简单做了一些记录分享给需要的朋友。

下载alsa-lib和alsa-utils
https://www.alsa-project.org/main/index.php/Download
这里可以下载最新版本的alsa-lib和alsa-utils,我下载的都是1.2.8版的。

编译alsa-lib

tar xvf alsa-lib-1.2.8.tar.bz2
cd alsa-lib-1.2.8

配置生成Makefile

./configure --host=arm-linux-gnueabihf --prefix=/home/jinggx/V3S/audio/alsa-lib --enable-shared –with-configdir=/usr/share/alsa-arm

1. --host 指定编译器,这里指定为交叉编译器。
2. --prefix 指定编译后文件的安装路径,即make install安装的位置。安装后创建 的lib 和 include都是给ARM开发板用的,宿主机用不到,所以此目录可以指定在任意位置。
3. --with-configdir 指定 conf 文件的安装目录,该目录将会直接移植到你的ARM开发板系统中,并且复制到开发板系统中的目录位置必须与编译时指定的-with-configdir完全一致,这一点非常重要,绝对路径必须完全一致!

./configure 成功之后才可以make。如果要重新./configure,建议先删掉Makefile文件。我这个菜鸟重新./configure 的时候没有注意到命令窗口的错误提示,然后继续make,结果一直make最初生成的Makefile文件,然后我就很蒙圈为什么修改了./configure 一直不起作用?我太傻了!!

make
make install

make instal可能会不成功,我在网上搜索到了别人分享的经验:需要切换到root用户下执行才可以。

sudo su
make
make install

编译alsa-utils

tar xvf alsa-utils-1.2.8tar.bz2
cd alsa-utils-1.2.8

配置生成Makefile

./configure --host=arm-linux-gnueabihf --prefix=/home/jinggx/V3S/audio/alsa-utils --with-alsa-inc-prefix=/home/jinggx/V3S/audio/alsa-lib/include CFLAGS="-I/home/jinggx/V3S/audio/alsa-lib/include" LDFLAGS="-L/home/jinggx/V3S/audio/alsa-lib/lib -lasound" --disable-alsamixer --disable-xmlto

1. --host 指定编译器,与 alsa-lib 的配置选项相同
2. --prefix 指定编译后文件的安装路径,与 alsa-lib 的配置选项相同
3. --with-alsa-inc-prefix 指定头文件目录,因为 ./configure  程序会去该目录检查版本情况。如果不指定的话,则会直接去默认目录 ( 即宿主机对应的 alsalib 目录中寻找,因此可能会有错误
4. CFLAGS 用于编译时指定的编译选项,在这里要使用 alsa-lib 编译后生成的头文件,指定该头文件所在目录,-I与路径之间不能有空格
5. LDFLAGS 用于编译时指定连接库文件,在这里要使用 alsa-lib 编译生成的库文件, 指定该头文件所在目录,-L与路径之间不能有空格

与前面编译alsa-lib类似,应该还是必须在root用户下执行才可以

sudo su
make
make install

如果编译遇到如下问题

make[2]: Entering directory '/home/jinggx/V3S/audio/alsa-utils-1.2.8/alsaconf/po'
mv: cannot stat 't-ja.gmo': No such file or directory
make[2]: *** [Makefile:41: ja.gmo] Error 1
make[2]: Leaving directory '/home/jinggx/V3S/audio/alsa-utils-1.2.8/alsaconf/po'
make[1]: *** [Makefile:480: all-recursive] Error 1
make[1]: Leaving directory '/home/jinggx/V3S/audio/alsa-utils-1.2.8/alsaconf'
make: *** [Makefile:461: all-recursive] Error 1

需要安装gettext

sudo apt install gettext

再次执行./configure ,然后再次编译即可。

lib 和 utils 安装到开发板
1. 把编译alsa-lib安装在$(--prefix)/lib目录中的libasound.la  libasound.so  libasound.so.2  libasound.so.2.0.0 拷贝到开发板上的/lib目录
2. 把编译alsa-lib时$(–with-configdir)整个目录拷贝到开发板上同样的位置,如果开发板上不存一样的目录则新建,必须保证编译时的$(–with-configdir)与开发板上的路径完全一致
3. 把编译alsa-utils安装在$(--prefix)/bin和$(--prefix)/sbin目录中生成的可以执行文件复制到开发板,我这里用到的文件只有aplay, amixer这2个,amixer用于设置声卡,aplay用于播放测试。aplay复制到开发板的/bin目录;amixer复制到开发板的/sbin目录,测试中发现amixer放在开发板的/bin目录不能运行,不知何故

测试
在开发板终端上ls -l /dev/snd查看,必须保证 /dev/snd/ 目录中应包含以下几个设备文件:
controlC0, pcmC0D0c, pcmC0D0p, timer
开机后默认状态是静音状态,需要取消掉静音状态

amixer -c 0 sset 'Headphone',0 100% unmute

切换到存放音频文件目录下

aplay xxx.wav

耳机连接到开发板的音频输出口,应该可以听到音乐声了。

使用alsa-lib编写应用程序
我自己写的简单wav文件播放代码

#include <stdio.h>
#include <stdlib.h>
#include <alsa/asoundlib.h>


#define FRAMES_PLAYBACK 1024    // define how many frames write to snd device at a time,
                                // 1 FRAME = 1 block_align
typedef struct _wave_header {
    uint8_t   chunk_id[4];      // signature "RIFF"
    uint32_t  chunk_size;       // size of next address to file end,
    uint8_t   format[4];        // signature "WAVE"
    uint8_t   sub_chunk1_id[4]; // signature "fmt "
    uint32_t  sub_chunk1_size;  // size of sub chunk1 (it seems always 0x00000010)
    uint16_t  audio_format;     // 0x0001 is PCM code
    uint16_t  num_channels;     // audio channels
    uint32_t  sample_rate;      // sample frequence
    uint32_t  byte_rate;        // byte rate pes second
    uint16_t  block_align;      // sample align bytes, 1 block_align = 1 FRAME
    uint16_t  bits_per_sample;  // bits per sample
    uint8_t   sub_chunk2_id[4]; // signature "data"
    uint32_t  sub_chunk2_size;  // wave samples size
} wave_header_t;

static void wav_info_dump(wave_header_t* wav_header)
{
    printf("wave header:\n");

    printf("chunk id    = %c%c%c%c\n",
                          wav_header->chunk_id[0],wav_header->chunk_id[1],
                          wav_header->chunk_id[2],wav_header->chunk_id[3]);
    printf("format      = %c%c%c%c\n",
                          wav_header->format[0],wav_header->format[1],
                          wav_header->format[2],wav_header->format[3]);
    printf("channels    = %d\n",wav_header->num_channels);
    printf("sample rate = %d\n",wav_header->sample_rate);
    printf("byte rate   = %d\n",wav_header->byte_rate * 8);
    printf("block align = %d\n",wav_header->block_align);
    printf("bits        = %d\n",wav_header->bits_per_sample);
    printf("samples size= %d\n",wav_header->sub_chunk2_size);
    //printf("Calculated information:\n");
    printf("file size   = %d\n",wav_header->chunk_size + 8);
    printf("data pos    = %d\n",(uint32_t)sizeof(wave_header_t));
    printf("duration    = %d s\n",wav_header->sub_chunk2_size / wav_header->block_align / wav_header->sample_rate);
}

int main(int argc, char *argv[])
{
    int  i, ret;
    char *pbuf;
    FILE *fp;
    snd_pcm_t           *h_playback;
    snd_pcm_hw_params_t *hw_params;
    snd_pcm_uframes_t   frames, buff_siz;
    wave_header_t       wav_header;

    if (argc != 2) {
        printf("cmd error: use ./wav xx.wav\n");
        exit(-1);
    }
    printf("play %s\n", argv[1]);

    fp = fopen(argv[1], "rb");
    if (fp == NULL) {
        printf("fopen %s failed\n", argv[1]);
        exit(-1);
    }
    fread((uint8_t *)&wav_header, 1, sizeof(wave_header_t), fp);
    wav_info_dump((wave_header_t*)&wav_header);

    // 1. 打开PCM,最后一个参数为0意味着标准配置
    if (snd_pcm_open(&h_playback, "default", SND_PCM_STREAM_PLAYBACK, 0) < 0) {
        perror("snd_pcm_open");
        fclose(fp);
        exit(-1);
    }
    // 2. 分配snd_pcm_hw_params_t结构体
    if (snd_pcm_hw_params_malloc(&hw_params) < 0) {
        perror("snd_pcm_hw_params_malloc");
        goto end;
    }
    // 3. 初始化hw_params
    if (snd_pcm_hw_params_any(h_playback, hw_params) < 0) {
        perror("snd_pcm_hw_params_any");
        goto end;
    }
    // 4. 初始化访问权限
    if (snd_pcm_hw_params_set_access(h_playback, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) {
        perror("snd_pcm_hw_params_set_access");
        goto end;
    }
    // 5. 初始化采样格式SND_PCM_FORMAT_S16,16位
    if (wav_header.bits_per_sample == 24) {
        if (snd_pcm_hw_params_set_format(h_playback, hw_params, SND_PCM_FORMAT_S24) < 0) {
            perror("snd_pcm_hw_params_set_format");
            goto end;
        }
    } else if (wav_header.bits_per_sample == 16) {
        if (snd_pcm_hw_params_set_format(h_playback, hw_params, SND_PCM_FORMAT_S16) < 0) {
            perror("snd_pcm_hw_params_set_format");
            goto end;
        }
    } else {
        printf("unsupport wav file, bits_per_sample=%d\n", wav_header.bits_per_sample);
        goto end;
    }
    // 6. 设置采样率,如果硬件不支持我们设置的采样率,将使用最接近的
    ret = wav_header.sample_rate;
    i = 0;
    if (snd_pcm_hw_params_set_rate_near(h_playback, hw_params, &ret, &i) < 0) {
        perror("snd_pcm_hw_params_set_rate_near");
        goto end;
    }
    // 7. 设置通道数量
    if (wav_header.num_channels == 2  ||  wav_header.num_channels == 1) {
        if (snd_pcm_hw_params_set_channels(h_playback, hw_params, wav_header.num_channels ) < 0) {
            perror("snd_pcm_hw_params_set_channels");
            goto end;
        }
    } else {
        printf("unsupport number of channels, wav_header.num_channels = %d\n", wav_header.num_channels );
        goto end;
    }
    // 8. 设置数据缓冲区大小
    frames = FRAMES_PLAYBACK;
    buff_siz = frames * wav_header.block_align;
    if (snd_pcm_hw_params_set_buffer_size_near(h_playback, hw_params, &buff_siz) < 0) {
        perror("snd_pcm_hw_params_set_buffer_size_near");
        goto end;
    }
    // 9. 设置hw_params
    if (snd_pcm_hw_params(h_playback, hw_params) < 0) {
        perror("snd_pcm_hw_params");
        goto end;
    }
    // allocate file read buffer
    pbuf = (char *) malloc(buff_siz);
    if (pbuf == NULL) {
        perror("pbuf malloc error");
        goto end;
    }

    i = 0;
    while (i < wav_header.sub_chunk2_size) {
        ret = fread(pbuf, 1, buff_siz, fp);
        if (ret != buff_siz) {
            if(ret == 0) {
                fprintf(stderr, "end of file\n");
                break;
            } else {
                frames = ret / wav_header.block_align;
            }
        }
        i = i + ret;
        
        ret = snd_pcm_writei(h_playback, pbuf, frames);
        if (ret < 0){
            if (ret == -EPIPE) {                        // EPIPE means underrun
                fprintf(stderr, "underrun occurred\n");
                snd_pcm_prepare(h_playback);// 完成硬件参数设置,使设备准备好
            } else {
                fprintf(stderr, "error from writei: %s\n", snd_strerror(ret));
            }
        }
    }

    printf("play end\n");

    free(pbuf);

    end:
    // 10. 关闭PCM设备句柄
    snd_pcm_drain(h_playback);
    snd_pcm_close(h_playback);

    fclose(fp);

    return 0;
}

Makefile代码

PREFIX = /home/jinggx/V3S/audio/alsa-lib
TARGET = wav
OBJS = wav

# set default to nothing for native builds
CROSS_COMPILE ?=

ifeq ($(CROSS_COMPILE),$())
CFLAGS  = -I.
LDFLAGS = -L.
else
CFLAGS  = -I $(PREFIX)/include
LDFLAGS = -L $(PREFIX)/lib
endif

CC	= $(CROSS_COMPILE)gcc

$(TARGET): %:%.c
	$(CC) $< -o $@ $(CFLAGS) $(LDFLAGS) -lasound

.PHONY: clean
clean:
	rm -f *.o $(OBJS)

编译生成在电脑上运行的可执行文件

make

编译生成在V3S开发板上运行的可执行文件

make CROSS_COMPILE=arm-linux-gnueabihf-

将wav音频文件放在可执行文件生成的目录下

./wav xxx.wav

耳机就可以听到音乐声了。

实际写代码测试个人感觉这个alsa-lib不太好用,比较啰嗦,参数也有点混乱,不如RT-Thread驱动那么简洁明了。

这些东西花费了我三四天的时间才搞出来,现在分享在这里,希望初学者遇到类似问题时能有一点点帮助。

离线

页脚

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

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