上一篇:《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. JS实例-DOM查询

    <!DOCTYPE html><html lang="en"><head> <meta charset="utf-8" ...

  2. GRE 协议 - 和 ISP 用的协议不一样怎么办

    GRE 出现的背景: 随着网络(公司)规模的增大,越来越多的公司需要在跨区域之间建设自己的分公司.但随之也就出现了这样的问题,考虑这样一个场景.公司 A 在北京和上海间开设了两家公司,由于业务的需要, ...

  3. 复习 Array,重学 JavaScript

    1 数组与对象 在 JavaScript 中,一个对象的键只能有两种类型:string 和 symbol.下文只考虑键为字符串的情况. 1.1 创建对象 在创建对象时,若对象的键为数字,或者由 字母+ ...

  4. vue.extend和vue.component的区别

    vue.extend 使用基础 Vue 构造器函数,通过原型继承,(返回)创建一个"子类"(构造器).参数是一个包含组件选项的对象. const Sub = function Vu ...

  5. C++用递归实现斐波那契数列

    [题目描述] 菲波那契数列是指这样的数列: 数列的第一个和第二个数都为1,接下来每个数都等于前面2个数之和. 给出一个正整数a,要求菲波那契数列中第a个数是多少. [输入] 第1行是测试数据的组数n, ...

  6. Android The layout "activity_main" in layout has no declaration in the base layout folder

    报错: The layout "activity_main" in layout has no declaration in the base layout folder; thi ...

  7. 前端路由、后端路由——想要学好vue-router 或者 node.js 必须得明白的两个概念

    前端路由和后端路由的概念讲解 引言 正文 一.路由的概念 二.后端路由 三.前端路由 四.其他知识 结束语 引言 无论你是正在学习vue 还是在学习node, 你一定会碰到前端路由和后端路由这两个概念 ...

  8. 第1章 RDD概念 弹性分布式数据集

    第1章 RDD概念  弹性分布式数据集 1.1 RDD为什么会产生 RDD是Spark的基石,是实现Spark数据处理的核心抽象.那么RDD为什么会产生呢? Hadoop的MapReduce是一种基于 ...

  9. ImportError: No module named git

    问题:ImportError: No module named git 解决:yum install GitPython

  10. install-newton部署安装--------控制节点

    #################################################################################################### ...