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

推荐订阅源

有赞技术团队
有赞技术团队
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
aimingoo的专栏
aimingoo的专栏
IT之家
IT之家
G
Google Developers Blog
爱范儿
爱范儿
博客园 - 司徒正美
Recent Announcements
Recent Announcements
The Register - Security
The Register - Security
J
Java Code Geeks
The Cloudflare Blog
M
MIT News - Artificial intelligence
Apple Machine Learning Research
Apple Machine Learning Research
Microsoft Security Blog
Microsoft Security Blog
博客园 - Franky
雷峰网
雷峰网
酷 壳 – CoolShell
酷 壳 – CoolShell
Blog — PlanetScale
Blog — PlanetScale
Vercel News
Vercel News
宝玉的分享
宝玉的分享
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
B
Blog
小众软件
小众软件
Microsoft Azure Blog
Microsoft Azure Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
WordPress大学
WordPress大学
T
Troy Hunt's Blog
Application and Cybersecurity Blog
Application and Cybersecurity Blog
H
Hacker News: Front Page
H
Help Net Security
S
Security @ Cisco Blogs
V
V2EX
Security Archives - TechRepublic
Security Archives - TechRepublic
Stack Overflow Blog
Stack Overflow Blog
O
OpenAI News
L
LINUX DO - 最新话题
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
S
Secure Thoughts
Help Net Security
Help Net Security
F
Full Disclosure
博客园 - 叶小钗
The Hacker News
The Hacker News
Spread Privacy
Spread Privacy
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Jina AI
Jina AI
K
Kaspersky official blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
V
Vulnerabilities – Threatpost
P
Privacy International News Feed
Scott Helme
Scott Helme

音视频开发进阶

音视频教程-第三节 音视频教程-第二节 真的,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视频文件如何缩放分辨率 002 | 播放器系列专栏-FFmpeg依赖库的配置 001 | 播放器系列专栏-关于播放器项目的一个小实践 Seek策略以及在有B帧情况下的处理 目前流媒体开发工程师工作内容主要是什么? 一个音视频领域专业问答的小圈子 干货收藏 || Vulkan Game Engine 视频教程 音视频春节假期内卷指南(实操) Vulkan 在 FFmpeg 中的支持 Windows 下 FFmpeg 和 LibX264 的编译和配置 Metal 开发 | 使用 C++ 进行接口调用 音视频开发工作经验分享 || 视频版 FFmpeg 调用 MediaCodec 硬解码到 Surface 上 代码吸猫 | 用 OpenGL 图像渲染的养猫计划 百倍变速--解码到底能不能丢 非参考帧 ?FFmpeg 有话说!!! 老生常谈-FFmpeg 的编译问题轻松搞定 FFmpeg 调用 Android MediaCodec 进行硬解码(附源码) 【WebRTC 专栏】--创建相机预览 Unity Shader 光照基础之 Half Lambert 光照模型 Unity Shader 光照基础之Lambert光照模型 Unity Shader 光照基础内容 Unity Shader 显示一张图片纹理 UnityShader 的基本概念 Unity 物体的基本操作 C++ 模板系列小结07-尾置返回类型 C++ 模板系列小结06-可变参数模板特性 C++ 中的多线程的使用和线程池建设 C++ 模板系列小结05-模板类型作为模板参数 C++ 模板系列小结04-类模板中的成员模板 C++ 模板系列小结03-在模板中指定变量类型 C++ 模板系列小结02-非类型模板参数 C++ 模板系列小结01-函数模板和类模板 从零打造渲染引擎系列01-什么是渲染引擎 iOS开发 - 在 Swift 中去调用 C/C++ 代码 2021 技术新番 - 从零打造渲染引擎系列 iOS 音视频开发的一些基础准备工作 音视频交流群又来啦~~~ 【WebRTC 专栏】WebRTC & Android 开发学习环境搭建~ 【喜大普奔】域名终于备案通过啦 Shader 优化 | OpenGL 绘制网格效果 【音视频连载-011】第二季 FFmpeg 一层一层获取文件信息 KodeLife | Shader 实时编辑预览的强大工具使用实践 推荐几个堪称教科书级别的 Android 音视频入门项目 【音视频连载-010】第二季 FFmpeg 日志打印 【音视频连载-008】基础学习篇-SDL 播放 PCM 音频文件(下) 【音视频连载-006】基础学习篇-SDL 播放 YUV 视频文件 【音视频连载-005】基础学习篇-SDL 加载 YUV 文件并显示 【音视频连载-004】基础学习篇-SDL 加载图片并显示 【音视频连载-003】基础学习篇-SDL 消息循环和事件响应 【音视频连载-002】基础学习篇-SDL 创建窗口并显示颜色 【音视频连载-001】基础学习篇- SDL 介绍以及工程配置 LearnOpenGL 源码在 MAC 上的编译与调试 2019 年终总结与回顾 Android NDK 开发的免费技术视频来啦~~ OpenGL 实现视频编辑中的转场效果 OpenGL 实践之贝塞尔曲线绘制 图像库 libjpeg-turbo 编译与实践 图像库 libpng 编译与实践 rust 开发编译 Android 动态库实践 Android NDK 开发 —— 从 Assets 文件夹加载图片并上传纹理 简单易用的图像解码库介绍 —— stb_image 博客图床迁移记 进击的 Vulkan 移动开发之 SwapChain 进击的 Vulkan 移动开发之 Command Buffer 进击的 Vulkan 移动开发之 Instance & Device & Queue 进击的 Vulkan 移动开发(一)之今生前世 Java 显式锁 Lock 与条件队列 C++ 标准容器库小结 MediaCodec 硬编码之相机内容编码成 H264 文件 一文读懂 YUV 的采样与格式 《OpenGL ES 3.x 游戏开发》碰撞检测之 AABB 包围盒
【音视频连载-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 音频文件(上)