当前位置: 首页 > news >正文

音视频开发_SDL音频播放器的实现

今天向大家介绍一下如何通过 SDL 实现一个PCM音频播放器。这是一个最简单的播放器,它不涉及到音频的解复用,解码等工作。我们只需要将音频原始数据喂给 SDL 音频接口就可以听到悦耳的声音了。在下面的列子中我将向你演示,使用 SDL 做这样一个播放器是何等的简单。

当然这个看似简单的播放器其实是由许多的理论基础在底层支持着的

播放音频的基本原则

如果我们要播放一段声音,想当然的认为直接将播放的声音发送给声卡,这样扬声器就会将声音播放出来。只要我们不断的送数据,声音就会不停的输出。

事实上真的是这样吗?当 然 不 是!!!

实际上,所有的音频播放都遵守着一个原则,就是当声卡将要播放的声音输出到扬声器时,它首先会通过回调函数,向你要它一部分声频数据,然后拿着这部分音频数据去播放。等播放完了,它会再向你要下一部分。

至于要的数据的多少,什么时候向你要,这些都是由声卡决定的。对于我们上层应用来说,这些都是由底层 API 决定的。

为什么会出现这种情况呢?为什么播放音频与我们一般的逻辑相反呢?这是因为声卡会严格按照音频的播放时间进行播放,不会多一秒,也不会少一秒。正因为它能准确的计算出时间来,而应用层是不知道这个时间的,所以我们必须按照声卡的要求给它喂数据,而不能依据自己的性子来。

那么有人会问,为什么声卡可以精准的计算出播放时间来呢?这是因为在播放之前我们给它设置了采样率、通道数、采样大小等参数,通过这些参数它就可以计算出时间来。

我们来做个计算,假设采样率是 48000, 双通道,采样大小是 16bit,那么一秒种的数据是多少呢? 48000*2*16=1536000. 反过来,如果我们有一段 8M 的数据,那么声卡就知道它能播放 5秒多的声音。

上面的一大段文字描述,实际上只是想说明一个道理,就是要播放的声音数据,是声卡主动要的,不能由上层直接设置。这是通过回调函数来实现的。后面会有具体的例子。

SDL如何处理音频

SDL是一个处理多媒体的开源库,我们来看看它是如何播放音频的,具体的操作步骤是啥?

  • 打开音频设备
  • 设置音频参数
  • 播放音频
  • 向声卡喂数据
  • 关闭音频设置

详细API介绍

  • 打开音频设备
int SDL_OpenAudio(SDL_AudioSpec* desired,SDL_AudioSpec* obtained)

desired: 设置音频参数。

参数

说明

freq

每秒采频率

SDL_AudioFormat

音频数据存储格式

channels

通道数

silence

静音值

samples

采样个数

size

音频缓冲区大小

SDL_AudioCallback

回调函数

userdata

回调函数参数指针

  • btained: 返回参数。
  • 关闭音频设备
void SDL_CloseAudio(void)

播放与暂停

void SDL_PauseAudio(int pause_on)
  • pause_on: 0, 暂停播放;1, 播放;
  • 喂数据
void SDL_MixAudio(Uint8*    dst,const Uint8* src,Uint32       len,int          volume)
  • dst: 目的缓冲区
  • src: 源缓冲区
  • len: 音频数据长度
  • volume: 音量大小,0-128 之间的数。SDL_MIX_MAXVOLUME代表最大音量。

例子

这个例子主要为大家展示了一下如何使用 SDL 的音频 API 来播放声音。其基本流程是,从 pcm 文件一块一块的读数据。然后通过 read_audio_data 这个回调函数给声卡喂数据。如果一次没用完,SDL会再次调用回调函数读数据。

如果audio_buf中的数据用完了,则再次从文件中读一块数据,直到读到文件尾。

#include <stdio.h>
#include <SDL.h>#define BLOCK_SIZE 4096000static Uint8 *audio_buf = NULL;
static Uint8 *audio_pos = NULL;
static size_t buffer_len = 0;//callback function for audio devcie
void read_audio_data(void *udata, Uint8 *stream, int len){if(buffer_len == 0){return;}SDL_memset(stream, 0, len);len = (len < buffer_len) ? len : buffer_len;SDL_MixAudio(stream, audio_pos, len, SDL_MIX_MAXVOLUME);audio_pos += len;buffer_len -= len;
}int main(int argc, char *argv[])
{int ret = -1;FILE *audio_fd = NULL;SDL_AudioSpec spec;char *path = "./test.pcm";//SDL initializeif(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)){fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());return ret;}//open pcm fileaudio_fd = fopen(path, "r");if(!audio_fd){fprintf(stderr, "Failed to open pcm file!\n");goto __FAIL;}//SDL_AudioSpecspec.freq = 44100;;spec.format = AUDIO_S16SYS;spec.channels = 2;spec.silence = 0;spec.samples = 1024;;spec.callback = read_audio_data;;spec.userdata = NULL;//open audio devcieif(SDL_OpenAudio(&spec, NULL)){fprintf(stderr, "Failed to open audio device, %s\n", SDL_GetError());goto __FAIL;}//play audioSDL_PauseAudio(0);do{//read data from pcm filebuffer_len = fread(audio_buf, 1, BLOCK_SIZE, audio_fd);fprintf(stderr, "block size is %zu\n", buffer_len);audio_pos = audio_buf;//the main thread wait for a momentwhile(audio_pos < (audio_buf + buffer_len)) {SDL_Delay(1);}}while(buffer_len !=0);//close audio deviceSDL_CloseAudio();ret = 0;__FAIL://release some resourcesif(audio_buf){free(audio_buf);}if(audio_fd){fclose(audio_fd);}//quit SDLSDL_Quit();return ret;
}

粉丝福利, 免费领取C++音视频学习资料包+学习路线大纲、技术视频/代码,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

相关文章:

  • vue+intro.js实现引导功能
  • 离散数学--连通性和矩阵
  • 通用视频模板解决方案,视频生产制作更轻松
  • C#面:什么是DLL文件,使用它们有什么好处
  • Vue47-修改默认配置webpack.config.js文件
  • 05-5.4.1 树的存储结构
  • Mac下载了docker,在终端使用docker命令时用不了
  • 使用 calibre 拆分电子书合辑
  • vue标签组
  • cloud_enum:一款针对不同平台云环境安全的OSINT工具
  • 当OpenHarmony遇上OpenEuler
  • 元数据:数据的罗塞塔石碑
  • Pytorch环境配置的方法
  • eclipse maven打包报错: 致命错误: 在类路径或引导类路径中找不到程序包 java.lang的解决
  • MySQL 保姆级教程(七):用正则表达式进行搜索
  • [nginx文档翻译系列] 控制nginx
  • 【162天】黑马程序员27天视频学习笔记【Day02-上】
  • 【node学习】协程
  • CentOS学习笔记 - 12. Nginx搭建Centos7.5远程repo
  • Cumulo 的 ClojureScript 模块已经成型
  • JAVA 学习IO流
  • JavaScript设计模式之工厂模式
  • Java反射-动态类加载和重新加载
  • learning koa2.x
  • LintCode 31. partitionArray 数组划分
  • Protobuf3语言指南
  • 创建一个Struts2项目maven 方式
  • 关于 Cirru Editor 存储格式
  • 看完九篇字体系列的文章,你还觉得我是在说字体?
  • 聊聊springcloud的EurekaClientAutoConfiguration
  • 前端每日实战 2018 年 7 月份项目汇总(共 29 个项目)
  • 实战:基于Spring Boot快速开发RESTful风格API接口
  • 微服务核心架构梳理
  • 微信小程序--------语音识别(前端自己也能玩)
  • 微信支付JSAPI,实测!终极方案
  • 我是如何设计 Upload 上传组件的
  • elasticsearch-head插件安装
  • Spring Batch JSON 支持
  • UI设计初学者应该如何入门?
  • 智能情侣枕Pillow Talk,倾听彼此的心跳
  • #{} 和 ${}区别
  • (12)目标检测_SSD基于pytorch搭建代码
  • (Arcgis)Python编程批量将HDF5文件转换为TIFF格式并应用地理转换和投影信息
  • (C语言)二分查找 超详细
  • (Mac上)使用Python进行matplotlib 画图时,中文显示不出来
  • (pojstep1.1.2)2654(直叙式模拟)
  • (SpringBoot)第七章:SpringBoot日志文件
  • (超简单)使用vuepress搭建自己的博客并部署到github pages上
  • .mat 文件的加载与创建 矩阵变图像? ∈ Matlab 使用笔记
  • .NET Project Open Day(2011.11.13)
  • .NET 常见的偏门问题
  • .net 调用海康SDK以及常见的坑解释
  • .NET 中使用 Mutex 进行跨越进程边界的同步
  • .net6 webapi log4net完整配置使用流程
  • .NET开源项目介绍及资源推荐:数据持久层 (微软MVP写作)