SDL(Simple DirectMedia Layer)是一个非常流行和强大的跨平台开发库,它主要被用来开发视频游戏和实时多媒体应用程序。它提供了一系列的功能来处理视频、音频、键盘、鼠标、操纵杆、图形硬件加速以及聚焦3D硬件的各种功能。SDL的API通过C编程语言被设计和实现,但存在多种语言的绑定,方便不同的开发者使用不同的编程语言。
在音视频开发方面,SDL提供了基础的API来进行音频播放和视频的渲染。例如,SDL_Audio接口允许你设定音频设备的参数,如采样率、音频格式和声道数,然后可以将音频数据送入这些设备进行播放。对于视频,SDL提供了一个简单的2D渲染API,可以创建窗口,加载图像,并且将它们渲染到屏幕上。
在跨平台方面,SDL支持多个操作系统,包括Windows、Mac OS X、Linux、iOS和Android。SDL的跨平台能力非常强,因为其接口在不同平台上尽可能地保持一致,差异性由其内部实现来抹平。
SDL官方网站地址是 https://www.libsdl.org/。
GitHub地址:https://github.com/libsdl-org/SDL/releases
SDL 环境搭建
一般步骤:
-
安装SDL:
-
对于Linux系统:
sudo apt-get install libsdl2-2.0 sudo apt-get install libsdl2-dev
这将安装SDL库及其开发文件。
-
对于Windows系统:
- 从SDL官方网站下载SDL开发库。
- 解压并将其包含在你的项目目录中或者设置系统环境变量以便编译器能找到这些文件。
-
对于macOS系统:
- 可以使用Homebrew来安装SDL:
brew install sdl2
-
-
安装FFmpeg:
- 对于Linux系统:
sudo apt-get update sudo apt-get install ffmpeg
- 对于Windows系统:
- 从FFmpeg官方网站下载构建好的二进制文件。
- 解压缩到你的项目目录或者添加到你的系统路径。
- 对于macOS系统:
- 可以使用Homebrew来安装FFmpeg:
brew install ffmpeg
- 对于Linux系统:
-
集成FFmpeg和SDL库到你的项目中:
- 确保在你的项目设置中包含了正确的头文件路径和库文件路径。如果你使用IDE(如Visual Studio或者Xcode),则需要在项目设置中指定这些文件。
- 包含必要的库文件链接。例如,在Linux上使用g++编译时,你可能需要链接SDL和FFmpeg的库,如下:
g++ main.cpp -o app.out `sdl2-config --cflags --libs` -lavformat -lavcodec -lavutil -lswscale
- 在代码中包含SDL和FFmpeg的头文件,例如:
#include <SDL.h> #include <libavformat/avformat.h> #include <libavcodec/avcodec.h>
以下是SDL库中一些常用模块的主要结构体和函数的概览,这将帮助你了解SDL中的一些关键组件:
常用结构体
- SDL_Window - 表示一个窗口。
- SDL_Renderer - 用于2D渲染的渲染器。
- SDL_Surface - 表示一个二维图像或图像集合。
- SDL_Texture - 通过渲染器使用的一块图像数据。
- SDL_Rect - 定义矩形区域的位置和大小。
- SDL_Color - 表示一个RGBA颜色。
- SDL_Event - 表示一个事件,例如键盘、鼠标事件等。
- SDL_AudioSpec - 定义音频输出格式和回调。
- SDL_PixelFormat - 描述像素格式。
常用函数
- SDL_Init - 初始化SDL库。
- SDL_CreateWindow - 创建一个窗口。
- SDL_DestroyWindow - 销毁一个窗口。
- SDL_CreateRenderer - 创建一个2D渲染器。
- SDL_DestroyRenderer - 销毁渲染器。
- SDL_CreateTextureFromSurface - 从表面创建纹理。
- SDL_PollEvent - 检索当前的事件。
- SDL_RenderClear - 清除当前渲染目标。
- SDL_RenderCopy - 将纹理复制到当前渲染目标。
- SDL_RenderPresent - 更新屏幕上的渲染内容。
- SDL_LoadBMP - 加载BMP图像文件到SDL_Surface。
- SDL_FreeSurface - 释放Surface。
- SDL_OpenAudio - 打开音频设备。
- SDL_PauseAudio - 暂停或开始播放音频。
- SDL_CloseAudio - 关闭音频设备。
高级模块下的结构体和函数
除了上述基本功能模块外,SDL还包括其他高级别的模块如:
- SDL_ttf - 用于字体渲染。
- SDL_image - 用于加载多种图像格式。
- SDL_mixer - 用于音频混音。
- SDL_net - 提供网络功能。
每个高级模块都有它的特定结构体和函数,是独立于基础SDL功能的。
SDL 事件处理
SDL 的事件处理机制是基于事件队列的。SDL对于不同的输入和系统事件都定义了一个事件类型。当这些事件发生时,它们被加入到一个中央事件队列中。你的应用程序则负责周期性地从这个队列中读取和处理这些事件。
以下是SDL事件处理的基本原理:
-
事件类型:
- SDL定义了多种类型的事件,如键盘按键事件(SDL_KEYDOWN、SDL_KEYUP)、鼠标事件(SDL_MOUSEMOTION、SDL_MOUSEBUTTONDOWN、SDL_MOUSEBUTTONUP)和窗口事件(SDL_WINDOWEVENT)等。
-
事件队列:
- 当事件发生时,例如用户按下键盘、移动鼠标或者窗口状态变更,这些事件被创建并加入到SDL的中央事件队列中。
-
事件轮询:
- 你的应用程序通过调用
SDL_PollEvent()
或SDL_WaitEvent()
不断查询事件队列。SDL_PollEvent()
非阻塞地检查队列,并立即返回;如果没有事件,它返回0。SDL_WaitEvent()
则是阻塞操作,直到队列中有事件时才返回。
- 你的应用程序通过调用
-
事件处理:
SDL_PollEvent()
从队列中抓取一个事件并将其放入一个SDL_Event
结构体实例中。你的代码需要根据事件的类型进行检查,并执行相应的操作。
一个简单的事件轮询循环可能看起来像这样:
SDL_Event event;
while (running) {
// 轮询获取事件
while (SDL_PollEvent(&event)) {
// 处理得到的事件
switch (event.type) {
case SDL_QUIT:
running = false;
break;
case SDL_KEYDOWN:
if (event.key.keysym.sym == SDLK_ESCAPE) {
running = false;
}
break;
// 更多事件处理...
}
}
// 执行其它游戏循环逻辑...
}
在这个例子中,running
是一个控制应用程序主循环的变量;我们继续轮询直到收到一个SDL_QUIT事件,通常由用户关闭窗口时产生,或用户按下了Escape键。
通过这种方式,SDL提供了一种相对简单的机制来处理用户输入和其他类型的事件,使得开发者可以轻松进行事件驱动的程序开发。
SDL 线程处理
SDL提供了一套自己的线程管理API,允许你在应用内创建和管理线程。SDL的线程处理API相对平台无关,意味着你可以在不同操作系统上使用大致相同的代码来处理线程。
SDL线程的处理主要遵循以下几个原则:
-
线程创建:
- 使用
SDL_CreateThread()
函数来创建一个新线程。你需要提供一个函数指针,SDL将会在新线程内调用这个函数。你也可以传递一个指向任何数据的指针,该数据将作为参数传递给线程函数。
- 使用
-
线程函数:
- 线程函数是你希望在新线程中运行的代码。这个函数需要遵循特定的原型(符合SDL_ThreadFunction类型定义),并且返回一个
int
作为线程退出代码。
- 线程函数是你希望在新线程中运行的代码。这个函数需要遵循特定的原型(符合SDL_ThreadFunction类型定义),并且返回一个
-
线程同步:
- SDL提供了几种同步机制,包括互斥锁(
SDL_mutex
)、条件变量(SDL_cond
)和信号量(SDL_sem
),以帮助管理线程间的协作和避免竞争条件。
- SDL提供了几种同步机制,包括互斥锁(
-
线程管理:
- 创建线程后,你可以使用
SDL_WaitThread()
等待一个线程结束。使用SDL_GetThreadID()
来获取线程标识符,或者用SDL_ThreadID()
获取当前线程的ID。
- 创建线程后,你可以使用
-
线程安全:
- 虽然SDL封装了线程创建和同步机制,但不保证所有的SDL API都是线程安全的。你需要确保当多个线程想要访问共享资源时,使用适当的同步机制来避免并发问题。
下面是一个使用SDL创建和管理线程的简单示例:
#include <SDL.h>
int threadFunction(void* data) {
// 线程的执行代码
// ...
return 0;
}
int main(int argc, char* argv[]) {
if (SDL_Init(SDL_INIT_EVERYTHING) != 0) {
// 初始化失败处理
return -1;
}
// 创建新线程
SDL_Thread* thread = SDL_CreateThread(threadFunction, "MyThread", (void*)NULL);
// 等待线程结束
SDL_WaitThread(thread, NULL);
SDL_Quit();
return 0;
}
在这个例子中,我们创建了一个名为"MyThread"的新线程,并在新线程中执行threadFunction
函数。SDL_WaitThread
用于等待这个线程结束才退出程序。记住在多线程编程时,要特别注意资源共享和同步问题。
SDL提供了哪些线程同步机制?
SDL提供了几种线程同步机制来控制对共享资源的访问,确保线程安全。这些同步机制包括:
-
互斥锁:
- 创建: 通过
SDL_CreateMutex()
创建一个互斥锁。 - 锁定: 使用
SDL_LockMutex()
来锁定互斥锁,如果互斥锁已经被另一个线程锁定,则当前线程将会阻塞直到互斥锁被解锁。 - 尝试锁定:
SDL_TryLockMutex()
尝试锁定互斥锁,但如果锁定不成功,不会阻塞线程。 - 解锁:
SDL_UnlockMutex()
解锁互斥锁,允许其他线程能够锁定它。 - 销毁: 使用
SDL_DestroyMutex()
来销毁互斥锁。
- 创建: 通过
-
信号量:
- 创建: 通过
SDL_CreateSemaphore()
创建一个带有初始计数值的信号量。 - 等待信号量:
SDL_SemWait()
会递减信号量的计数值,如果信号量的值为0,则线程将阻塞。 - 尝试等待:
SDL_SemTryWait()
尝试递减信号量的计数值,但在信号量的值为0时不会阻塞线程。 - 递增信号量:
SDL_SemPost()
递增信号量的计数值,可能会释放正在等待的线程。 - 销毁:
SDL_DestroySemaphore()
销毁信号量对象。
- 创建: 通过
-
条件变量:
- 创建: 用
SDL_CreateCond()
创建一个条件变量。 - 等待条件变量:
SDL_CondWait()
使线程阻塞,直到条件变量被信号化。通常这与互斥锁一起使用,以便线程在阻塞前释放互斥锁并再次获取互斥锁以继续执行。 - 带超时的等待:
SDL_CondWaitTimeout()
等待条件变量,但如果在给定的超时时间内条件没有被信号化,则返回。 - 信号化一个线程:
SDL_CondSignal()
信号化一个等待中的线程来继续执行。 - 广播信号:
SDL_CondBroadcast()
将信号发送给所有等待的线程。 - 销毁:
SDL_DestroyCond()
销毁条件变量。
- 创建: 用
视频YUV画面渲染
在SDL中渲染YUV视频数据,通常涉及到使用SDL_Texture
和SDL_Renderer
来绘制YUV图像。
以下是步骤:
-
初始化SDL:
- 在开始之前,确保调用
SDL_Init(SDL_INIT_VIDEO)
来初始化SDL视频子系统。
- 在开始之前,确保调用
-
创建窗口和渲染器:
- 使用
SDL_CreateWindow
创建一个窗口。 - 使用
SDL_CreateRenderer
创建一个与窗口关联的渲染器。
- 使用
-
创建YUV纹理:
- 使用
SDL_CreateTexture
创建一个YUV格式的纹理(SDL_PIXELFORMAT_IYUV
或SDL_PIXELFORMAT_YV12
)。
- 使用
-
更新纹理与YUV数据:
- 使用
SDL_UpdateYUVTexture
来更新纹理的Y、U、V平面。传入Y、U、V平面的数据和它们各自的行跨距(pitch)。
- 使用
-
渲染纹理:
- 使用
SDL_RenderClear
来清除当前渲染目标。 - 使用
SDL_RenderCopy
将YUV纹理复制到渲染器上。 - 使用
SDL_RenderPresent
来更新窗口显示内容。
- 使用
-
循环处理视频帧:
- 对于视频播放,你需要在播放循环中重复执行更新纹理和渲染纹理的步骤,以呈现每个视频帧。
-
资源清理:
- 使用
SDL_DestroyTexture
来销毁纹理。 - 使用
SDL_DestroyRenderer
来销毁渲染器。 - 使用
SDL_DestroyWindow
来销毁窗口。 - 最终调用
SDL_Quit
来退出SDL并清理资源。
- 使用
以下是基于SDL 2.0的伪代码示例,展示了如何渲染YUV画面:
SDL_Init(SDL_INIT_VIDEO);
SDL_Window* window = SDL_CreateWindow("YUV Player",
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
width, height,
SDL_WINDOW_SHOWN);
SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
SDL_Texture* texture = SDL_CreateTexture(renderer,
SDL_PIXELFORMAT_IYUV,
SDL_TEXTUREACCESS_STREAMING,
width, height);
while(playing) {
// 其中yPlane, uPlane, vPlane是外部提供的YUV视频帧数据
SDL_UpdateYUVTexture(texture, NULL,
yPlane, yPitch,
uPlane, uPitch,
vPlane, vPitch);
SDL_RenderClear(renderer);
SDL_RenderCopy(renderer, texture, NULL, NULL);
SDL_RenderPresent(renderer);
// 处理SDL事件,检查是否退出等...
}
SDL_DestroyTexture(texture);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
如何使用SDL渲染RGB图像?
在SDL中渲染RGB图像相比于YUV图像稍显简单,因为不需要处理不同的色彩平面。
使用SDL渲染RGB图像:
-
初始化SDL:
- 使用
SDL_Init(SDL_INIT_VIDEO)
初始化SDL视频子系统。
- 使用
-
创建窗口:
- 使用
SDL_CreateWindow
创建一个新的窗口。
- 使用
-
创建渲染器:
- 使用
SDL_CreateRenderer
创建一个渲染器,链接到你的窗口。
- 使用
-
创建纹理:
- 使用
SDL_CreateTexture
创建一个纹理,其像素格式设置为RGB格式(如SDL_PIXELFORMAT_RGB24
或SDL_PIXELFORMAT_ARGB8888
,取决于你的RGB数据是24位还是32位)。
- 使用
-
更新纹理:
- 使用
SDL_UpdateTexture
来将你的RGB数据上传到纹理。你需要提供原始的RGB数据和该数据的行跨距或者 pitch(即每行像素的字节数)。
- 使用
-
清空渲染器:
- 使用
SDL_RenderClear
来清除当前渲染目标。
- 使用
-
渲染纹理:
- 使用
SDL_RenderCopy
将纹理复制到渲染器上。
- 使用
-
显示图像:
- 使用
SDL_RenderPresent
将上述所有的渲染操作呈现到屏幕上。
- 使用
-
循环处理:
- 如果你正在渲染视频或动画,你需要在一个循环中重复上述更新纹理和渲染纹理的步骤。
-
释放资源:
- 使用
SDL_DestroyTexture
来销毁纹理,SDL_DestroyRenderer
来销毁渲染器,以及SDL_DestroyWindow
来销毁窗口。 - 最后,调用
SDL_Quit
清理和退出SDL。
- 使用
以下是一个基本的示例代码,演示了如何在SDL中渲染一个RGB图像:
// 初始化SDL
SDL_Init(SDL_INIT_VIDEO);
// 创建窗口
SDL_Window* window = SDL_CreateWindow("RGB Image Renderer",
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
width, height,
SDL_WINDOW_SHOWN);
// 创建渲染器
SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
// 创建纹理
SDL_Texture* texture = SDL_CreateTexture(renderer,
SDL_PIXELFORMAT_ARGB8888, // 对于32位RGB
SDL_TEXTUREACCESS_STREAMING,
width, height);
// RGB数据更新到纹理
SDL_UpdateTexture(texture, NULL, rgbPixels, pitch);
// 清空渲染器
SDL_RenderClear(renderer);
// 绘制纹理到渲染器
SDL_RenderCopy(renderer, texture, NULL, NULL);
// 展示渲染出的图像
SDL_RenderPresent(renderer);
// 呈现相关处理的代码,比如等待时间或事件处理
// 清理资源
SDL_DestroyTexture(texture);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
在此示例中,rgbPixels
代表你的RGB图像数据,pitch
代表每行像素的字节数。这个简单的代码片段无法单独运行,因为它不包含游戏循环或事件处理。
音频PCM声音输出
在SDL中播放PCM音频数据的过程相对直接。你需要设置音频设备的参数,打开音频设备,写入PCM数据到音频缓冲区,最后关闭设备。
下面是播放PCM音频数据的基本步骤:
-
初始化SDL音频子系统:
- 使用
SDL_Init(SDL_INIT_AUDIO)
来初始化SDL音频子系统。
- 使用
-
配置音频规格(
SDL_AudioSpec
):- 创建一个
SDL_AudioSpec
结构体来指定音频数据的格式、声道数、采样率等信息。 - 为
callback
函数设置一个回调,SDL将在需要音频数据时调用这个回调函数。
- 创建一个
-
打开音频设备:
- 使用
SDL_OpenAudio()
或SDL_OpenAudioDevice()
打开音频设备。 - 如果成功,SDL将开始播放音频流,并根据需要调用设置的回调函数来获取音频数据填充到播放缓冲区。
- 使用
-
播放音频:
- 使用
SDL_PauseAudio(0)
或SDL_PauseAudioDevice
来开始播放。 - 在回调函数中,你应该将PCM数据复制到SDL提供的缓冲区中,这会将数据提供给音频设备进行播放。
- 使用
-
音频播放控制:
- 你可以使用
SDL_PauseAudio(1)
暂停音频,并使用SDL_PauseAudio(0)
恢复播放。
- 你可以使用
-
关闭音频设备:
- 使用
SDL_CloseAudio()
或SDL_CloseAudioDevice()
来关闭音频设备,释放资源。
- 使用
下面是一个简单的示例代码,演示了上述步骤:
// 回调函数,当需要更多的音频数据时,SDL将调用它
void audio_callback(void* userdata, Uint8* stream, int len) {
// 用户提供的数据
AudioData* audio = (AudioData*)userdata;
// 从音频数据源复制数据到播放缓冲区
SDL_memset(stream, 0, len);
SDL_memcpy(stream, audio->audio_pos, len);
audio->audio_pos += len;
audio->audio_len -= len;
}
// 初始化SDL音频子系统
SDL_Init(SDL_INIT_AUDIO);
SDL_AudioSpec wanted_spec, obtained_spec;
// 指定PCM音频数据的参数
wanted_spec.freq = 44100; // 采样率
wanted_spec.format = AUDIO_S16SYS; // 16位有符号数,系统字节序
wanted_spec.channels = 2; // 双声道
wanted_spec.samples = 4096; // 音频缓冲区的样本数量
wanted_spec.callback = audio_callback; // 回调函数
wanted_spec.userdata = &audio_data; // 传递给回调的数据
// 打开音频设备
if (SDL_OpenAudio(&wanted_spec, &obtained_spec) < 0) {
fprintf(stderr, "Couldn't open audio: %s\n", SDL_GetError());
return -1;
}
// 开始播放
SDL_PauseAudio(0);
// 处理你的应用程序的逻辑...
// 最后,关闭音频设备并清理
SDL_CloseAudio();
SDL_Quit();
在上述代码中,audio_callback
函数是当播放队列需要更多数据时由SDL调用的。
如何在SDL中配置回调函数来获取音频数据?
在SDL中,配置回调函数来获取音频数据的目的是,在播放音频数据时可以不断地将音频样本提供给音频设备,以便连续播放。
下面是如何配置SDL音频回调函数的步骤:
-
定义音频回调函数:
- 回调函数必须符合
void (*)(void* userdata, Uint8* stream, int len)
的签名。 userdata
参数用于传递给回调函数任何必要的外部数据。stream
参数是指向音频设备需要播放的音频缓冲区的指针。len
参数表示stream
缓冲区的大小,以字节为单位。
- 回调函数必须符合
-
设置音频规格(
SDL_AudioSpec
)结构体:- 将上面定义的回调函数指定给
SDL_AudioSpec
结构体的callback
字段。 - 配置其它字段,比如音频格式(
format
)、采样率(freq
)、声道数(channels
)等。 - 设置
userdata
字段以传递你需要在回调中访问的数据。
- 将上面定义的回调函数指定给
-
打开音频设备并启用回调:
- 使用
SDL_OpenAudio()
或SDL_OpenAudioDevice()
函数打开音频设备,传入SDL_AudioSpec
结构体。 - 如果设备打开成功,当音频设备准备好接收数据时,SDL将定期调用你的回调函数。
- 使用
搭建这些步骤的代码:
// 自定义的音频数据结构
struct MyAudioData {
Uint8* audio_buf; // 音频缓冲区
Uint32 audio_len; // 音频缓冲区的长度
Uint32 audio_pos; // 当前读取的位置
};
// 音频回调函数
void MyAudioCallback(void* userdata, Uint8* stream, int len) {
MyAudioData* myAudioData = static_cast<MyAudioData*>(userdata);
// 填充音频数据
if (myAudioData->audio_len == 0) {
// 音频数据已经播放完毕
memset(stream, 0, len); // 静音处理
return;
}
len = (len > myAudioData->audio_len ? myAudioData->audio_len : len); // 不要超出缓冲区
SDL_memcpy(stream, myAudioData->audio_buf + myAudioData->audio_pos, len); // 复制数据
myAudioData->audio_pos += len;
myAudioData->audio_len -= len;
}
// 主函数中的代码:
SDL_Init(SDL_INIT_AUDIO);
SDL_AudioSpec spec;
MyAudioData audioData;
// 填写结构体的字段
spec.freq = 44100; // 这里设置你的音频采样率
spec.format = AUDIO_S16LSB; // 这里设置你的音频数据格式,比如16位符号整形数据
spec.channels = 2; // 这里设置你的音频声道数
spec.samples = 2048; // 这里设置你的音频缓冲区的大小
spec.callback = MyAudioCallback; // 刚定义的回调函数
spec.userdata = &audioData; // 指向音频数据的指针
// 打开音频设备
if (SDL_OpenAudio(&spec, NULL) != 0) {
SDL_Log("Failed to open audio: %s", SDL_GetError());
// 这里处理打开失败的情况
}
// 加载音频数据到audioData结构体中
// audioData.audio_buf = ...
// audioData.audio_len = ...
// audioData.audio_pos = 0;
// 开始播放音频
SDL_PauseAudio(0);
// 在程序的其它部分管理音频数据和SDL事件
// 清理工作
SDL_CloseAudio();
SDL_Quit();
在这个代码示例中,需要将音频数据加载到audioData.audio_buf
中,并设置audioData.audio_len
为音频缓冲区的字节长度。回调函数MyAudioCallback
会被定期调用,把音频数据从audioData
中复制到stream
缓冲区中,这个缓冲区随后会被音频设备播放出来。SDL的音频回调函数是在一个单独的线程中调用的,所以回调中的操作要尽可能的快,并且要保证线程安全。
如何在SDL中设置音频规格?
在SDL中设置音频规格是通过填充SDL_AudioSpec
结构体完成的,并使用它打开音频设备。下面是设置音频规格的详细步骤:
-
创建
SDL_AudioSpec
结构体实例:
你需要先创建一个SDL_AudioSpec
结构体的实例。 -
指定音频参数:
在SDL_AudioSpec
结构体中,你需要设置几个关键的参数,包括频率(freq
)、音频格式(format
)、声道数(channels
)以及缓冲区采样数(samples
)。 -
设置音频回调:
在SDL_AudioSpec
结构体中设置callback
函数,SDL会在需要新的音频数据来填充音频设备的播放缓冲时调用这个函数。同时,设置userdata
字段,该字段可以在回调中使用,通常用来传递应用程序的音频数据或其它状态信息。 -
打开音频设备:
使用SDL_OpenAudio()
或SDL_OpenAudioDevice()
函数,并提供填充好的SDL_AudioSpec
结构体来打开音频设备。你可以选择接收一个SDL_AudioSpec
的实例,用来接收设备实际使用的音频规格参数,有时候这可能由于硬件限制与请求的参数不完全相符。
下面是代码说明如何填充SDL_AudioSpec
结构体并使用它来打开音频设备:
// 定义回调函数
void MyAudioCallback(void* userdata, Uint8* stream, int len) {
// 填充音频缓冲区的代码
}
// 主函数或初始化音频的部分
SDL_Init(SDL_INIT_AUDIO);
SDL_AudioSpec wanted_spec; // 请求的音频规格
SDL_AudioSpec obtained_spec; // 实际得到的音频规格
// 设置请求的音频规格参数
wanted_spec.freq = 44100; // 设置采样率为44100Hz
wanted_spec.format = AUDIO_S16SYS; // 设置音频数据格式为16-bit signed samples
wanted_spec.channels = 2; // 设置为双声道
wanted_spec.samples = 2048; // 设置缓冲区采样数
wanted_spec.callback = MyAudioCallback; // 设置回调函数
wanted_spec.userdata = NULL; // userdata可用于传递给回调函数的自定义数据
// 打开音频设备并获取设备的实际音频规格
if (SDL_OpenAudio(&wanted_spec, &obtained_spec) != 0) {
fprintf(stderr, "Couldn't open audio: %s\n", SDL_GetError());
// 处理打开音频设备失败的情况
}
// 开始播放
SDL_PauseAudio(0);
// 进入程序的主循环,等待回调函数被调用
// 清理并关闭音频设备
SDL_CloseAudio();
SDL_Quit();
参考资料:
音视频流媒体开发课程(从基础到高级,从理论到实践)学习计划、一对一答疑
音视频开发(FFmpeg/WebRTC/RTMP)
整理了一些音视频开发学习资料、面试题 如有需要自行添加群:739729163 领取