上一篇:《SDL开发笔记(一):SDL介绍、编译使用以及工程模板
下一篇:敬请期待

 

前言

  对于Qt应用来说,为了更大的跨平台通用性,使用SDL播放音频,同时也能做更多的扩充操作。

 

声波

  声音是通过空气传播的一种连续的波,简称声波。声音的强弱体现在声波压力的大小上,音调的音调体现在声音的频率上。
  声音信号由两个基本参数是频率和复读。信号的频率指的是信号每秒变化的次数,用Hz表示。
频率范围为20Hz20Khz的信号成为音频信号。该范围内的音频声音幅度在0120dB之间,可被人感知到。
声音转换为数字信号,则成为音频信号。

 

音频信号

  音频信号(acoustic signals)是带有语音、音乐和音效的有规律的声波的频率、幅度变化信息载体。根据声波的特征,可把音频信息分类为规则音频和不规则声音。其中规则音频又可以分为语音、音乐和音效。规则音频是一种连续变化的模拟信号,可用一条连续的曲线来表示,称为声波。
  声音的三个要素是音调、音强和音色。声波或正弦波有三个重要参数:频率 ω0、幅度An和相位ψn ,这也就决定了音频信号的特征。
  对音频信号进行采样,模拟信号数字化后,就是数字音频信号了。

 

数字音频信号

  数字音频计算机数据的存储是以0、1的形式存取的,那么数字音频就是首先将音频文件转化,接着再将这些电平信号转化成二进制数据保存,播放的时候就把这些数据转换为模拟的电平信号再送到喇叭播出,数字声音和一般磁带、广播、电视中的声音就存储播放方式而言有着本质区别。相比而言,它具有存储方便、存储成本低廉、存储和传输的过程中没有声音的失真、编辑和处理非常方便等特点。
  数字音频信号,就是我们最终处理的音频数据。
  音频数字信号信号具备几个特征:

量化级

  简单地说就是描述声音波形的数据是多少位的二进制数据,通常用bit做单位,如16bit、24bit。16bit量化级记录声音的数据是用16位的二进制数,因此,量化级也是数字声音质量的重要指标。我们形容数字声音的质量,通常就描述为24bit(量化级)、48KHz采样,比如标准CD音乐的质量就是16bit、44.1KHz采样。

声道

  可以简单的理解为通过一个振膜采样到的音频数据就是一个声道,两个振膜就是两个声道,以此类推。振膜一般有大、中、小三种尺寸,尺寸越大,对声波越敏感,成本也越高。一个麦克风里面有的有一个振膜,有的有两个振膜。一个振膜的麦克风进行的是Mono单声道录音,两个振膜的麦克风进行的是Stereo双声道立体声录音。五声道环绕立体声录音就是麦克风1录取东北方向的声音,麦克风2录取西北方向的声音,麦克风3录取西南方向的声音,麦克风4录取东南方向的声音,麦克风5录取正前方的声音。另外还有四声道环绕立体声录音和七声道环绕立体声录音。

采样率

  简单地说就是通过波形采样的方法记录1秒钟长度的声音,需要多少个数据。44KHz采样率的声音就是要花费44000个数据来描述1秒钟的声音波形。原则上采样率越高,声音的质量越好。

比特率

一种数字音乐压缩效率的参考性指标,表示记录音频数据每秒钟所需要的平均比特值(比特是电脑中最小的数据单位,指一个0或者1的数),通常我们使用Kbps(通俗地讲就是每秒钟1024比特)作为单位。CD中的数字音乐比特率为1411.2Kbps(也就是记录1秒钟的CD音乐,需要1411.2×1024比特的数据),近乎于CD音质的MP3数字音乐需要的比特率大约是112Kbps~128Kbps。

压缩率

  通常指音乐文件压缩前和压缩后大小的比值,用来简单描述数字声音的压缩效率。

 

SDL音频播放流程解析

  基本流程如下:

步骤一:初始化子系统

  初始化音频系统,其他多余的系统不用初始化。

步骤二:根据音频信息打开音频设备

  填充好SDL_AudioSpec音频信息,打开音频设备,此时会返回最接近的音频设备,若没有接近的则第二个参数返回0,此时我们直接第二个参数如0,无需返回。

步骤三:开始播放

  使用SDL_PauseAudio(0)进行播放。

步骤四:循环补充数据

  根据缓冲区数据长度和文件剩余的数据长度进行补充,若缓冲区数据没了,就补充一次,使用SDL_Delay进行1ms的延迟,用当前缓存区剩余未播放的长度大于0结合前面的延迟进行等待。

步骤四(附加):回调函数

  开始播放后,会有音频其他子线程来调用回调函数,进行音频数据的补充,经过测试每次补充4096个字节。

步骤五:关闭音频设别

步骤六:退出SDL系统

 

SDL播放音频相关变量

struct SDL_AudioSpec

  SDL_AudioSpec是包含音频输出格式的结构体,同时它也包含当音频设备需要更多数据时调用的回调函数,此结构体是关键。

typedef struct SDL_AudioSpec
{
int freq; // DSP频率—每秒采样数
SDL_AudioFormat format; // 音频数据格式
Uint8 channels; // 通道数1-单声道,2-立体声
Uint8 silence; // 音频缓冲静音值(计算)
Uint16 samples; // 基本是512、1024设置不合适可能会导致卡顿’
Uint16 padding; // 对于某些编译环境是必需的
Uint32 size; // 音频缓冲区大小(字节)(计算)
SDL_AudioCallback callback; // 为音频设备提供数据回调(空值使用SDL 自身预先定义的SDL_QueueAudio ()回调函数)
void *userdata; // 传递给回调的Userdata(对于空回调忽略)
} SDL_AudioSpec;

  举例:播放pcm音频“匆匆那年-44100-16位-双通道.pcm”

// 音频结构体设置
SDL_AudioSpec sdlAudioSpec;
sdlAudioSpec.freq = 44100;
sdlAudioSpec.format = AUDIO_S16SYS;
sdlAudioSpec.channels = 1;
sdlAudioSpec.silence = 0;
sdlAudioSpec.samples = 1024;
sdlAudioSpec.callback = callBack_fillAudioData;
sdlAudioSpec.userdata = 0;
 

SDL播放音频相关原型

SDL_Init()

int SDLCALL SDL_Init(Uint32 flags);

  使用此函数初始化SDL库,必须在使用大多数其他SDL函数之前调用它,初始化的时候尽量做到“够用就好”,而不要用SDL_INIT_EVERYTHING。会出现一些不可预知的问题。

  • 参数一:输入初始化的设备

SDL_OpenAudio()

int SDL_OpenAudio(SDL_AudioSpec * desired,
SDL_AudioSpec * obtained);

  此函数使用所需参数打开音频设备,然后如果成功,则返回0,将实际硬件参数放入已获得指向的结构。如果获得的为空,则音频传递给回调函数的数据将被保证在请求的格式,并将自动转换为硬件音频格式(如有必要)。如果失败,此函数返回-1,则无法打开音频设备,或无法设置音频线程。

  • 参数一:输入需要打开的音频设备参数;
  • 参数二:返回打开成功的音频设备参数;

SDL_PauseAudio()

extern DECLSPEC void SDLCALL SDL_PauseAudio(int pause_on);

  暂停音频功能。函数暂停和取消暂停音频回调处理。
  打开音频后,应使用参数0调用它们开始播放声音的设备。这样就可以在打开音频设备后安全地初始化回调函数的数据。
  暂停期间,静音将写入音频设备。

SDL_MixAudio:混音播放函数

void SDL_MixAudio(Uint8 * dst,
const Uint8 * src,
Uint32 len,
int volume);

  这需要播放音频格式和混音的两个音频缓冲区它们执行加法、音量调节和溢出剪辑。音量的范围从0到128,应设置为SDL_MIX_MAXVOLUME全音频音量。注意这不会改变硬件的音量。
这是为了方便起见,可以混合音频数据。

  • 参数一:目标数据,这个是回调函数里面的stream指针指向的,直接使用回调的stream指针即可。
  • 参数二:音频数据,这个是将需要播放的音频数据混到stream里面去,那么这里就是我们需要填充的播放的数据。
  • 参数三:音频数据的长度,这个是我们填充过去的长度。
  • 参数四:音量,0~128范围,SAL_MIX_MAXVOLUME为128,设置的是软音量,不是硬件的音响。

SDL_Delay()

void SDL_Delay(Uint32 ms);

  在返回之前等待指定的毫秒数。

SDL_Quit()

void SDLCALL SDL_Quit(void);

  此函数用于清除所有初始化的子系统。在所有退出条件后调用它。

 

Demo源码

void SDLManager::testPlayPCM()
{
int ret = 0;
// 音频结构体
SDL_AudioSpec sdlAudioSpec;
// sdlAudioSpec.freq = 44100;
sdlAudioSpec.freq = 22050;
// sdlAudioSpec.format = AUDIO_U8; // x
// sdlAudioSpec.format = AUDIO_S8; // x
// sdlAudioSpec.format = AUDIO_U16LSB; // x
// sdlAudioSpec.format = AUDIO_S16LSB; // √
// sdlAudioSpec.format = AUDIO_U16MSB; // x
// sdlAudioSpec.format = AUDIO_U16LSB; // x
// sdlAudioSpec.format = AUDIO_S16MSB; // x
// sdlAudioSpec.format = AUDIO_U16; // x
sdlAudioSpec.format = AUDIO_S16; // √
// sdlAudioSpec.format = AUDIO_S16SYS; // x
// sdlAudioSpec.format = AUDIO_S32SYS; // x
// sdlAudioSpec.format = AUDIO_F32SYS; // x
// sdlAudioSpec.format = AUDIO_F32MSB; // x
sdlAudioSpec.channels = 1;
sdlAudioSpec.silence = 0;
sdlAudioSpec.samples = 1024; // 导致错误512~1024之间
sdlAudioSpec.callback = callBack_fillAudioData;
sdlAudioSpec.userdata = 0; QString fileName; #if 0
fileName = "testPCM/王妃-22050-16位-单通道.pcm";
sdlAudioSpec.freq = 22050;
sdlAudioSpec.channels = 1;
sdlAudioSpec.format = AUDIO_S16;
#endif
#if 1
fileName = "testPCM/匆匆那年-44100-16位-双通道.pcm";
sdlAudioSpec.freq = 44100;
sdlAudioSpec.channels = 2;
sdlAudioSpec.format = AUDIO_S16;
#endif
#if 0
fileName = "testPCM/北京北京8k16bits单声道.pcm";
sdlAudioSpec.freq = 8000;
sdlAudioSpec.channels = 1;
sdlAudioSpec.format = AUDIO_S16;
#endif
#if 0
fileName = "testPCM/冰雨片段48k16bit单声道.pcm";
sdlAudioSpec.freq = 48000;
sdlAudioSpec.channels = 1;
sdlAudioSpec.format = AUDIO_S16;
#endif
#if 0
fileName = "testPCM/浪花一朵朵片段48k16bit单声道.pcm";
sdlAudioSpec.freq = 48000;
sdlAudioSpec.channels = 1;
sdlAudioSpec.format = AUDIO_S16;
#endif QFile file(fileName);
if(!file.open(QIODevice::ReadOnly))
{
LOG << "Failed" << file.exists();
return;
} // 步骤一:初始化音频子系统
ret = SDL_Init(SDL_INIT_AUDIO);
if(ret)
{
LOG << "Failed";
return;
} // 步骤二:打开音频设备
ret = SDL_OpenAudio(&sdlAudioSpec, 0);
if(ret)
{
LOG << "Failed";
return;
} // 步骤三:开始播放
SDL_PauseAudio(0); #if 1
// 步骤四:一次性读取所有的数据
QByteArray data = file.readAll();
int pos = 0;
_audioPos = (uint8_t *)data.data();
_audioLen = data.size();
pos += data.size();
while(_audioLen > 0)
{
SDL_Delay(1);
}
#else
// 步骤四:一次性读取4096
int readSize = 4096;
while(true)
{
_audioPos = (uint8_t *)file.read(readSize).data();
_audioLen = readSize;
while(_audioLen > 0)
{
SDL_Delay(1);
}
}
#endif
// 步骤:播放完毕
SDL_CloseAudio(); // 步骤:释放SDL
SDL_Quit(); if(file.isOpen())
{
file.close();
return;
}
} void SDLManager::callBack_fillAudioData(void *userdata, uint8_t *stream, int len)
{
SDL_memset(stream, 0, len);
if(_audioLen == 0)
{
return;
}
len = (len > _audioLen ? _audioLen : len); SDL_MixAudio(stream, _audioPos, len, SDL_MIX_MAXVOLUME); _audioPos += len;
_audioLen -= len; // 每次加载4096
LOG << len;
}
 

工程模板:对应版本号v1.1.0

  对应版本号v1.1.0:播放裸PCM数据。

 

上一篇:《SDL开发笔记(一):SDL介绍、编译使用以及工程模板
下一篇:敬请期待

 

SDL开发笔记(二):音频基础介绍、使用SDL播放音频的更多相关文章

  1. Linux及Arm-Linux程序开发笔记(零基础入门篇)

    Linux及Arm-Linux程序开发笔记(零基础入门篇)  作者:一点一滴的Beer http://beer.cnblogs.com/ 本文地址:http://www.cnblogs.com/bee ...

  2. 【Linux开发】Linux及Arm-Linux程序开发笔记(零基础入门篇)

    Linux及Arm-Linux程序开发笔记(零基础入门篇) 作者:一点一滴的Beer http://beer.cnblogs.com/ 本文地址:http://www.cnblogs.com/beer ...

  3. Django开发笔记二

    Django开发笔记一 Django开发笔记二 Django开发笔记三 Django开发笔记四 Django开发笔记五 Django开发笔记六 1.xadmin添加主题.修改标题页脚和收起左侧菜单 # ...

  4. EasyUI 开发笔记(二)

    接上篇 :EasyUI 开发笔记(一)  (http://www.cnblogs.com/yiayi/p/3485258.html) 这期就简单介绍下, easyui 的 list 展示, 在easy ...

  5. iOS陆哥开发笔记(七) (AVFoundation简单介绍)

    在AVFoundation框架中AVAudioRecorder类专门处理录音操作,支持多种音频格式. 以下是经常使用的属性和方法: 属性 说明 @property(readonly, getter=i ...

  6. SDL 开发实战(三):使用 SDL 绘制基本图形

    在上文 SDL 开发实战(二):SDL 2.0 核心 API 解析 我们讲解了SDL最核心的API,并结合Hello World代码了解了SDL渲染画面的基本原理. 本文我们来讲一下,如何使用SDL的 ...

  7. SDL 开发实战(七): 使用 SDL 实现 PCM播放器

    在上文,我们做了YUV播放器,这样我们就入门了SDL播放视频.下面我们来做一个PCM播放,即使用SDL播放PCM数据. 下面说明一下使用SDL播放PCM音频的基本流程,主要分为两大部分:初始化SDL. ...

  8. SDL 开发实战(六): 使用 SDL 实现 YUV 播放器

    前面铺垫了这么多,现在终于进入核心的主题了,那就是使用SDL播放视频,本节我们将使用SDL播放YUV视频,也就是做一个YUV播放器. 下面说明一下使用SDL播放YUV视频的基本流程,主要分为两大部分: ...

  9. [基础]斯坦福cs231n课程视频笔记(二) 神经网络的介绍

    目录 Introduction to Neural Networks BP Nerual Network Convolutional Neural Network Introduction to Ne ...

随机推荐

  1. Springboot调用Oracle存储过程的几种方式

    因工作需要将公司SSH项目改为Spingboot项目,将项目中部分需要调用存储过程的部分用entityManagerFactory.unwrap(SessionFactory.class).openS ...

  2. 记一次生产环境tomcat线程数打满情况分析

    前言 旨在分享工作中遇到的各种问题及解决思路与方案,与大家一起学习. -- 学无止境, 加油 ! Just do it ! 问题描述 运行环境描述 tomcat-8.5 单节点(该应用集群20个节点) ...

  3. mycat数据库集群系列之数据库多实例安装

    mycat数据库集群系列之数据库多实例安装 最近在梳理数据库集群的相关操作,现在花点时间整理一下关于mysql数据库集群的操作总结,恰好你又在看这一块,供一份参考.本次系列终结大概包括以下内容:多数据 ...

  4. PythonCrashCourse 第三章习题

    PythonCrashCourse 第三章习题 3.1 将一些朋友的姓名存储在一个列表中,并将其命名为names.依次访问该列表中的每个元素,从而将每个朋友的姓名都打印出来 names = ['lih ...

  5. 海豚星空扫码投屏 Android 接收端 SDK 集成 六步骤

    一 跟目录的build.gradle添加私有mevan仓库 maven {url 'http://nexus.dolphinstar.cn/repo/openmavenx'} 二 app/build. ...

  6. 开发APP遇到的问题

    1.代码尽量复用 2.调用高德地图,直辖市等,省字段一定有值,市可能为空(pro:'北京市',city:[]) 3.支付密码不用组件 <template> <view> < ...

  7. lvm常用指令

    1.物理卷命令一般维护命令:#pvscan //在系统的所有磁盘中搜索已存在的物理卷#pvdisplay 物理卷全路径名称 //用于显示指定物理卷的属性.#pvdata 物理卷全路径名称 //用于显示 ...

  8. Java中实现对集合中对象按中文首字母排序

    有一个person对象如下: public class Person { private String id;private String nam; } 一个list集合如下: List<Emp ...

  9. python字典的概念与基本操作

    字典是非常常用的一种数据结构,它与json格式的数据非常相似,核心就是以键值对的形式存储数据,关于Python中的字典做如下四点说明: 1.构造字典对象需要用大括号表示 {},每个字典元素都是以键值对 ...

  10. ACM study day3

    今天练了二分和快速幂,题目挺难的,挑几个我做上的说一下吧. 先给出几个二分和快速幂的模板函数: 二分 void BS(int m) { int x=,y=a[m-]-a[]; while(y-x> ...