SDL基础使用06 (SDL播放pcm文件)

SDL播放PCM文件

C语言文件打开方式

// 提取PCM文件
// ffmpeg -i input.mp4 -t 20 -codec:a pcm_s16le -ar 44100 -ac 2 -f s16le suiyue_44100_2_s16le.pcm
// 测试PCM文件
// ffplay -ar 44100 -ac 2 -f s16le suiyue_44100_2_s16le.pcm
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
extern "C"
{
#include <SDL.h>
}

// 每次读取2帧数据, 以1024个采样点一帧 2通道 16bit采样点为例
#define PCM_BUFFER_SIZE (2 * 1024 * 2 * 2)

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)
{
	// udata: 用户自定义数据
	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;
	// 设置混音
	SDL_MixAudio(stream, s_audio_pos, len, SDL_MIX_MAXVOLUME / 8);  // 音量的值 SDL_MIX_MAXVOLUME 128
	printf("use %d bytes\n", len);									// 4096 bytes  samples * channels * format

	s_audio_pos += len;  // 移动缓存指针(当前pos)
}

#undef main
int main()
{
	int ret = -1;
	FILE *audio_fd = NULL;
	SDL_AudioSpec spec;                         // SDL音频设备
	const char *path = "./suiyue_44100_2_s16le.pcm";
	
	size_t read_buffer_len = 0;					// 存储每次读取文件数据的长度

	// 1. SDL初始化AUDIO
	if (SDL_Init(SDL_INIT_AUDIO))    
	{
		fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());
		return ret;
	}

	// 2. 打开PCM文件(以二进制方式打开)
	audio_fd = fopen(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;              // 采样频率 44.1k
	spec.format = AUDIO_S16SYS;     // 采样点格式 (交错模式16bit)
	spec.channels = 2;              // 2通道
	spec.silence = 0;               // 设置静音的值
	// samples * channels * format = 4096 bytes  (回调每次读取的数据)
	// 每次读取的采样数量,多久调用一次回调和samples有关系(回调间隔 1024 / 44100)23.2ms
	spec.samples = 1024;
	spec.callback = fill_audio_pcm; // 设置回调函数
	spec.userdata = NULL;

	// 3. 打开音频设备
	if (SDL_OpenAudio(&spec, NULL))
	{
		fprintf(stderr, "Failed to open audio device, %s\n", SDL_GetError());
		goto _FAIL;
	}

	// 4. 设置为0时开始播放,为1时播放静音数据
	SDL_PauseAudio(0);

	int data_count = 0;    // 存储读取的总字节数
	while (1)
	{
		// 从文件读取PCM数据到s_audio_buf中(每次读取两帧数据)
		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("read %10d bytes data\n", data_count);			 // 每次加2帧数据 8192 bytes
		s_audio_end = s_audio_buf + read_buffer_len;			 // 更新buffer的结束位置(buf位置 + 本次读取的长度)
		s_audio_pos = s_audio_buf;							     // 更新buffer的起始位置

		while (s_audio_pos < s_audio_end)                // 等待回调函数将数据读取完毕
		{
			// 不能大于上面计算的23.2ms 否则会产生断音
			SDL_Delay(10);  // 等待PCM数据消耗
		}
	}
	printf("play PCM finish\n");

	// 关闭音频设备
	SDL_CloseAudio();

_FAIL:
	// 释放资源退出
	if (s_audio_buf)
		free(s_audio_buf);

	if (audio_fd)
		fclose(audio_fd);

	SDL_Quit();

	return 0;
}

 C++文件打开方式

  •     1. 初始化SDL                                SDL_Init
  •     2. 创建音频播放回调函数,打开音频设备        SDL_OpenAudio
  •     3. 开始播放                                SDL_PauseAudio
  •     4. 打开文件循环读取数据到缓冲                    
  •     5. 关闭音频设备                            SDL_CloseAudio
  •     6. 释放资源,退出                        SDL_Quit 
#include <iostream>
#include <fstream>
#include <cmath>

// SDL 播放PCM数据
extern "C"
{
#include <SDL.h>
}
#pragma comment(lib, "SDL2.lib")


Uint8* g_audio_chunk;				// 下面读取两帧数据的缓存
Uint32 g_audio_len;					// 读取的长度
Uint8* g_audio_pos;					// 当前声卡消耗到的位置

// 接收音频数据的回调函数
void read_audio_data_cb(void* udata, Uint8* stream, int len)
{
	SDL_memset(stream, 0x00, len);
	if (g_audio_len == 0)
		return;
	len = SDL_min(len, static_cast<int>(g_audio_len));

	// 将音频数据 g_audio_pos 传给 stream
	std::cout << "use " << len << " bytes" << std::endl;
	SDL_MixAudio(stream, g_audio_pos, len, SDL_MIX_MAXVOLUME);	// (设置混音(也可直接拷贝))将声音喂给声卡(最后一个参数音量0-128)
	g_audio_pos += len;
	g_audio_len -= len;
}

#undef main
int main()
{
	// 1. 初始化SDL为audio
	int nRet = SDL_Init(SDL_INIT_AUDIO);
	if (nRet < 0)
	{
		std::cout << "SDL Error: " << SDL_GetError() << std::endl;
		return -1;
	}

	// 定义结构
	SDL_AudioSpec spec;
	spec.freq = 44100;
	spec.channels = 2;
	spec.format = AUDIO_S16SYS;				// s16le
	spec.samples = 1024;					// 字节数(与回调间隔有关) 1024 / 44100 约 23.2ms (设置延时超过这个数会出现断音现象)
	spec.callback = read_audio_data_cb;		// 回调函数(回调函数缓冲len的长度为 samples * format * channels)
	spec.userdata = NULL;
	// 2. 根据参数打开音频设备
	if (SDL_OpenAudio(&spec, NULL) < 0)
	{
		return -1;
	}

	// 3. 打开文件(需要使用二进制方式打开)
	std::ifstream pcmFile("./suiyue_44100_2_s16le.pcm", std::ios::in | std::ios::binary);
	if (!pcmFile.is_open())
	{
		return -1;
	}

	// 4. 开始播放
	SDL_PauseAudio(0);			// 0 开始播放 1 播放静音数据
	 
	char* buffer = (char*)malloc(2 * 1024 * 2 * 2);		// 每次读两帧数据,以1024个采样点 双声道 位深16 为一帧
	while (!pcmFile.eof())
	{
		// 将两帧音频数据读取到buffer中(8192 bytes)
		pcmFile.read(buffer, 1024 * 2 * 2 * 2);
		if (pcmFile.bad())
		{
			return -1;
		}

		g_audio_chunk = reinterpret_cast<Uint8*>(buffer);	// 真实读取的音频数据
		g_audio_len = pcmFile.gcount();						// 真实读取的字节数
		g_audio_pos = g_audio_chunk;						// 将音频位置设置到刚读取到的buffer位置

		std::cout << "read " << g_audio_len << " bytes" << std::endl;
		// 延时等待声卡消耗音频数据
		while (g_audio_len > 0)
		{
			SDL_Delay(10);
		}
	}

	// 5. 关闭音频设备
	SDL_CloseAudio();

	// 6. 释放资源、退出
	free(buffer);
	buffer = NULL;
	SDL_Quit();

	return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

石小浪♪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值