[ALSA]从零开始,使用ALSA驱动播放一个音频

前言

最近学了不少有关音频相关的,最近搞一下ALSA驱动

安装

参考Linux应用开发【第八章】ALSA应用开发 中提到的ALSA库及工具章节,本文中有比较详细的有关ALSA驱动引用程序怎么安装的,这里不再赘述。

关于ALSA,就当成一个音频有关的,建立在Linux操作系统上类似WinApi的东西就可以了,实际应用起来也比较简单,之后会举例。

第一个项目

为了进行第一个ALSA项目的开发,那么我们先来建立一个CMake工程。在安装部分我们已经安装了ALSA驱动,其实这个时候就已经将ALSA的头文件加入到我们的项目中来了,可以直接试着在main文件里面加上这个头文件试试会不会报错:

#include <alsa/asoundlib.h>

如果这个时候CMake生成没有报错,那么引入就是正常的。

1. 引入alsa依赖

在Linux上开发是需要引入alsa的依赖以及符号文件的,这里我们需要以下语句,这里我提供一个最精简的CMakeLists.txt范本:

cmake_minimum_required(VERSION 3.0)
project(alsa_example)

# 获得ALSA的Package
find_package(ALSA REQUIRED)

add_executable(alsa_example main.cpp)

# 链接
target_link_libraries(alsa_example PRIVATE ${ALSA_LIBRARY})

# 头文件引入
target_include_directories(alsa_example PRIVATE ${ALSA_INCLUDE_DIRS})

这样我们就能先将ALSA的依赖引用了。

2.写个简单的程序来将当前所有的音频设备全部列举了

在正式操作一个音频设备之前,我们当然了,需要知道我们需要的音频设备到底是哪一个,那我们现在将所有的音频设备信息全部打印出来,如下:

#include <alsa/asoundlib.h>
#include <iostream>

int main() {
    snd_ctl_t *handle;
    snd_ctl_card_info_t *info;
    int card = -1;

    snd_ctl_card_info_alloca(&info);

    // Iterate over sound cards
    while (snd_card_next(&card) >= 0 && card >= 0) {
        char name[32];
        sprintf(name, "hw:%d", card);

        if (snd_ctl_open(&handle, name, 0) < 0) {
            std::cerr << "Cannot open control for card " << card << std::endl;
            continue;
        }

        if (snd_ctl_card_info(handle, info) < 0) {
            std::cerr << "Cannot get card info for card " << card << std::endl;
            snd_ctl_close(handle);
            continue;
        }

        std::cout << "Card " << card << ": " << snd_ctl_card_info_get_name(info) << std::endl;

        snd_ctl_close(handle);
    }

    return 0;
}

返回值如下:

Card 0: rockchip,dp0
Card 1: rockchip-hdmi0
Card 2: rockchip-hdmi1
Card 3: rockchip,es8388
Card 4: rockchip,hdmiin

我这里是做了点处理的,实际上返回值里面并没有告诉你什么Card 0这种,而是告诉你hw:0,也就是hardware的意思,总之你只用知道每个音频设备的唯一标识应该是类似hw:0的形式。

这里我们可以看音频设备的名称,其中dp0 hdmi hdmi1 hdmiin都是显示器接口音频设备,我们这里显然是用不上的,那么我们就找到前面板的显示音频卡设备es8388(不同的设备输出声卡应该不同,这个需要根据具体声卡而定。

我们这里知道我们需要选中的前面板声卡设备是hw:3,这个之后我们会用到

3. 向音频设备里面写数据

我们知道,Linux下一切皆文件嘛,播放音频实际上就是往某个设备句柄中写入音频信号,这里我们尝试一下向音频设备写入信号。

流程如下:

  1. 使用二进制流的方式打开mp3文件
  2. 打开声卡pcm句柄
  3. 设置pcm句柄的属性
  4. 循环将二进制流以buffer的形式写入到pcm句柄内

实际代码如下:

#include <alsa/asoundlib.h>
#include <iostream>
#include <fstream>
#include <vector>

#define PCM_DEVICE "hw:3"  // Replace with your desired device, e.g., "hw:3"
#define MP3_FILE_PATH "/home/orangepi/workhsop/testProject/123.mp3"  // Replace with your MP3 file path

int main() {
    const char *mp3File = MP3_FILE_PATH;

    // Open the MP3 file
    std::ifstream file(mp3File, std::ios::binary);
    if (!file) {
        std::cerr << "Error opening file: " << mp3File << std::endl;
        return 1;
    }

    // Initialize ALSA
    snd_pcm_t *handle;
    if (snd_pcm_open(&handle, PCM_DEVICE, SND_PCM_STREAM_PLAYBACK, 0) < 0) {
        std::cerr << "Error opening PCM device " << PCM_DEVICE << std::endl;
        return 1;
    }

    // Set parameters: 44100 Hz, stereo, 16-bit little-endian
    snd_pcm_set_params(handle,
                       SND_PCM_FORMAT_S16_LE,
                       SND_PCM_ACCESS_RW_INTERLEAVED,
                       2,           // Channels (stereo)
                       44100,       // Sample rate
                       0,           // Soft resampling disabled
                       500000);     // Latency in microseconds

    // Buffer for audio data
    std::vector<char> buffer(4096);

    // Read and play audio data
    while (file.read(buffer.data(), buffer.size())) {
        int num_samples = buffer.size() / 2;  // Assuming 16-bit samples (2 bytes per sample)
        int err = snd_pcm_writei(handle, buffer.data(), num_samples);
        if (err < 0) {
            std::cerr << "Playback error: " << snd_strerror(err) << std::endl;
            break;
        }
    }

    // Close PCM handle
    snd_pcm_close(handle);

    return 0;
}

这样我们就完成了向设备中写音频这件事,但是这个音频爆炸吵的同时,也全是乱码,显然这并不是我们需要的结果。这是因为我们播放的是mp3音频,是经过了一系列压缩算法的文件,并不是可以直接用于播放的有实际意义的音频信息。在我们正式播放音频之前,需要使用mpg123库对音频进行解码,得到pcm音频流然后再一点点将这个流写入到音频设备内。

4. 使用mpg123解码

如果需要使用mpg123解码mp3文件,首先用sudo apt-get install mpg123 将mpg123安装到本地之后,需要在CMakeLists.txt下引入这个mpg123库,为了方便理解,我这里还是将最小限度的mpg123的引入方式

cmake_minimum_required(VERSION 3.0)
project(MyAudioPlayer)

# 设置C++标准
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 查找ALSA库
find_package(ALSA REQUIRED)
include_directories(${ALSA_INCLUDE_DIRS})

# 查找mpg123库
find_package(PkgConfig REQUIRED)
pkg_search_module(MPG123 REQUIRED mpg123)
include_directories(${MPG123_INCLUDE_DIRS})

# 添加可执行文件
add_executable(audio_player main.cpp)

# 链接ALSA和mpg123库
target_link_libraries(audio_player ${ALSA_LIBRARIES} ${MPG123_LIBRARIES})

写出一段带解码和播放的代码,如下:

#include <iostream>
#include <vector>
#include <alsa/asoundlib.h>
#include <mpg123.h>

#define PCM_DEVICE "hw:3"  // 目标音频设备
#define MP3_FILE_PATH "/home/orangepi/workhsop/testProject/123.mp3"  // 目标音频

int main() {
    const char *mp3File = MP3_FILE_PATH;

    // 初始化mpg123库
    mpg123_handle *mh;
    mpg123_init();
    mh = mpg123_new(NULL, NULL);

    // 打开mp3文件
    if (mpg123_open(mh, mp3File) != MPG123_OK) {
        std::cerr << "Error opening file: " << mp3File << std::endl;
        return 1;
    }

    // 获取格式信息
    int channels, encoding;
    long rate;
    mpg123_getformat(mh, &rate, &channels, &encoding);

    // 初始化ALSA
    snd_pcm_t *handle;
    if (snd_pcm_open(&handle, PCM_DEVICE, SND_PCM_STREAM_PLAYBACK, 0) < 0) {
        std::cerr << "Error opening PCM device " << PCM_DEVICE << std::endl;
        return 1;
    }

    // 设置声卡信息
    snd_pcm_set_params(handle,
                       SND_PCM_FORMAT_S16_LE,
                       SND_PCM_ACCESS_RW_INTERLEAVED,
                       channels,
                       rate,
                       0,
                       500000);

    // 缓冲区
    std::vector<short> buffer(4096);

    // 读取并播放音频
    size_t bytesRead;
    //使用mpg123进行音频信息解码,获得pcm音频信号
    while (mpg123_read(mh, reinterpret_cast<unsigned char*>(buffer.data()), buffer.size() * sizeof(short), &bytesRead) == MPG123_OK) {
        int num_samples = bytesRead / sizeof(short);
        int err = snd_pcm_writei(handle, buffer.data(), num_samples);
        if (err < 0) {
            std::cerr << "Playback error: " << snd_strerror(err) << std::endl;
            break;
        }
    }

    // Close mpg123 handle
    mpg123_close(mh);
    mpg123_delete(mh);
    mpg123_exit();

    // Close PCM handle
    snd_pcm_close(handle);

    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/766832.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

[OC]萝卜圈Python手动机器人脚本

这是给机器人设置的端口&#xff0c;对照用 代码 # #作者:溥哥’ ##机器人驱动主程序 #请在main中编写您自己的机器人驱动代码 import msvcrt def main():a"none"while True:key_input msvcrt.getch()akey_inputif abw:print(a)robot_drv.set_motors(1,40,2,40,3,…

(漏洞检查项) | 任意文件包含漏洞 file-include

(漏洞检查项)|任意文件包含漏洞 file-include 漏洞场景 1.含有动态包含语句 2.有类似于文件读取的url 漏洞描述 攻击者可以利用任意文件包含漏洞&#xff0c;读取任意文件&#xff0c;对服务器造成危害。 程序开发人员为了代码的灵活性&#xff0c;常常会将包含文件的路径…

SpringBoot怎么单独关闭某个类打印出来的日志?

application.yml文件增加以下内容&#xff1a; logging:level:org.springframework.amgp.rabbit: OFF 配置logging:level是配置的什么&#xff1f; 在application.yml文件中配置logging.level是用来设置日志级别的。这是Spring Boot应用中的一个常用配置&#xff0c;它允许您…

JeecgFlow错误事件

事件定义 错误事件可以用做一个流程的开始事件或者作为一个任务或者子流程的边界事件&#xff0c;错误事件没有提供作用中间事件的功能&#xff0c;这一点和前面介绍的定时器事件和消息事件还有区别的。在错误事件中提供了错误结束事件。 BPMN错误和Java异常并没有直接关联。BP…

tiktok数据分析应用介绍和tiktok数据分析平台分享

对于创作者、商家&#xff0c;tiktok官方有提供相应的数据分析为精细化运营给予辅助支持。 tiktok官方数据分析功能 TikTok Pro Account&#xff08;专业账户&#xff09;&#xff0c;包括CA账户&#xff08;Creator Account&#xff09;和BA&#xff08;Business Account&am…

ONLYOFFICE8.1版本桌面编辑器简单测评

ONLYOFFICE官网链接&#xff1a;在线PDF查看器和转换器 | ONLYOFFICE ONLYOFFICE介绍&#xff1a;https://www.onlyoffice.com/zh/office-suite.aspx OnlyOffice 是一款免费且开源的 Office 协作办公套件&#xff0c;支持桌面端和移动端等多平台&#xff0c;由一家领先的 IT 公…

Python深度理解系列之【排序算法——冒泡排序】

读者大大们好呀&#xff01;&#xff01;!☀️☀️☀️ &#x1f440;期待大大的关注哦❗️❗️❗️ &#x1f680;欢迎收看我的主页文章➡️木道寻的主页 文章目录 &#x1f525;前言&#x1f680;冒泡排序python实现算法实现图形化算法展示 ⭐️⭐️⭐️总结 &#x1f525;前…

师傅们 ~ 2024HW一手资料

各位师傅们&#xff0c;2024HW来了&#xff01; 从2026年开始&#xff0c;随着我国对网络安全的重视&#xff0c;涉及单位不断增加&#xff0c;越来越多单位和个人都加入到HW当中。 2024HW就在眼前&#xff0c; 那么还有不了解或者还没投简历面试的朋友们&#xff0c;需要注意…

职升网:中级会计师考试难度是怎样的?

中级会计师考试确实被普遍认为是具有一定难度的考试。以下是我对其难度的分析&#xff1a; 一、知识体系的广泛性 中级会计师考试覆盖的内容十分广泛&#xff0c;包括但不限于财务管理、财务会计、成本会计、税法等。这就要求考生具备扎实的基础知识和广泛的知识面&#xff0…

咨询公司在推行TPM管理中有哪些不可替代的作用?

TPM管理作为一种先进的生产维护理念&#xff0c;正逐渐成为企业追求卓越生产性能的不二之选。在这场转型升级的浪潮中&#xff0c;咨询公司扮演着不可替代的角色&#xff0c;它们如何助力企业成功推行TPM管理&#xff0c;成为了我们今天要探讨的焦点。 一、专业引领&#xff0c…

在Ubuntu 22.04 LTS 上安装 MySQL两种方式:在线方式和离线方式

Ubuntu安装MySQL 介绍&#xff1a; Ubuntu 是一款基于Linux操作系统的免费开源发行版&#xff0c;广受欢迎。它以稳定性、安全性和用户友好性而闻名&#xff0c;适用于桌面和服务器环境。Ubuntu提供了大量的软件包和应用程序&#xff0c;拥有庞大的社区支持和活跃的开发者社区…

五、【源码】资源加载器

源码地址&#xff1a;https://github.com/spring-projects/spring-framework 仓库地址&#xff1a;https://gitcode.net/qq_42665745/spring/-/tree/05-resource-loader 资源加载器 流程&#xff1a; 1.初始化BeanFactory 2.创建XmlBeanDefinitionReader用于从 XML 文件中读…

LoadRunner初学篇

我也是初学&#xff0c;写一篇文章记录下过程及心得&#xff0c;有不同建议的大佬可评价&#xff0c;感谢提携 这是什么 LoadRunner&#xff0c;是一种预测系统行为和性能的负载测试工具。通过模拟上千万用户实施并发负载及实时性能监测的方式来确认和查找问题&#xff0c;Loa…

焦化厂甲烷气体监测:甲烷传感器如何选择?

在焦化厂的日常运营中&#xff0c;煤的高温干馏和石油的渣油焦炭化是两大关键工艺。这些过程中&#xff0c;不仅产生了众多我们日常生活所需的化学产品&#xff0c;如糖精、焦油、沥青等&#xff0c;还伴随着大量焦炉煤气的生成。然而&#xff0c;这些煤气中含有的高浓度甲烷等…

坑——python的redis库的decode_responses设置

python的redis库查询返回的值默认是返回字节串&#xff0c;可以在redis.Redis()方法中通过设置decode_responses参数&#xff0c;让返回值直接是字符串&#xff1b; 查询返回字节串是因为Redis()方法中decode_responses默认值是False&#xff1a; 设置decode_responses为True就…

解决“Undefined control sequence. \hline”

解决“Undefined control sequence. \hline” Q:创建表格时显示错误“Undefined control sequence. \Xhline”A:解决方法C介绍\usepackage{makecell}作用使用方法示例其他功能总结 Q:创建表格时显示错误“Undefined control sequence. \Xhline” MTMAGVDPP.tex: 错误: 211: Un…

开源TTS模型支持中日韩并可以微调自己的声音模型;微软开源的知识图谱RAG;RAG和LLMs构建的搜索应用程序

✨ 1: Fish Speech Fish Speech 开源TTS模型支持中日韩&#xff0c;语音合成不止于自然 Fish Speech 是一个开源的语音生成项目&#xff0c;致力于开发和改进语音合成技术。项目最新稳定版本为1.1.2&#xff0c;并正在向1.2版本更新中。 Fish Speech 虽然仅为亿级参数的模型&…

huggingface datasets 数据集下载

pip install datasets但是国内下载一般由于网络下载失败&#xff1a;ConnectionError: Couldn’t reach ‘reach-vb/pokemon-blip-captions’ on the Hub (ConnectionError) 解决办法&#xff08;先vp*下载&#xff09;&#xff1a; 下载使用 from datasets import Dataset, …

Cube-Studio:开源大模型全链路一站式中台

开源项目&#xff0c;欢迎star哦&#xff0c;https://github.com/data-infra/cube-studio 一款真正意义的 LLMOps 框架 LLMOps&#xff08;Large Language Model Operations&#xff09;是一个涵盖了大型语言模型&#xff08;如GPT系列&#xff09;开发、部署、维护和优化的一…

海外报纸媒体投放形式分为哪些?传播当中有什么优势-大舍传媒

国外报纸媒体投放新闻稿作为一种传统而有效的传播方式&#xff0c;依然在现代媒体环境中保持着其独特的价值和权威性。以下几点阐述了报纸媒体宣发的几个关键方面&#xff0c;特别是当通过专业机构如大舍传媒进行操作时&#xff1a; 权威性和公信力&#xff1a;报纸作为历史悠久…