惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

A
About on SuperTechFans
Y
Y Combinator Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Microsoft Security Blog
Microsoft Security Blog
aimingoo的专栏
aimingoo的专栏
I
InfoQ
C
Check Point Blog
IT之家
IT之家
MyScale Blog
MyScale Blog
Apple Machine Learning Research
Apple Machine Learning Research
Vercel News
Vercel News
Last Week in AI
Last Week in AI
GbyAI
GbyAI
P
Proofpoint News Feed
量子位
Stack Overflow Blog
Stack Overflow Blog
Microsoft Azure Blog
Microsoft Azure Blog
月光博客
月光博客
阮一峰的网络日志
阮一峰的网络日志
人人都是产品经理
人人都是产品经理
B
Blog
T
The Blog of Author Tim Ferriss
H
Help Net Security
云风的 BLOG
云风的 BLOG

音视频开发进阶

音视频教程-第三节 音视频教程-第二节 真的,AI 可能就是新时代的信息差 充值 Cursor 之后,工作有了哪些变化?🤔 个人'蒸馏'大模型能做哪些有意思的事情 DeepSeek 大模型在 Mac 上的部署和运行 音视频教程-第一节 【WebRTC 专栏】-- Android 开发集成 WebRTC 库的几种方式 【WebRTC 专栏】-- 在 Mac M1 等系列芯片编译和开发 WebRTC-Android 库 Meta Llama3 大模型在 Mac 上的部署和运行 iOS VideoToolBox 解码 HEVC Open-GOP 视频的问题排查 Flutter 状态管理之 InheritedWidget 使用和分析 用 ChatGPT 回答技术问题怎么样 ? 音视频开发系统入门大致路线 UE 4.27 添加自定义 ShadingModel 用 UE4 虚幻引擎做个捏脸小功能~~ UE4 材质练习 之 凹凸贴图偏移的使用 UE4 材质练习系列基础 OpenGL上下文创建以及共享机制 007 | 播放器系列专栏-解析 MP4 文件读取信息 006 | 播放器系列专栏-在 Mac 上查看 MP4 格式信息 干货 | 快速抽取缩略图是怎么练成的? 关于直播、WebRTC、FFmpeg 的那些事 005 | 播放器系列专栏-在 Windows 上查看 MP4 格式信息 将音视频中的花屏、绿屏、黑屏问题一网打尽 关于音视频里面的解码帧率和渲染帧率 004 | 播放器系列专栏-认识MP4视频(下) 003 | 播放器系列专栏-认识MP4视频(上) 入门或者转行音视频,应该要怎么做? H264视频文件如何缩放分辨率
【音视频连载-007】基础学习篇-SDL 播放 PCM 音频文件(上)
音视频开发进阶 · 2020-03-16 · via 音视频开发进阶

一个专注音视频领域的小圈子

在前面的文章中已经能够利用 SDL 去播放 YUV 视频文件了,接下来要通过 SDL 去播放 PCM 音频文件。 SDL 播放音频文件有两种方法,可以理解成 推(push)拉(pull)两种模式。 就是我们主动向设备缓冲区填充 Buffer ,而 就是由设备拉取 Buffer 填充到缓冲区。 在一些开发模型中,如果数据传递能够抽象成的形式,那么肯定就会有两种模式。 本篇文章主要是讲解 SDL 以推的形式播放音频文件。

首先还是得准备素材,做音视频相关实验就是这么麻烦~~

找一个 mp3 文件,使用 FFmpeg 命令将它转换成 pcm 文件,方便的话可以直接使用代码仓库提供的 mp3 文件。

不像在视频播放中准备素材那样简单,音频文件对于参数的信息要求多一点。首先要使用 ffmpeg 查看 mp3 文件的一些信息,比如采样率、声道数等。

得到如图所示的信息,可以看到 mp3 文件采样率是 44100 Hz,双声道,再使用 FFmpeg 转换时要用到上面的信息。

ffmpeg -i file_name.mp3 -acodec pcm_s16le -f s16le -ac 2 -ar 44100 file_name.pcm

其中:

  • -acodec pcm_s16le

    • 指定编码器
  • -f s16le

    • 指定文件格式,是大端模式还是小端模式
  • -ac 2

    • 指定通道数,2 代表双通道
  • -ar 44100

    • 指定采样率,这里是 44100 Hz

在转换时要根据原文件的采样率和声道数进行转换,否则转换后的 pcm 文件播放声音不对了。

ffplay -ar 44100 -channels 2 -f s16le -i file_name.pcm

通过上面的命令可以验证转换是否正确,还是要注意声道数和采样率的设置,如果没问题的话,说明 PCM 文件素材就准备完毕,可以进行代码实践了。

代码实践

首先要通过 SDL_OpenAudioDevice 方法打开一个音频设备。

SDL_OpenAudioDevice(const char
                 *device,
                 int iscapture,
                 const SDL_AudioSpec * desired,
                 SDL_AudioSpec *obtained,
                 int allowed_changes);

其中结构体 SDL_AudioSpec 指定了一系列音频相关的参数,具体如下:

typedef struct SDL_AudioSpec
{
    int freq;                   /**< DSP frequency -- samples per second */
    SDL_AudioFormat format;     /**< Audio data format */
    Uint8 channels;             /**< Number of channels: 1 mono, 2 stereo */
    Uint8 silence;              /**< Audio buffer silence value (calculated) */
    Uint16 samples;             /**< Audio buffer size in sample FRAMES (total samples divided by channel count) */
    Uint16 padding;             /**< Necessary for some compile environments */
    Uint32 size;                /**< Audio buffer size in bytes (calculated) */
    SDL_AudioCallback callback; /**< Callback that feeds the audio device (NULL to use SDL_QueueAudio()). */
    void *userdata;             /**< Userdata passed to callback (ignored for NULL callbacks). */
} SDL_AudioSpec;

这些参数和音频是息息相关的,比如采样率、声道、音频数据格式、采样个数等,后面会专门去介绍它们。

SDL_OpenAudioDevice 方法有两个参数 desiredobtained 都是 SDL_AudioSpec 类型的。

这里的意思是我们传入 desired 指定的音频参数,但不一定是 SDL 支持的,所以 SDL 会返回一个它支持的参数信息放在 obtained 里面。

不过为了简单就先把它写死好了,但即使写死了有些信息还是要和你的 PCM 文件对应上才行,比如 freg 采样率和 channels 通道数等。

    SDL_AudioSpec audioSpec;
    audioSpec.freq = 44100;
    audioSpec.format = AUDIO_S16SYS;
    audioSpec.channels = 2;
    audioSpec.silence = 0;
    audioSpec.samples = 1024;
    // 因为是推模式,所以这里为 nullptr
    audioSpec.callback = nullptr;

    SDL_AudioDeviceID deviceId;
    if ((deviceId = SDL_OpenAudioDevice(nullptr,0,&audioSpec, nullptr,SDL_AUDIO_ALLOW_ANY_CHANGE)) < 2){
        cout << "open audio device failed " << endl;
        return -1;
    }

注意到 SDL_AudioSpec 有个参数 callback 设置为了 nullptr 。这个回调是为了在 模式中从回调取数据的,因为这里暂时用不到就写成了 nullptr ,下一篇文章就会用到了。

这样就打开了音频设备,返回一个文件 Id,如果结果小于 2 说明打开失败了。

接下来通过 SDL_PauseAudioDevice 方法去播放或者暂停音乐。

SDL_PauseAudioDevice(SDL_AudioDeviceID dev,
                   int pause_on);

SDL_AudioDeviceID 参数就是上面返回的文件 Id,pause_on 为 0 的话代表播放,1 代表暂停。

最后就要开始主动向设备缓冲区填充 Buffer 了。

就向 SDL 播放 YUV 视频那样,要从 PCM 文件中读取一块 Buffer ,然后通过 SDL_QueueAudio 方法进行填充。

    int bufferSize = 4096;
    char* buffer = (char *)malloc(bufferSize);
    // 省略中间代码
    num =  fread(buffer,1,bufferSize,pFile);
    if (num){
        // 填充
        SDL_QueueAudio(deviceId,buffer,bufferSize);
    }

如上代码,首先定义了缓冲区的大小 4096,然后 fread 方法读取这么大的内容,最后把它填充进去。

此时运行程序,就会听到和原来 mp3 文件一样的声音了。

不过这里有要注意的地方,并不是填充了一下 Buffer 就马上会有声音播放出来的,要多填充一些才会有声音播放。

另外,当播放声音时,必须要让程序不能退出,因为音频播放并不是一个阻塞当前主线程的方法,填充完数据就不管了的话,是听不到声音的。要么加个 SDL_Delay 方法要么就把 SDL_QueueAudio 方法放在接受消息队列信息的循环中,我采用的就是后者。

总结

以上就是音视频基础学习连载的 007 篇。

本文具体代码见仓库:

https://github.com/glumes/av-beginner

本篇文章对应的提交 tagav-beginner-004,可切换至对应源码查看。

能力有限,文中有不对之处,欢迎加我微信 ezglumes 进行交流~~

原创文章,转载请注明来源:    【音视频连载-007】基础学习篇-SDL 播放 PCM 音频文件(上)