本文记录SDL播放音频的技术。在这里使用的版本是SDL2。实际上SDL本身并不提供视音频播放的功能,它只是封装了视音频播放的底层API。在Windows平台下,SDL封装了Direct3D这类的API用于播放视频;封装了DirectSound这类的API用于播放音频。因为SDL的编写目的就是简化视音频播放的开发难度,所以使用SDL播放视频(YUV/RGB)和音频(PCM)数据非常的容易。

SDL简介

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

 

SDL实际上并不限于视音频的播放,它将功能分成下列数个子系统(subsystem):

Video(图像):图像控制以及线程(thread)和事件管理(event)。

Audio(声音):声音控制

Joystick(摇杆):游戏摇杆控制

CD-ROM(光盘驱动器):光盘媒体控制

Window Management(视窗管理):与视窗程序设计集成

Event(事件驱动):处理事件驱动

在Windows下,SDL与DirectX的对应关系如下。

SDL

DirectX

SDL_Video、SDL_Image

DirectDraw、Direct3D

SDL_Audio、SDL_Mixer

DirectSound

SDL_Joystick、SDL_Base

DirectInput

SDL_Net

DirectPlay

注:上文内容在《使用SDL播放视频》的文章中已经介绍,这里再次重复贴一遍。

SDL播放音频的流程

SDL播放音频的流程狠简单,分为以下步骤。

1. 初始化

1) 初始化SDL。

2) 根据参数(SDL_AudioSpec)打开音频设备

2. 循环播放数据

1) 播放音频数据。

2) 延时等待播放完成。

下面详细分析一下上文流程。

1. 初始化

1) 初始化SDL。

使用SDL_Init()初始化SDL。该函数可以确定希望激活的子系统。SDL_Init()函数原型如下:

  1. int SDLCALL SDL_Init(Uint32 flags)

其中,flags可以取下列值:

SDL_INIT_TIMER:定时器
SDL_INIT_AUDIO:音频
SDL_INIT_VIDEO:视频
SDL_INIT_JOYSTICK:摇杆
SDL_INIT_HAPTIC:触摸屏
SDL_INIT_GAMECONTROLLER:游戏控制器
SDL_INIT_EVENTS:事件
SDL_INIT_NOPARACHUTE:不捕获关键信号(这个不理解)
SDL_INIT_EVERYTHING:包含上述所有选项

有关SDL_Init()有一点需要注意:初始化的时候尽量做到“够用就好”,而不要用SDL_INIT_EVERYTHING。因为有些情况下使用SDL_INIT_EVERYTHING会出现一些不可预知的问题。例如,在MFC应用程序中播放纯音频,如果初始化SDL的时候使用SDL_INIT_EVERYTHING,那么就会出现听不到声音的情况。后来发现,去掉了SDL_INIT_VIDEO之后,问题才得以解决。

2) 根据参数(SDL_AudioSpec)打开音频设备
使用SDL_OpenAudio()打开音频设备。该函数需要传入一个SDL_AudioSpec的结构体。DL_OpenAudio()的原型如下。

  1. int SDLCALL SDL_OpenAudio(SDL_AudioSpec * desired,
  2. SDL_AudioSpec * obtained);

它的参数是两个SDL_AudioSpec结构体,它们的含义:
desired:期望的参数。
obtained:实际音频设备的参数,一般情况下设置为NULL即可。

SDL_AudioSpec结构体的定义如下。

  1. typedef struct SDL_AudioSpec
  2. {
  3. int freq;                   /**< DSP frequency -- samples per second */
  4. SDL_AudioFormat format;     /**< Audio data format */
  5. Uint8 channels;             /**< Number of channels: 1 mono, 2 stereo */
  6. Uint8 silence;              /**< Audio buffer silence value (calculated) */
  7. Uint16 samples;             /**< Audio buffer size in samples (power of 2) */
  8. Uint16 padding;             /**< Necessary for some compile environments */
  9. Uint32 size;                /**< Audio buffer size in bytes (calculated) */
  10. SDL_AudioCallback callback;
  11. void *userdata;
  12. } SDL_AudioSpec;

其中包含了关于音频各种参数:
freq:音频数据的采样率。常用的有48000,44100等。
format:音频数据的格式。举例几种格式:
AUDIO_U16SYS:Unsigned 16-bit samples
AUDIO_S16SYS:Signed 16-bit samples
AUDIO_S32SYS:32-bit integer samples
AUDIO_F32SYS:32-bit floating point samples
channels:声道数。例如单声道取值为1,立体声取值为2。
silence:设置静音的值。
samples:音频缓冲区中的采样个数,要求必须是2的n次方。
padding:考虑到兼容性的一个参数。
size:音频缓冲区的大小,以字节为单位。
callback:填充音频缓冲区的回调函数。
userdata:用户自定义的数据。
在这里记录一下填充音频缓冲区的回调函数的作用。当音频设备需要更多数据的时候会调用该回调函数。回调函数的格式要求如下。

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

回调函数的参数含义如下所示。
userdata:SDL_AudioSpec结构中的用户自定义数据,一般情况下可以不用。
stream:该指针指向需要填充的音频缓冲区。
len:音频缓冲区的大小(以字节为单位)。
在回调函数中可以使用SDL_MixAudio()完成混音等工作。众所周知SDL2和SDL1.x关于视频方面的API差别很大。但是SDL2和SDL1.x关于音频方面的API是一模一样的。唯独在回调函数中,SDL2有一个地方和SDL1.x不一样:SDL2中必须首先使用SDL_memset()将stream中的数据设置为0。

2. 循环播放数据
1) 播放音频数据。

使用SDL_PauseAudio()可以播放音频数据。SDL_PauseAudio()的原型如下。

  1. void SDLCALL SDL_PauseAudio(int pause_on)

当pause_on设置为0的时候即可开始播放音频数据。设置为1的时候,将会播放静音的值。

2) 延时等待播放完成。
这一步就是延时等待音频播放完毕了。使用像SDL_Delay()这样的延时函数即可。

代码

源代码如下所示。
  1. /**
  2. * 最简单的SDL2播放音频的例子(SDL2播放PCM)
  3. * Simplest Audio Play SDL2 (SDL2 play PCM)
  4. *
  5. * 雷霄骅 Lei Xiaohua
  6. * leixiaohua1020@126.com
  7. * 中国传媒大学/数字电视技术
  8. * Communication University of China / Digital TV Technology
  9. * http://blog.csdn.net/leixiaohua1020
  10. *
  11. * 本程序使用SDL2播放PCM音频采样数据。SDL实际上是对底层绘图
  12. * API(Direct3D,OpenGL)的封装,使用起来明显简单于直接调用底层
  13. * API。
  14. *
  15. * 函数调用步骤如下:
  16. *
  17. * [初始化]
  18. * SDL_Init(): 初始化SDL。
  19. * SDL_OpenAudio(): 根据参数(存储于SDL_AudioSpec)打开音频设备。
  20. *
  21. * [循环播放数据]
  22. * SDL_PauseAudio(): 播放音频数据。
  23. * SDL_Delay(): 延时等待播放完成。
  24. *
  25. * This software plays PCM raw audio data using SDL2.
  26. * SDL is a wrapper of low-level API (DirectSound).
  27. * Use SDL is much easier than directly call these low-level API.
  28. *
  29. * The process is shown as follows:
  30. *
  31. * [Init]
  32. * SDL_Init(): Init SDL.
  33. * SDL_OpenAudio(): Opens the audio device with the desired
  34. *                  parameters (In SDL_AudioSpec).
  35. *
  36. * [Loop to play data]
  37. * SDL_PauseAudio(): Play Audio.
  38. * SDL_Delay(): Wait for completetion of playback.
  39. */
  40. #include <stdio.h>
  41. #include <tchar.h>
  42. extern "C"
  43. {
  44. #include "sdl/SDL.h"
  45. };
  46. //Buffer:
  47. //|-----------|-------------|
  48. //chunk-------pos---len-----|
  49. static  Uint8  *audio_chunk;
  50. static  Uint32  audio_len;
  51. static  Uint8  *audio_pos;
  52. /* Audio Callback
  53. * The audio function callback takes the following parameters:
  54. * stream: A pointer to the audio buffer to be filled
  55. * len: The length (in bytes) of the audio buffer
  56. *
  57. */
  58. void  fill_audio(void *udata,Uint8 *stream,int len){
  59. //SDL 2.0
  60. SDL_memset(stream, 0, len);
  61. if(audio_len==0)        /*  Only  play  if  we  have  data  left  */
  62. return;
  63. len=(len>audio_len?audio_len:len);   /*  Mix  as  much  data  as  possible  */
  64. SDL_MixAudio(stream,audio_pos,len,SDL_MIX_MAXVOLUME);
  65. audio_pos += len;
  66. audio_len -= len;
  67. }
  68. int main(int argc, char* argv[])
  69. {
  70. //Init
  71. if(SDL_Init(SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
  72. printf( "Could not initialize SDL - %s\n", SDL_GetError());
  73. return -1;
  74. }
  75. //SDL_AudioSpec
  76. SDL_AudioSpec wanted_spec;
  77. wanted_spec.freq = 44100;
  78. wanted_spec.format = AUDIO_S16SYS;
  79. wanted_spec.channels = 2;
  80. wanted_spec.silence = 0;
  81. wanted_spec.samples = 1024;
  82. wanted_spec.callback = fill_audio;
  83. if (SDL_OpenAudio(&wanted_spec, NULL)<0){
  84. printf("can't open audio.\n");
  85. return -1;
  86. }
  87. FILE *fp=fopen("../NocturneNo2inEflat_44.1k_s16le.pcm","rb+");
  88. if(fp==NULL){
  89. printf("cannot open this file\n");
  90. return -1;
  91. }
  92. //For YUV420P
  93. int pcm_buffer_size=4096;
  94. char *pcm_buffer=(char *)malloc(pcm_buffer_size);
  95. int data_count=0;
  96. while(1){
  97. if (fread(pcm_buffer, 1, pcm_buffer_size, fp) != pcm_buffer_size){
  98. // Loop
  99. fseek(fp, 0, SEEK_SET);
  100. fread(pcm_buffer, 1, pcm_buffer_size, fp);
  101. data_count=0;
  102. }
  103. printf("Now Playing %10d Bytes data.\n",data_count);
  104. data_count+=pcm_buffer_size;
  105. //Set audio buffer (PCM data)
  106. audio_chunk = (Uint8 *) pcm_buffer;
  107. //Audio buffer length
  108. audio_len =pcm_buffer_size;
  109. audio_pos = audio_chunk;
  110. //Play
  111. SDL_PauseAudio(0);
  112. while(audio_len>0)//Wait until finish
  113. SDL_Delay(1);
  114. }
  115. return 0;
  116. }

运行结果

运行的结果如下图所示。运行的时候可以听见音乐播放的声音。

 

下载

代码位于“Simplest Media Play”中

SourceForge项目地址:https://sourceforge.net/projects/simplestmediaplay/

CSDN下载地址:http://download.csdn.net/detail/leixiaohua1020/8054395

上述工程包含了使用各种API(Direct3D,OpenGL,GDI,DirectSound,SDL2)播放多媒体例子。其中音频输入为PCM采样数据。输出至系统的声卡播放出来。视频输入为YUV/RGB像素数据。输出至显示器上的一个窗口播放出来。
通过本工程的代码初学者可以快速学习使用这几个API播放视频和音频的技术。
一共包括了如下几个子工程:
simplest_audio_play_directsound:  使用DirectSound播放PCM音频采样数据。
simplest_audio_play_sdl2:  使用SDL2播放PCM音频采样数据。
simplest_video_play_direct3d:  使用Direct3D的Surface播放RGB/YUV视频像素数据。
simplest_video_play_direct3d_texture:使用Direct3D的Texture播放RGB视频像素数据。
simplest_video_play_gdi:  使用GDI播放RGB/YUV视频像素数据。
simplest_video_play_opengl:  使用OpenGL播放RGB/YUV视频像素数据。
simplest_video_play_opengl_texture: 使用OpenGL的Texture播放YUV视频像素数据。
simplest_video_play_sdl2:  使用SDL2播放RGB/YUV视频像素数据。

from:http://blog.csdn.net/leixiaohua1020/article/details/40544521

最简单的视音频播放示例9:SDL2播放PCM的更多相关文章

  1. 最简单的视音频播放示例7:SDL2播放RGB/YUV

    本文记录SDL播放视频的技术.在这里使用的版本是SDL2.实际上SDL本身并不提供视音频播放的功能,它只是封装了视音频播放的底层API.在Windows平台下,SDL封装了Direct3D这类的API ...

  2. 最简单的视音频播放示例6:OpenGL播放YUV420P(通过Texture,使用Shader)

    本文记录OpenGL播放视频的技术.上一篇文章中,介绍了一种简单的使用OpenGL显示视频的方式.但是那还不是OpenGL显示视频技术的精髓.和Direct3D一样,OpenGL更好的显示视频的方式也 ...

  3. 最简单的视音频播放演示样例4:Direct3D播放RGB(通过Texture)

    ===================================================== 最简单的视音频播放演示样例系列文章列表: 最简单的视音频播放演示样例1:总述 最简单的视音频 ...

  4. 最简单的视音频播放演示样例5:OpenGL播放RGB/YUV

    ===================================================== 最简单的视音频播放演示样例系列文章列表: 最简单的视音频播放演示样例1:总述 最简单的视音频 ...

  5. 最简单的视音频播放演示样例8:DirectSound播放PCM

    ===================================================== 最简单的视音频播放演示样例系列文章列表: 最简单的视音频播放演示样例1:总述 最简单的视音频 ...

  6. 最简单的视音频播放演示样例3:Direct3D播放YUV,RGB(通过Surface)

    ===================================================== 最简单的视音频播放演示样例系列文章列表: 最简单的视音频播放演示样例1:总述 最简单的视音频 ...

  7. 最简单的视音频播放演示样例7:SDL2播放RGB/YUV

    ===================================================== 最简单的视音频播放演示样例系列文章列表: 最简单的视音频播放演示样例1:总述 最简单的视音频 ...

  8. 最简单的视音频播放示例5:OpenGL播放RGB/YUV

    本文记录OpenGL播放视频的技术.OpenGL是一个和Direct3D同一层面的技术.相比于Direct3D,OpenGL具有跨平台的优势.尽管在游戏领域,DirectX的影响力已渐渐超越OpenG ...

  9. 最简单的视音频播放示例8:DirectSound播放PCM

    本文记录DirectSound播放音频的技术.DirectSound是Windows下最常见的音频播放技术.目前大部分的音频播放应用都是通过DirectSound来播放的.本文记录一个使用Direct ...

随机推荐

  1. 【BZOJ】【2463】【中山市选2009】谁能赢呢?

    博弈论 这能算博弈论吗…… orz ZYF so sad……窝智商太低 题解搬运: 当n为偶数时,可以被2*1的骨牌完全覆盖,所以每次都走骨牌的另一端,而另一个人只能走新的骨牌,直到没有为止 当n为奇 ...

  2. Matlab 高斯分布 均匀分布 以及其他分布 的随机数

    Matlab 高斯分布 均匀分布 以及其他分布 的随机数 betarnd 贝塔分布的随机数生成器 binornd 二项分布的随机数生成器 chi2rnd 卡方分布的随机数生成器 exprnd 指数分布 ...

  3. NGINX的奇淫技巧 —— 3. 不同域名输出不同伺服器标识

    NGINX的奇淫技巧 —— 3. 不同域名输出不同伺服器标识 ARGUS 1月13日 发布 推荐 0 推荐 收藏 6 收藏,707 浏览 大家或许会有这种奇葩的需求...要是同一台主机上, 需要针对不 ...

  4. 精华阅读第 9 期 |滴滴出行 iOS 客户端架构演进之路

    「架构都是演变出来的,没有最好的架构,只有最合适的架构!」最近,滴滴出行平台产品中心 iOS 技术负责人李贤辉接受了 infoQ 的采访,阐述了滴滴的 iOS 客户端架构模式与演变过程.李贤辉也是移动 ...

  5. [C++]默认构造函数

    默认构造函数(default constructor)就是在没有显示提供初始化式时调用的构造函数.它由不带参数的构造函数,或者为所有的形参提供默认实参的构造函数定义.若个定义某个类的变量时没有提供初始 ...

  6. Java 获取amr音频格式的音频长度

    import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; public class GetAm ...

  7. mysql的学习记录

    1 MySQL -h localhost -u UserName -p Password-h不写,默认为localhost注意:最好先MySQL -h localhost -u UserName -p ...

  8. 找啊找啊找GF

    P1013 找啊找啊找GF 时间: 1000ms / 空间: 131072KiB / Java类名: Main 背景 MM七夕模拟赛 描述 "找啊找啊找GF,找到一个好GF,吃顿饭啊拉拉手, ...

  9. Java API —— BigDecimal类

    1.BigDecimal类概述  由于在运算的时候,float类型和double很容易丢失精度,演示案例.所以,为了能精确的表示.计算浮点数,Java提供了BigDecimal 不可变的.任意精度的有 ...

  10. tokudb引擎安装-2

    前言:因为现在tokuDB直接整合到Percona server里面了,下载页面直接跳转到下载Percona Server 页面了.安装方法跟以前不一样了,下面就来看一下新版本怎么安装了 ##准备阶段 ...