SDL基础使用06 (SDL播放pcm文件)

简介: 如何使用SDL库在C和C++中播放PCM音频文件,包括初始化SDL音频、设置音频参数、读取PCM数据、播放音频以及资源释放的完整流程。

SDL播放PCM文件

C语言文件打开方式

// 提取PCM文件
// ffmpeg -i input.mp4 -t 20 -codec:a pcm_s16le -ar 44100 -ac 2 -f s16le suiyue_44100_2_s16le.pcm
// 测试PCM文件
// ffplay -ar 44100 -ac 2 -f s16le suiyue_44100_2_s16le.pcm
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
extern "C"
{
#include <SDL.h>
}

// 每次读取2帧数据, 以1024个采样点一帧 2通道 16bit采样点为例
#define PCM_BUFFER_SIZE (2 * 1024 * 2 * 2)

static Uint8 *s_audio_buf = NULL;   // 存储当前读取的两帧数据
static Uint8 *s_audio_pos = NULL;   // 目前读取的位置
static Uint8 *s_audio_end = NULL;   // 缓存结束位置

//音频设备回调函数
void fill_audio_pcm(void *udata, Uint8 *stream, int len)
{
    // udata: 用户自定义数据
    SDL_memset(stream, 0, len);
    if (s_audio_pos >= s_audio_end)            // (当前两帧)数据读取完毕
    {
        return;
    }

    // 数据够了就读预设长度,数据不够就只读部分(不够的时候剩多少就读取多少)
    int remain_buffer_len = s_audio_end - s_audio_pos;
    len = (len < remain_buffer_len) ? len : remain_buffer_len;
    // 设置混音
    SDL_MixAudio(stream, s_audio_pos, len, SDL_MIX_MAXVOLUME / 8);  // 音量的值 SDL_MIX_MAXVOLUME 128
    printf("use %d bytes\n", len);                                    // 4096 bytes  samples * channels * format

    s_audio_pos += len;  // 移动缓存指针(当前pos)
}

#undef main
int main()
{
    int ret = -1;
    FILE *audio_fd = NULL;
    SDL_AudioSpec spec;                         // SDL音频设备
    const char *path = "./suiyue_44100_2_s16le.pcm";

    size_t read_buffer_len = 0;                    // 存储每次读取文件数据的长度

    // 1. SDL初始化AUDIO
    if (SDL_Init(SDL_INIT_AUDIO))    
    {
        fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());
        return ret;
    }

    // 2. 打开PCM文件(以二进制方式打开)
    audio_fd = fopen(path, "rb");
    if (!audio_fd)
    {
        fprintf(stderr, "Failed to open pcm file!\n");
        goto _FAIL;
    }

    // 申请一段内存用于缓存读取的两帧(自定义的长度)音频数据
    s_audio_buf = (uint8_t *)malloc(PCM_BUFFER_SIZE);

    // 音频参数设置SDL_AudioSpec
    spec.freq = 44100;              // 采样频率 44.1k
    spec.format = AUDIO_S16SYS;     // 采样点格式 (交错模式16bit)
    spec.channels = 2;              // 2通道
    spec.silence = 0;               // 设置静音的值
    // samples * channels * format = 4096 bytes  (回调每次读取的数据)
    // 每次读取的采样数量,多久调用一次回调和samples有关系(回调间隔 1024 / 44100)23.2ms
    spec.samples = 1024;
    spec.callback = fill_audio_pcm; // 设置回调函数
    spec.userdata = NULL;

    // 3. 打开音频设备
    if (SDL_OpenAudio(&spec, NULL))
    {
        fprintf(stderr, "Failed to open audio device, %s\n", SDL_GetError());
        goto _FAIL;
    }

    // 4. 设置为0时开始播放,为1时播放静音数据
    SDL_PauseAudio(0);

    int data_count = 0;    // 存储读取的总字节数
    while (1)
    {
        // 从文件读取PCM数据到s_audio_buf中(每次读取两帧数据)
        read_buffer_len = fread(s_audio_buf, 1, PCM_BUFFER_SIZE, audio_fd);
        if (read_buffer_len == 0)
        {
            break;
        }
        data_count += read_buffer_len;                             // 统计读取的数据总字节数
        printf("read %10d bytes data\n", data_count);             // 每次加2帧数据 8192 bytes
        s_audio_end = s_audio_buf + read_buffer_len;             // 更新buffer的结束位置(buf位置 + 本次读取的长度)
        s_audio_pos = s_audio_buf;                                 // 更新buffer的起始位置

        while (s_audio_pos < s_audio_end)                // 等待回调函数将数据读取完毕
        {
            // 不能大于上面计算的23.2ms 否则会产生断音
            SDL_Delay(10);  // 等待PCM数据消耗
        }
    }
    printf("play PCM finish\n");

    // 关闭音频设备
    SDL_CloseAudio();

_FAIL:
    // 释放资源退出
    if (s_audio_buf)
        free(s_audio_buf);

    if (audio_fd)
        fclose(audio_fd);

    SDL_Quit();

    return 0;
}

C++文件打开方式

    1. 初始化SDL SDL_Init
    1. 创建音频播放回调函数,打开音频设备 SDL_OpenAudio
    1. 开始播放 SDL_PauseAudio
    1. 打开文件循环读取数据到缓冲
    1. 关闭音频设备 SDL_CloseAudio
    1. 释放资源,退出 SDL_Quit
#include <iostream>
#include <fstream>
#include <cmath>

// SDL 播放PCM数据
extern "C"
{
#include <SDL.h>
}
#pragma comment(lib, "SDL2.lib")


Uint8* g_audio_chunk;                // 下面读取两帧数据的缓存
Uint32 g_audio_len;                    // 读取的长度
Uint8* g_audio_pos;                    // 当前声卡消耗到的位置

// 接收音频数据的回调函数
void read_audio_data_cb(void* udata, Uint8* stream, int len)
{
    SDL_memset(stream, 0x00, len);
    if (g_audio_len == 0)
        return;
    len = SDL_min(len, static_cast<int>(g_audio_len));

    // 将音频数据 g_audio_pos 传给 stream
    std::cout << "use " << len << " bytes" << std::endl;
    SDL_MixAudio(stream, g_audio_pos, len, SDL_MIX_MAXVOLUME);    // (设置混音(也可直接拷贝))将声音喂给声卡(最后一个参数音量0-128)
    g_audio_pos += len;
    g_audio_len -= len;
}

#undef main
int main()
{
    // 1. 初始化SDL为audio
    int nRet = SDL_Init(SDL_INIT_AUDIO);
    if (nRet < 0)
    {
        std::cout << "SDL Error: " << SDL_GetError() << std::endl;
        return -1;
    }

    // 定义结构
    SDL_AudioSpec spec;
    spec.freq = 44100;
    spec.channels = 2;
    spec.format = AUDIO_S16SYS;                // s16le
    spec.samples = 1024;                    // 字节数(与回调间隔有关) 1024 / 44100 约 23.2ms (设置延时超过这个数会出现断音现象)
    spec.callback = read_audio_data_cb;        // 回调函数(回调函数缓冲len的长度为 samples * format * channels)
    spec.userdata = NULL;
    // 2. 根据参数打开音频设备
    if (SDL_OpenAudio(&spec, NULL) < 0)
    {
        return -1;
    }

    // 3. 打开文件(需要使用二进制方式打开)
    std::ifstream pcmFile("./suiyue_44100_2_s16le.pcm", std::ios::in | std::ios::binary);
    if (!pcmFile.is_open())
    {
        return -1;
    }

    // 4. 开始播放
    SDL_PauseAudio(0);            // 0 开始播放 1 播放静音数据

    char* buffer = (char*)malloc(2 * 1024 * 2 * 2);        // 每次读两帧数据,以1024个采样点 双声道 位深16 为一帧
    while (!pcmFile.eof())
    {
        // 将两帧音频数据读取到buffer中(8192 bytes)
        pcmFile.read(buffer, 1024 * 2 * 2 * 2);
        if (pcmFile.bad())
        {
            return -1;
        }

        g_audio_chunk = reinterpret_cast<Uint8*>(buffer);    // 真实读取的音频数据
        g_audio_len = pcmFile.gcount();                        // 真实读取的字节数
        g_audio_pos = g_audio_chunk;                        // 将音频位置设置到刚读取到的buffer位置

        std::cout << "read " << g_audio_len << " bytes" << std::endl;
        // 延时等待声卡消耗音频数据
        while (g_audio_len > 0)
        {
            SDL_Delay(10);
        }
    }

    // 5. 关闭音频设备
    SDL_CloseAudio();

    // 6. 释放资源、退出
    free(buffer);
    buffer = NULL;
    SDL_Quit();

    return 0;
}
相关文章
|
存储 SQL 缓存
StarRocks常见面试问题(一)
StarRocks常见面试问题(一)
|
监控 关系型数据库 分布式数据库
【PolarDB开源】PolarDB故障恢复机制:快速恢复与数据一致性保障
【5月更文挑战第22天】阿里云PolarDB的故障恢复机制保证了云数据库的高可用性和一致性。通过ROW快照备份和增量日志,实现秒级备份和恢复,确保数据安全。日志分析快速定位故障,启用备用实例实现快速恢复。分布式事务和强一致性读等技术保障数据一致性。这套全面的解决方案使PolarDB在云原生数据库中表现出色。
840 10
|
机器学习/深度学习 人工智能 API
如何在 TensorRT-LLM 中支持 Qwen 模型
大型语言模型正以其惊人的新能力推动人工智能的发展,扩大其应用范围。然而,由于这类模型具有庞大的参数规模,部署和推理的难度和成本极高,这一挑战一直困扰着 AI 领域。此外,当前存在大量支持模型部署和推理的框架和工具,如  ModelScope 的 Model Pipelines API,和 HuggingFace 的 Text Generation Inference 等,各自都有其独特的特点和优势。然而,这些工具往往未能充分发挥  GPU 的性能。
72444 0
如何在 TensorRT-LLM 中支持 Qwen 模型
|
存储 安全 算法
MiniOS 3.3.4 发布,新功能有这些!
【10月更文挑战第19天】
567 0
MiniOS 3.3.4 发布,新功能有这些!
|
Linux 开发工具
Linux查看已经安装软件的版本,安装软件的路径,以及dpkg、aptitude、apt-get、apt工具的使用
Linux查看已经安装软件的版本,安装软件的路径,以及dpkg、aptitude、apt-get、apt工具的使用
952 2
Linux查看已经安装软件的版本,安装软件的路径,以及dpkg、aptitude、apt-get、apt工具的使用
|
机器学习/深度学习 PyTorch 数据处理
数据增强与 DataLoader:提升模型泛化能力的策略
【8月更文第29天】在深度学习中,数据的质量和数量对于模型的性能至关重要。数据增强是一种常用的技术,它通过对原始数据进行变换(如旋转、缩放、裁剪等)来生成额外的训练样本,从而增加训练集的多样性和规模。这有助于提高模型的泛化能力,减少过拟合的风险。同时,`DataLoader` 是 PyTorch 中一个强大的工具,可以有效地加载和预处理数据,并支持并行读取数据,这对于加速训练过程非常有帮助。
1212 1
|
小程序 前端开发 索引
微信小程序中的条件渲染和列表渲染,wx:if ,wx:elif,wx:else,wx:for,wx:key的使用,以及block标记和hidden属性的说明
这篇文章介绍了微信小程序中条件渲染和列表渲染的使用方法,包括wx:if、wx:elif、wx:else、wx:for、wx:key以及block标记和hidden属性的使用。
微信小程序中的条件渲染和列表渲染,wx:if ,wx:elif,wx:else,wx:for,wx:key的使用,以及block标记和hidden属性的说明
|
Android开发
38. 【Android教程】Handler 消息传递机制
38. 【Android教程】Handler 消息传递机制
374 2

热门文章

最新文章