SDL播放音频PCM

躬行之

共 8960字,需浏览 18分钟

 ·

2024-03-25 12:00

0f2c438694136e1cdcba7180ac22edf8.webp

PS:从思维中解放出来的开始就是认识到你不是一个思考问题的实体——思考者

SDL 是一个很好用跨平台多媒体开发库,可以方便的使用 SDL 来学习音视频,本文将介绍如何使用 SDL 播放 PCM 数据,主要内容如下:

  1. SDL介绍

  2. SDL_AudioSpec

  3. SDL_AudioCallback

  4. FFmpeg提取PCM数据

  5. 播放本地PCM文件

SDL介绍

SDL(Simple DirectMedia Layer)是一套开放源代码的跨平台多媒体开发库,使用 C 语言编写,SDL 提供了数种控制图像、声音、输出入的函数,让开发者只要用相同或是相似的代码就可以开发出跨多个平台(Linux、Windows、Mac OS X等)的应用软件,目前 SDL 多用于开发游戏、模拟器、媒体播放器等多媒体应用领域,其架构图如下:

dd357cab78e2e5c9fed0b36e501167ef.webp

后续主要是借助 SDL 来测试音频、视频的播放功能,后面中提到的 SDL 都是用的 SDL2 版本。

SDL_AudioSpec

SDL_AudioSpec 是 SDL 中表示音频输出格式的一个结构体,同时也包含当前音频设备需要更多数据时调用的回调函数,其定义如下:

      

1/**
2 *  The calculated values in this structure are calculated by SDL_OpenAudio().
3 *  For multi-channel audio, the default SDL channel mapping is:
4 *  2:  FL FR                       (stereo)
5 *  3:  FL FR LFE                   (2.1 surround)
6 *  4:  FL FR BL BR                 (quad)
7 *  5:  FL FR LFE BL BR             (4.1 surround)
8 *  6:  FL FR FC LFE SL SR          (5.1 surround - last two can also be BL BR)
9 *  7:  FL FR FC LFE BC SL SR       (6.1 surround)
10 *  8:  FL FR FC LFE BL BR SL SR    (7.1 surround)
11 */

12typedef struct SDL_AudioSpec{
13    int freq;                   /**< DSP frequency -- samples per second */
14    SDL_AudioFormat format;     /**< Audio data format */              
15    Uint8 channels;             /**< Number of channels: 1 mono, 2 stereo */ 
16    Uint8 silence;              /**< Audio buffer silence value (calculated) */
17    Uint16 samples;             /**< Audio buffer size in sample FRAMES (total samples divided by channel count) */
18    Uint16 padding;             /**< Necessary for some compile environments */
19    Uint32 size;                /**< Audio buffer size in bytes (calculated) */
20    SDL_AudioCallback callback; /**< Callback that feeds the audio device (NULL to use SDL_QueueAudio()). */
21    void *userdata;             /**< Userdata passed to callback (ignored for NULL callbacks). */
22} SDL_AudioSpec;

SDL_AudioSpec具体信息如下:

  • freq:音频采样率,即每秒采样数,单位 Hz,采样率越高,音频质量越好,但占用的带宽和存储空间也越大。

  • format:音频数据格式。

  • channels:音频通道数,常见的如 1 表示单声道,2 表示立体声。

  • silence:表示静音音量值,常为 0。

  • samples:采样帧的音频缓冲区大小,以采样数量为单位,可以看[[关于采样的理解]]。

  • padding:内部填充字段,不需要使用,一般用于兼容。

  • size:音频缓冲区总大小,以字节数为单位。用于表示缓冲区能够容纳的音频数据的最大量,这个字段的值通常等于 samples * channels * format / 8。

  • callback:填充音频缓冲区的回调函数。

SDL_AudioCallback

SDL_AudioCallbackSDL_AudioSpec结构体中的回调函数,在音频设备需要数据的时候回调该函数,定义如下:

      

1typedef void (SDLCALL * SDL_AudioCallback) (void *userdata, Uint8 * stream,int len);

SDL_AudioCallback 回调函数参数如下:

  • userdata:用户自定义数据。

  • stream:该指针指向需要填充的音频缓冲区,回调函数需要通过填充这个缓冲区中的数据来提供音频信息。

    • 对于播放音频而言,缓冲区中的数据需要被播放出来。

    • 对于录制音频而言,数据则需要被写入到音频文件中。

  • len:音频缓冲区大小,以字节为单位,回调函数需要在这个缓冲区中填充 len 个字节的音频数据。

SDL_AudioCallback 回调函数的简单实现见下文案例。

FFmpeg提取PCM数据

先使用 FFmpeg 提取用来测试的 PCM 数据,通过如下命令从 mp4 文件中提取 20s 的 pcm 音频数据,如下:

      

1ffmpeg -i xxx.mp4 -t 20 -codec:a pcm_s16le -ar 44100 -ac 2 -f s16le 44100_16bit_2ch.pcm

提取完毕后使用 ffplay 验证是否可以播放:

      

1ffplay -ar 44100 -ac 2 -f s16le 44100_16bit_2ch.pcm

确认播放正常后可以使用 SDL 播放 PCM 音频了。

播放本地PCM文件

下面使用 SDL 来读取本地 PCM 文件来播放,主要步骤如下:

  1. 打开PCM文件:

      

1FILE *pcmFile = fopen(path, "rb");

  1. 初始化SDL为音频场景:

      

1SDL_Init(SDL_INIT_AUDIO);

  1. 填充SDL_AudioSpec音频参数:

      

1SDL_AudioSpec audioSpec;
2audioSpec.freq = 44100;  
3audioSpec.format = AUDIO_S16SYS;  
4audioSpec.channels = 2;  
5audioSpec.samples = 1024;   // 1024/44100 = 0.0232199546485261 约等于23.2ms,也就是23.2ms至少回调一次  
6audioSpec.silence = 0;  
7audioSpec.callback = sdl_fill_pcm;  
8audioSpec.userdata = NULL;

这里的callback就是上文中的SDL_AudioCallback

  1. 使用初始化好的SDL_AudioSpec打开音频设备:

      

1SDL_OpenAudio(&audioSpec, NULL);

  1. 播放音频:

      

1SDL_PauseAudio(0);

  1. 循环从本地读取 PCM 数据:

      

1// 数据读取,记录读取的总数据
2uint64_t dataCount = 0;
3while (1) {
4    // 从文件中读取PCM数据
5    read_buf_len = fread(audio_buf, 1, PCM_BUFFER_SIZE, pcmFile);
6    if (read_buf_len == 0) {
7        break;
8    }
9    // 统计读取的总字节数
10    dataCount += read_buf_len;
11    // 更新数据读取结束位置
12    audio_end = audio_buf + read_buf_len;
13    // 更新数据读取开始位置
14    audio_pos = audio_buf;
15
16    while (audio_pos < audio_end) {
17        // 等待PCM数据消耗
18        SDL_Delay(10);
19    }
20}

  1. 实现 SDL_AudioCallback回调函数如下:

      

1void sdl_fill_pcm(void *userdata, Uint8 *stream, int len) {
2    SDL_memset(stream, 0, len);
3    if (audio_pos >= audio_end) {
4        return;
5    }
6    // 数据不够直接读完,否则只读取一定长度的数据
7    int remain_buf_len = audio_end - audio_pos;
8    len = (remain_buf_len > len) ? len : remain_buf_len;
9    // 拷贝数据到stream并设置音量
10    SDL_MixAudio(stream, audio_pos, len, SDL_MIX_MAXVOLUME / 3);
11    audio_pos += len;
12}

  1. 释放资源:

      

1SDL_CloseAudio();
2if (audio_buf) {  
3    free(audio_buf);  
4}  
5if (pcmFile) {  
6    fclose(pcmFile);  
7}  
8SDL_Quit();

最后附上 Clion 工程的 CMakeLists.txt 文件,其内容如下:

      

1cmake_minimum_required(VERSION 3.26)
2project(sdf_pcm C)
3set(CMAKE_C_STANDARD 11)
4include_directories(E:/msys64/mingw64/include/SDL2)
5link_directories(E:/msys64/mingw64/lib)
6add_executable(sdl_pcm main.c)
7target_link_libraries(sdl_pcm mingw32 SDL2 SDL2main)

到此,SDL播放音频 PCM 基本流程结束。

最后推荐下秦子帅老哥最近撰写的公众号快速涨粉指南,有需要的童鞋可以看一下:

推荐阅读:


浏览 19
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报