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;
}
相关文章
|
11月前
|
存储 Cloud Native Linux
音视频 SDL vs2017配置
音视频 SDL vs2017配置
|
3天前
|
Windows
SDL基础使用07(YUV数据显示)
使用SDL库在Windows上处理和显示YUV数据,包括生成随机YUV数据、播放YUV文件以及实现带缩放的实时渲染。
13 1
|
3天前
SDL基础使用04(SDL_image与SDL_mixer扩展库)
本文介绍了如何使用SDL_image和SDL_mixer扩展库在SDL项目中加载和显示图片以及播放音频文件。
16 1
|
5月前
|
内存技术
SDL播放PCM
SDL播放PCM
36 0
实战--ffmpeg与sdl实现最简单的视频播放器
实战--ffmpeg与sdl实现最简单的视频播放器
95 0
|
存储 缓存 内存技术
SDL开发笔记(二):音频基础介绍、使用SDL播放音频
SDL开发笔记(二):音频基础介绍、使用SDL播放音频
SDL开发笔记(二):音频基础介绍、使用SDL播放音频
|
缓存 编解码 API
FFmpeg开发笔记(八):ffmpeg解码音频并使用SDL同步音频播放
FFmpeg开发笔记(八):ffmpeg解码音频并使用SDL同步音频播放
FFmpeg开发笔记(八):ffmpeg解码音频并使用SDL同步音频播放
|
编解码 API
FFmpeg开发笔记(六):ffmpeg解码视频并使用SDL同步时间显示播放
FFmpeg开发笔记(六):ffmpeg解码视频并使用SDL同步时间显示播放
FFmpeg开发笔记(六):ffmpeg解码视频并使用SDL同步时间显示播放
|
存储 编解码 缓存
FFmpeg 开发(03):FFmpeg + OpenSLES 实现音频解码播放
本文将利用 FFmpeg 对一个 Mp4 文件的音频流进行解码,然后使用 libswresample 将解码后的 PCM 音频数据转换为目标格式的数据,最后利用 OpenSLES 进行播放。
527 0
FFmpeg 开发(03):FFmpeg + OpenSLES 实现音频解码播放
|
消息中间件 vr&ar 数据格式
【音视频连载-007】基础学习篇-SDL 播放 PCM 音频文件(上)
在前面的文章中已经能够利用 SDL 去播放 YUV 视频文件了,接下来要通过 SDL 去播放 PCM 音频文件。
566 0
【音视频连载-007】基础学习篇-SDL 播放 PCM 音频文件(上)