/**
* SDL2播放PCM
*
*
* 本程序使用SDL2播放PCM音频采样数据。SDL实际上是对底层绘图
* API(Direct3D,OpenGL)的封装,使用起来明显简单于直接调用底层
* API。
* 测试的PCM数据采用采样率44.1k, 采用精度S16SYS, 通道数2
*
* 函数调用步骤如下:
*
* [初始化]
* SDL_Init(): 初始化SDL。
* SDL_OpenAudio(): 根据参数(存储于SDL_AudioSpec)打开音频设备。
* SDL_PauseAudio(): 播放音频数据。
*
* [循环播放数据]
* SDL_Delay(): 延时等待播放完成。
*
*/
#include <stdio.h>
#include <SDL.h>
#include <iostream>
// 每次读取2帧数据, 以1024个采样点一帧 2通道 16bit采样点为例
// 一个采样点为16bit
#define PCM_BUFFER_SIZE (1024*2*2*2)
// 音频PCM数据缓存
static Uint8* s_audio_buf = NULL;
// 目前读取的位置
static Uint8* s_audio_pos = NULL;
// 缓存结束位置
static Uint8* s_audio_end = NULL;
//音频设备回调函数
void fill_audio_pcm(void* udata, Uint8* stream, int len)
{
std::cout << "fill_audio_pcm" << std::endl;
SDL_memset(stream, 0, len);
if (s_audio_pos >= s_audio_end) // 数据读取完毕
{
return;
}
// 数据够了就读预设长度,数据不够就只读部分(不够的时候剩多少就读取多少)
int remain_buffer_len = s_audio_end - s_audio_pos;
len = (len < remain_buffer_len) ? len : remain_buffer_len;
// 拷贝数据到stream并调整音量
SDL_MixAudio(stream, s_audio_pos, len, SDL_MIX_MAXVOLUME / 8);
printf("len = %d\n", len);
s_audio_pos += len; // 移动缓存指针
}
// 提取PCM文件
// ffmpeg -i input.mp4 -t 20 -codec:a pcm_s16le -ar 44100 -ac 2 -f s16le 44100_16bit_2ch.pcm
// 测试PCM文件
// ffplay -ar 44100 -ac 2 -f s16le 44100_16bit_2ch.pcm
#undef main
int main(int argc, char* argv[])
{
int ret = -1;
FILE* audio_fd = NULL;
SDL_AudioSpec spec;//音频设备
const char* path = "44100_16bit_2ch.pcm";
// 每次缓存的长度
size_t read_buffer_len = 0;
//SDL initialize
if (SDL_Init(SDL_INIT_AUDIO)) // 支持AUDIO
{
fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());
return ret;
}
//打开PCM文件
fopen_s(&audio_fd,path, "rb");
if (!audio_fd)
{
fprintf(stderr, "Failed to open pcm file!\n");
// goto _FAIL;
}
s_audio_buf = (uint8_t*)malloc(PCM_BUFFER_SIZE);
// 音频参数设置SDL_AudioSpec
spec.freq = 44100; // 采样频率
spec.format = AUDIO_S16SYS; // 采样点格式
spec.channels = 2; // 2通道
spec.silence = 0;
spec.samples = 1024; // 23.2ms -> 46.4ms 每次读取的采样数量,多久产生一次回调和 samples
spec.callback = fill_audio_pcm; // 回调函数
spec.userdata = NULL;
//打开音频设备
if (SDL_OpenAudio(&spec, NULL))
{
fprintf(stderr, "Failed to open audio device, %s\n", SDL_GetError());
//goto _FAIL;
}
//play audio
SDL_PauseAudio(0);
int data_count = 0;
while (1)
{
// 从文件读取PCM数据
read_buffer_len = fread(s_audio_buf, 1, PCM_BUFFER_SIZE, audio_fd);
if (read_buffer_len == 0)
{
break;
}
data_count += read_buffer_len; // 统计读取的数据总字节数
printf("now playing %10d bytes data.\n", data_count);
s_audio_end = s_audio_buf + read_buffer_len; // 更新buffer的结束位置
s_audio_pos = s_audio_buf; // 更新buffer的起始位置
//the main thread wait for a moment
while (s_audio_pos < s_audio_end)
{
SDL_Delay(10); // 等待PCM数据消耗
}
}
printf("play PCM finish\n");
// 关闭音频设备
SDL_CloseAudio();
_FAIL:
//release some resources
if (s_audio_buf)
free(s_audio_buf);
if (audio_fd)
fclose(audio_fd);
//quit SDL
SDL_Quit();
return 0;
}
当使用SDL_PauseAudio(0)时, 表示音频开始播放
-参数 0:
当参数为 0 时,表示恢复音频播放。相反,如果传入 1,则表示暂停音频播放。
SDL_PauseAudio(0);
// 音频PCM数据缓存
static Uint8* s_audio_buf = NULL;
// 目前读取的位置
static Uint8* s_audio_pos = NULL;
// 缓存结束位置
static Uint8* s_audio_end = NULL;
这个函数的功能是将缓存的数据存放到stream中, 但是这里使用的并不是s_audio_buf, 而是s_audio_pos, 其实s_audio_pos也指向缓存(这里刚刚解释过了)
// 拷贝数据到stream并调整音量
SDL_MixAudio(stream, s_audio_pos, len, SDL_MIX_MAXVOLUME / 8);
音频的数据和视频还不太一样, 视频是以帧为单位播放的, 可以指定单位时间内播放的帧的数量并且视频帧是需要在显示屏上停留一段微小的时间, 但是音频是连续的, 无法直接给定时间. 当给stream的值消耗完成后, 及s_audio_pos ==s_audio_end, 重新从文件中获取数据
// 等待PCM数据消耗,
while (s_audio_pos < s_audio_end)
{
SDL_Delay(10); // 每次循环延时10ms
}
首先, pcm是一种纯文本格式, 也就是说, pcm的播放速度是人为指定的, 这个就是我们使用的采样率
spec.freq = 44100; // 采样频率
当使用不同采样率的时候, 播放速度是不一样的, 可以通过计时器来验证一下
当采样频率比较小的时候, 播放时间就会比较长, 但是并不是所有的音频都可以通过修改Player的采样频率来实现是否倍数播放, 比如非纯文本格式的mp3是不可以的, 这样做的后果只是video的清晰度而已
因篇幅问题不能全部显示,请点此查看更多更全内容
Copyright © 2019- bangwoyixia.com 版权所有 湘ICP备2023022004号-2
违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com
本站由北京市万商天勤律师事务所王兴未律师提供法律服务